SQLite Backups
Continuously replicate the Portr server's SQLite database to S3-compatible storage with Litestream, and restore it automatically after a lost volume.
Portr can run on SQLite instead of PostgreSQL, which removes the need for a separate database service. The trade-off is that everything lives in a single file on the host, so losing the ./data directory means losing every user, team, API key and reserved subdomain.
The server image ships with Litestream, which streams the SQLite write-ahead log to S3-compatible object storage as changes happen, and restores the database on boot if the file is missing.
Litestream only applies to SQLite. If PORTR_DB_URL points at PostgreSQL, it is skipped entirely and you should use your database provider's backup tooling instead.
Enable replication
Set PORTR_DB_URL to a SQLite path inside the mounted data directory, then add the Litestream variables to your .env:
Replication is off unless LITESTREAM_REPLICA_URL is set. Leaving it empty keeps the server behaving exactly as it does without backups.
| Variable | Description | Default |
|---|---|---|
LITESTREAM_REPLICA_URL | Replica destination, e.g. s3://bucket/prefix. Empty disables replication | Optional |
LITESTREAM_ACCESS_KEY_ID | Access key for the bucket | Required when replicating |
LITESTREAM_SECRET_ACCESS_KEY | Secret key for the bucket | Required when replicating |
LITESTREAM_REPLICA_ENDPOINT | Custom S3 endpoint for non-AWS providers | Optional |
LITESTREAM_REPLICA_REGION | Bucket region | Optional |
Provider examples
AWS S3 needs only the region:
Cloudflare R2 needs the account endpoint, and uses auto as the region:
MinIO or any self-hosted S3 gateway:
Run migrations in-process
When replication is enabled, set PORTR_AUTO_MIGRATE=true rather than running portrd migrate as a separate one-off container.
Only portrd start is wrapped by Litestream. A one-off portrd migrate container writes to the database without replicating, so those writes reach the replica later, once the server syncs again. Running migrations in-process under start all keeps every write inside the replicated window.
Never run a one-off container that starts its own replication against a database the server is already replicating. Litestream does not lock the replica or detect a second writer, so two daemons will interleave writes into the same replica with no error.
Verify it is working
Litestream logs its sync activity to the container logs:
You can also list what has been written to the replica:
If you pointed PORTR_DB_URL at a path other than /app/data/db.sqlite3, pass -e PORTR_DB_PATH=<your path> to exec and run as well, so Litestream looks up the same database.
Restore
On startup the server restores the database automatically if the file does not exist, which covers a wiped volume or a move to a new host. An existing database is never overwritten, so a restore will not clobber live data.
To restore manually, stop the server, restore alongside the current database, then swap it in. Litestream refuses to write over an existing file, so restoring to a separate path first also keeps the old database around if you need to go back.
Delete the old -wal and -shm files as shown. Leaving them next to a freshly restored database can corrupt it.
If the credentials or the replica URL are wrong, the server fails to start rather than running unreplicated. This is deliberate: a server that silently stops backing up is worse than one that refuses to boot.