Tephra on DigitalOcean
One Droplet with a Volume attached. Simple and cheap.
doctl auth initAn SSH key added to your DigitalOcean accountA domain you can add DNS records to - 1
Grab Tephra
git clone https://github.com/championswimmer/tephra && cd tephra - 2
Launch it
Swap in your own domain. The Droplet, Volume, IP and HTTPS are all set up for you.
DOMAIN=notes.yourname.com ./scripts/deploy/do.sh - 3
Point your domain at it
The script prints an IP address. At your domain provider, add an
Arecord for your domain pointing to that IP. Domain already on DigitalOcean? AddDNS_ZONE=yourname.comto the command above and this is done for you. - 4
Copy your setup token
Give the server about 5 minutes to warm up. The script prints this exact command with your IP filled in.
ssh root@YOUR_IP grep BOOTSTRAP /opt/tephra/tephra.env
Environment variables
The steps above already set everything Tephra needs. This is the complete list for a manual install, a different host, or a change later on.
/opt/tephra/tephra.env on the Droplet (mode 600), loaded by Docker Compose.
ssh root@YOUR_IP 'grep TEPHRA_ /opt/tephra/tephra.env' The deploy script generates both secrets and sets TEPHRA_PUBLIC_URL for you.
| Variable | When | Default | What it does |
|---|---|---|---|
TEPHRA_PUBLIC_URL | Always | none | Browser-visible origin, including the scheme (https://notes.yourname.com). Used for redirects, cookies and MCP OAuth discovery. It must match the address people open. |
TEPHRA_SESSION_SECRET | Always | none | Signs and protects browser sessions. Use at least 32 random bytes, for example openssl rand -hex 32. Never reuse the bootstrap token here. |
TEPHRA_BOOTSTRAP_TOKEN | First boot | none | One-time proof for creating the first admin account at /setup. Delete it from the environment after that account exists. |
HOST | Optional | 0.0.0.0 in the image | HTTP bind address. Leave it at 0.0.0.0 inside a container. |
PORT | Optional | 8080 | Single port for the API and the web app. Railway injects its own value, so do not set it there. |
NODE_ENV | Optional | production in the image | When set to production, session cookies are marked Secure. The container image already sets it. |
TEPHRA_DATABASE_DRIVER | Optional | sqlite | Must be sqlite. Tephra refuses to start with any other value: live storage is disk-only. |
TEPHRA_SQLITE_PATH | Optional | /data/tephra.db in the image | SQLite database file. Keep it on the persistent disk, never in the container layer. |
TEPHRA_BLOB_DRIVER | Optional | filesystem | Must be filesystem, matching the disk-only profile. |
TEPHRA_BLOB_PATH | Optional | /data/blobs in the image | Content-addressed attachment store. Must sit next to the database on the same disk. |
TEPHRA_MAX_BLOB_BYTES | Optional | 104857600 (100 MiB) | Largest attachment Tephra accepts. Platform request limits can be lower; raise both or neither. |
TEPHRA_WEB_ROOT | Optional | /app/tephra-server/apps/web/dist in the image | Directory of the built web app served by the API process. Only change it for custom builds. |
Delete TEPHRA_BOOTSTRAP_TOKEN after your first login. It only exists to create the first admin. Run ssh root@YOUR_IP "sed -i '/BOOTSTRAP_TOKEN/d' /opt/tephra/tephra.env && cd /opt/tephra && docker compose --env-file tephra.env up -d".
Want every detail, every knob? The full DigitalOcean guide is on GitHub.