Archigov · docs
  1. Docs
  2. 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:

  1. If /data/app.db does not exist, it restores the latest state from the replica. With an empty replica it does nothing, so a first start is safe.
  2. 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

SituationNightly backups aloneAdd Litestream
Evaluation, a demo, a laptopEnoughNot needed
Production, and you already snapshot the volume off the hostOften enoughIf you want a recovery point of seconds rather than a day
Production, where losing the volume or the host must not lose dataNot enoughYes
A container platform without persistent volumes (ephemeral disks)Not enoughRequired: it is what brings the data back on every start. The managed service runs this way
Moving to a new hostCopy the volumeStart 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:

Env
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 region

For 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_ENDPOINT set to a non-AWS store failed to start. On those versions, put the endpoint in the URL yourself and leave AWS_ENDPOINT unset: LITESTREAM_URL=s3://archigov-backups/prod/app.db?endpoint=https://<account>.r2.cloudflarestorage.com&forcePathStyle=true

A directory on another disk:

Env
LITESTREAM_URL=file:///replica/app.db

Mount it in docker-compose.prod.yml:

YAML
    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:

  1. Mount the repository's infra/litestream/litestream.yml at /etc/litestream.yml, or set LITESTREAM_CONFIG to its path.
  2. Set LITESTREAM_BUCKET, LITESTREAM_PATH, AWS_ENDPOINT and AWS_REGION.

The example pushes changes every second, snapshots daily and keeps a week.

#3. Start and Watch the Logs

Shell
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/keys is 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_KEY is 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_KEY is 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:

Shell
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:

Shell
# 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-test

Without 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:

Shell
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):

Shell
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:

  1. Stop the instance: docker compose -f docker-compose.prod.yml stop archigov.
  2. Copy app-at-0800.db over /data/app.db on the volume, removing app.db-wal and app.db-shm.
  3. 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.
  4. 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 logsCauseFix
InvalidAccessKeyId from an AWS address while you use another storeThe endpoint was not used: Archigov 0.2.0 or earlier in one-URL modeUpgrade, or put ?endpoint=…&forcePathStyle=true in the URL
lookup <bucket>.<host>: no such hostThe store needs path-style addressingAdd &forcePathStyle=true to the URL, or use config-file mode, which sets it with an endpoint
AccessDenied or 403The key cannot read or write the bucket or pathGrant read, write and list on the bucket
The container restarts in a loopReplication could not start, so the application did not eitherRead the first error line, and check the endpoint, region, bucket and credentials
A new host started empty instead of restoringThe volume was not empty, or the settings point at a different pathThe script only restores when /data/app.db is missing; check LITESTREAM_URL or LITESTREAM_PATH