Self-hosting

You can use Lectern on the hosted service, or run the same app on your own server, with your library on your own disk. Everything works the same on both, including the browser extension and Claude and ChatGPT.

Lectern is open source under the MIT license: download it and use it however you like. It's one Rails app, a PostgreSQL database, and a small service that extracts articles from web pages, all started together with Docker.

Run it

  1. Get the code, and copy .env.example to .env in its folder.
  2. In .env, set SECRET_KEY_BASE and DB_PASSWORD to long random values, which openssl rand -hex 64 makes, and APP_HOSTS to your domain.
  3. Start it:
docker compose -f compose.production.yaml up -d

Lectern builds and starts on port 80, sets up its database on the first run, and is ready when /up answers. Open it and sign up.

Move your library over

Already reading on the hosted service? Download everything from Settings → Your data there, then import it in the same place on your own Lectern.

Put it on the internet

Claude, ChatGPT, and the extension on your other devices need to reach your Lectern, and Claude and ChatGPT need https. Run Lectern behind an HTTPS proxy, such as Caddy, Traefik, Cloudflare Tunnel, or the one Kamal sets up from config/deploy.yml.

For a Lectern that stays on your home network, set ALLOW_HTTP=1 instead.

Email

Lectern sends email to verify addresses and reset passwords. Set SMTP_ADDRESS and the other SMTP_ settings to send it; without them, Lectern skips email and everything else works.

Settings

Variable Does
SECRET_KEY_BASE Required. Signs sessions and shapes public ids; keep it secret and don't change it
DB_PASSWORD Required. The database password
APP_HOSTS The domains Lectern answers on, comma-separated, as in read.example.com; the first is used in emails
PORT The port Lectern listens on; 80 if unset
ALLOW_HTTP Set it to serve plain http, for a Lectern that stays on a home network
ALLOW_PRIVATE_FETCH Set it to save pages from your own network; otherwise Lectern only fetches public pages
API_RATE_LIMIT API requests per minute for each token; 120 if unset
SMTP_ADDRESS, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD Your outgoing mail server
MAIL_FROM Who email comes from

Update

Get the new code, then start it again. Lectern updates its database as it starts.

docker compose -f compose.production.yaml up -d --build

Back up

Your library lives in the database, and the copies of its articles' images in the storage volume. Back up both:

docker compose -f compose.production.yaml exec db pg_dump -U lectern lectern_production > lectern.sql
docker compose -f compose.production.yaml cp web:/rails/storage ./lectern-storage

Restoring the database without the images still leaves every article readable: they show the original images from the web, where those still exist.

Lectern · read later, here or on your own server