Lanternlog

Docs

How to install Lanternlog, create blogs on it and publish posts through its API.

On this page
  1. Install
  2. Settings
  3. Create a blog
  4. Your own brand
  5. Publish posts and pages
  6. API reference
  7. What each blog serves

Everything Lanternlog does is set through environment variables and a small HTTP API. This page covers both.

Install

With Docker:

git clone https://github.com/backmeupplz/lanternlog && cd lanternlog
docker build -t lanternlog .
docker run -d --name lanternlog -p 8080:8080 -v lanternlog:/data \
  -e LANTERNLOG_TOKEN="$(openssl rand -hex 32)" lanternlog

From source, with Rust installed:

cargo build --release
LANTERNLOG_TOKEN=change-me ./target/release/lanternlog

Point each blog’s domain at the server and put a TLS proxy in front of it; any proxy that passes the Host header works. On Tugport, deploy the repository as one service with a volume at /data, set LANTERNLOG_TOKEN, and add every blog’s domain to the service. Tugport issues the certificates and deploys each push without downtime.

Settings

VariableDefaultWhat it does
PORT8080Port to listen on
LANTERNLOG_DATA./dataFolder for lanternlog.db and uploaded images
LANTERNLOG_TOKENnoneBearer token for the admin API; the API is off without it
LANTERNLOG_SCHEMEhttpsScheme used in addresses in pages and feeds
LANTERNLOG_INDEXNOWonoff stops IndexNow pings
LANTERNLOG_FONTSnoneAn extra folder of fonts for share images (Inter is in the Docker image)

Create a blog

Every API call needs Authorization: Bearer $LANTERNLOG_TOKEN and can go to any of the server’s domains.

curl -X PUT https://blog.example.com/api/sites/blog.example.com \
  -H "Authorization: Bearer $LANTERNLOG_TOKEN" -H "Content-Type: application/json" -d '{
    "title": "Example Notes",
    "description": "What we learn running Example in production.",
    "aliases": ["www.blog.example.com"],
    "author": "Jane Doe", "author_url": "https://jane.example",
    "project_name": "Example", "project_url": "https://example.com",
    "cta": "Example deploys an app with one command.",
    "accent": "#e4572e", "lang": "en"
  }'
FieldWhat it does
title, descriptionThe blog’s name and one-sentence summary, used in titles, feeds, llms.txt and the home page
aliasesOther domains that redirect here, such as www.
author, author_urlThe person in every post’s structured data; without one, the blog itself is the author
project_name, project_url, ctaThe product the blog writes about: linked in the header and in a call to action after every post
accentA color such as #e4572e for links, buttons and share images
langThe language tag of the blog, such as en or pt-BR
homeThe slug of a page to show at / instead of the plain list of posts, with the latest posts below it

Your own brand

A blog can look like your product rather than like Lanternlog. All of these are optional.

FieldWhat it does
logoAn image shown next to the blog’s name in the header and drawn into every share card. Upload it with the media call first, as a PNG, JPEG or GIF, so the cards can use it
faviconThe browser icon; without one, the logo is used, and without a logo, a generated icon
og_imageThe share image for the home page and other pages without their own; without one, Lanternlog draws a card
linksUp to 8 header links, such as [{"label": "Pricing", "url": "https://example.com/pricing"}]
headHTML added to every page’s head, such as a search console verification tag or an analytics snippet
footerA line of text for the footer, such as a copyright notice
fontserif (the default) or sans for the text of posts
creditfalse removes “Published with Lanternlog” from the footer

A post can also set og_image to use a share image of its own instead of the generated card.

Publish posts and pages

curl -X PUT https://blog.example.com/api/sites/blog.example.com/posts/one-server \
  -H "Authorization: Bearer $LANTERNLOG_TOKEN" -H "Content-Type: application/json" -d '{
    "title": "Moving to one server",
    "description": "Why we put every service on one machine, and what it cost.",
    "body": "We moved **every service** to one box.\n\n## Why one box\n\n...",
    "tags": ["Self-hosting"],
    "status": "published"
  }'

The slug comes from the address. A post stays a draft until its status is published, and its first publication date is kept when you edit it later. Set "kind": "page" for pages such as About: they appear in the header rather than in the post list and the feed. cover takes an image address shown at the top of the post, and og_image replaces the post’s generated share card.

Posts are Markdown with tables, footnotes, task lists, smart quotes and heading ids (## Setup {#setup}). Raw HTML passes through untouched, so only give the token to writers you trust.

API reference

CallWhat it does
GET /api/sitesAll blogs
GET, PUT, DELETE /api/sites/{host}One blog; PUT creates or replaces its settings
GET /api/sites/{host}/postsIts posts and pages, drafts included
GET, PUT, DELETE /api/sites/{host}/posts/{slug}One post; PUT creates or replaces it
POST /api/sites/{host}/mediaUploads an image (the raw bytes with their Content-Type) and answers with its address
GET /api/health{"ok": true, "version"}

What each blog serves

AddressWhat it is
/The home page: the latest posts, or the page named in home
/{slug}/A post or page; /{slug} redirects here
/{slug}.mdThe same post as Markdown, for AI assistants
/tags/{tag}/Posts with a tag (kept out of search results)
/feed.xmlAn Atom feed with the full text of the latest 50 posts
/sitemap.xml, /robots.txtFor search engines
/llms.txt, /llms-full.txtA map of the blog for AI assistants, and every post in one file
/og/{slug}.png, /og.pngShare images for each post and for the blog
/favicon.svgA favicon made from the blog’s initial and accent color