- Docs
- Guides
Continuous Replication with Litestream
What Litestream does for Archigov, when you need it rather than the nightly backups, and how to set it up, check it, test a restore and recover to a point in time.
#What It Is
All of Archigov's state is one SQLite file, /data/app.db. Litestream is an open-source tool that streams every change to that file to a replica: object storage (Amazon S3, Cloudflare R2 or any S3-compatible store) or a directory on another disk. Changes are pushed about once a second and a full snapshot is taken daily. History is kept for a day with the simplest setup, or as long as you configure (the example config file keeps a week).
The Archigov image includes Litestream 0.5.17, checksum-verified at build time. You do not install anything: you configure it, and the container's start-up script does the rest. On every start it does two things:
- If
/data/app.dbdoes not exist, it restores the latest state from the replica. With an empty replica it does nothing, so a first start is safe. - It runs Archigov as Litestream's child process, so replication starts and stops with the application.
#Why Archigov Uses It
The nightly backups are always on, but they have two limits:
- They sit on the same volume as the database. Losing the volume or the host loses the backups with it.
- They are up to a day old. A failure in the afternoon loses the morning's decisions, positions and reviews.
A replica fixes both. It lives somewhere else, and it is at most a second or so behind. It can also be restored to any moment within the retention period: a day by default, a week with the example config file.
#When to Use It
| Situation | Nightly backups alone | Add Litestream |
|---|---|---|
| Evaluation, a demo, a laptop | Enough | Not needed |
| Production, and you already snapshot the volume off the host | Often enough | If you want a recovery point of seconds rather than a day |
| Production, where losing the volume or the host must not lose data | Not enough | Yes |
| A container platform without persistent volumes (ephemeral disks) | Not enough | Required: it is what brings the data back on every start. The managed service runs this way |
| Moving to a new host | Copy the volume | Start the new host with an empty volume and the same settings: it restores itself |
Litestream complements the nightly backups; it does not replace them. Keep both.
#Set It Up
#1. Choose Where the Replica Lives
- Object storage. Amazon S3, Cloudflare R2, or any S3-compatible store, including self-hosted ones such as Garage or SeaweedFS. Create a bucket and an access key that can read and write only that bucket. Put the bucket in the same jurisdiction as the instance, so your residency promise covers the replica too.
- A directory on another disk or a NAS share, mounted into the container. This is the simplest option on a single server; make sure it is a different physical disk.
Note
MinIO stopped publishing its community container images in October 2025, so the compose example no longer includes it. Any other S3-compatible store works the same way.
#2. Configure It
Add the settings to the .env file next to docker-compose.prod.yml. Choose one of the following.
Object storage, one URL:
LITESTREAM_URL=s3://archigov-backups/prod/app.db
LITESTREAM_ACCESS_KEY_ID=<key id>
LITESTREAM_SECRET_ACCESS_KEY=<secret>
AWS_ENDPOINT=https://<account>.r2.cloudflarestorage.com # omit for Amazon S3
AWS_REGION=auto # Amazon S3: the bucket's regionFor any endpoint other than Amazon S3, the start-up script adds the endpoint to the replica URL with path-style addressing, which every S3-compatible store accepts.
Warning
Archigov 0.2.0 and earlier did not do this. In this mode, a container with
AWS_ENDPOINTset to a non-AWS store failed to start. On those versions, put the endpoint in the URL yourself and leaveAWS_ENDPOINTunset:LITESTREAM_URL=s3://archigov-backups/prod/app.db?endpoint=https://<account>.r2.cloudflarestorage.com&forcePathStyle=true
A directory on another disk:
LITESTREAM_URL=file:///replica/app.dbMount it in docker-compose.prod.yml:
volumes: ["archigov-data:/data", "/mnt/backup-disk/archigov:/replica"]A config file, for a different sync interval, snapshot cadence or retention. The two settings above keep a day of history; use a config file to keep longer:
- Mount the repository's
infra/litestream/litestream.ymlat/etc/litestream.yml, or setLITESTREAM_CONFIGto its path. - Set
LITESTREAM_BUCKET,LITESTREAM_PATH,AWS_ENDPOINTandAWS_REGION.
The example pushes changes every second, snapshots daily and keeps a week.
#3. Start and Watch the Logs
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml logs archigov | grep -E "entrypoint:|snapshot|error"Expect these lines:
entrypoint: running with Litestream replication to …;- a few seconds later,
snapshot complete.
An error line, or a container that keeps restarting, means the replica is unreachable. See Troubleshooting.
#What It Does Not Cover
/data/keysis never replicated, by design. It holds three things:- the evidence-signing key pair;
- the session secret;
- the secret key that encrypts stored AI provider keys, if
ARCHIGOV_SECRET_KEYis not set.
Keep a copy in your secret store. A restore on a new host without it still brings back every record, but new evidence packs are signed by a new identity, everyone signs in again, and stored AI keys must be re-entered unless
ARCHIGOV_SECRET_KEYis set.One writer per replica path. Never run two instances replicating to the same path. Give every instance its own path, and start test copies without the replication settings, as in Test a Restore.
It is not an archive. History is kept for the retention period. Copy nightly backups off the host if you need to keep years.
#Check It
Run the image's own Litestream with the same .env. A direct command does not go through the start-up script, so for a non-AWS endpoint write the endpoint into the URL yourself:
R="s3://archigov-backups/prod/app.db?endpoint=https://<account>.r2.cloudflarestorage.com&forcePathStyle=true"
# Amazon S3: R="s3://archigov-backups/prod/app.db"
# another disk: R="file:///replica/app.db" (and add -v /mnt/backup-disk/archigov:/replica)
docker run --rm --env-file .env --entrypoint litestream archigov:local ltx "$R"The list shows the replicated files, the newest last. If it grows as you work, replication is running.
#Test a Restore
Do this once when you set it up, and again after any change to the storage. The copy is restored into a scratch volume and started without the replication settings, so it never writes to your replica:
# 1. Restore the latest state into a scratch volume
docker volume create archigov-restore-test
docker run --rm --env-file .env -v archigov-restore-test:/data --entrypoint litestream archigov:local \
restore -o /data/app.db "$R"
# 2. Start a copy on it: no .env, so no replication, reachable only from this host
docker run -d --name archigov-restore-test -p 127.0.0.1:8099:8080 -v archigov-restore-test:/data \
-e SEED_DEMO=false archigov:local
# 3. Check the audit chain: expect "ok": true and the number of rows your instance holds
curl -s --retry 30 --retry-all-errors http://127.0.0.1:8099/api/audit/verify
# 4. Remove the copy and its volume
docker rm -f archigov-restore-test && docker volume rm archigov-restore-testWithout the .env the copy runs in local sign-in mode, so keep it on the loopback address as above, and remove it when you are done. Opening http://127.0.0.1:8099 on the host shows your records, if you want to look before removing it.
To see what a restore would fetch without writing anything:
docker run --rm --env-file .env --entrypoint litestream archigov:local restore -dry-run -o /tmp/check.db "$R"#Recover to a Point in Time
When a mistake needs undoing, restore a copy as it stood at a given moment (UTC):
docker run --rm --env-file .env -v "$PWD:/out" --entrypoint litestream archigov:local \
restore -timestamp 2026-10-16T08:00:00Z -o /out/app-at-0800.db "$R"To make that copy the live database:
- Stop the instance:
docker compose -f docker-compose.prod.yml stop archigov. - Copy
app-at-0800.dbover/data/app.dbon the volume, removingapp.db-walandapp.db-shm. - Point replication at a new path (for example
prod-2/app.db), so the restored history does not mix with the old one. Keep the old replica until you are sure. - Start the instance, and verify the audit chain.
Anything recorded after that moment is gone from the live database, but stays in the old replica if you need to look it up.
#Troubleshooting
| Symptom in the logs | Cause | Fix |
|---|---|---|
InvalidAccessKeyId from an AWS address while you use another store | The endpoint was not used: Archigov 0.2.0 or earlier in one-URL mode | Upgrade, or put ?endpoint=…&forcePathStyle=true in the URL |
lookup <bucket>.<host>: no such host | The store needs path-style addressing | Add &forcePathStyle=true to the URL, or use config-file mode, which sets it with an endpoint |
AccessDenied or 403 | The key cannot read or write the bucket or path | Grant read, write and list on the bucket |
| The container restarts in a loop | Replication could not start, so the application did not either | Read the first error line, and check the endpoint, region, bucket and credentials |
| A new host started empty instead of restoring | The volume was not empty, or the settings point at a different path | The script only restores when /data/app.db is missing; check LITESTREAM_URL or LITESTREAM_PATH |
Something unclear or out of date? Write to hello@archigov.com or open an issue on GitHub.