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
- Get the code, and copy
.env.exampleto.envin its folder. - In
.env, setSECRET_KEY_BASEandDB_PASSWORDto long random values, whichopenssl rand -hex 64makes, andAPP_HOSTSto your domain. - 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.
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.