Skip to content
Serelane

Operations

The whole of /data, and nothing else.

Three things live in it:

serelane.db The database: projects, entries, votes, comments, accounts, sessions
secret.key The signing key, generated on first boot unless you set SERELANE_SECRET_KEY
uploads Images attached to entries

A backup that skips secret.key is not a backup. That key signs sessions, sign-in links and anonymous cookies. Restoring without it signs every user out, invalidates every pending sign-in link, and detaches every anonymous identity from the votes and comments it made.

If you would rather not depend on a generated file, set SERELANE_SECRET_KEY yourself and keep it wherever you keep secrets.

SQLite is being written to while you copy it, and a plain cp of a live database can capture a torn file.

Use SQLite’s own backup, which takes a consistent snapshot of a database in use:

sqlite3 /data/serelane.db ".backup '/backup/serelane.db'"
cp /data/secret.key /backup/

If you would rather not install a SQLite client, stop the container first:

docker stop serelane
tar czf serelane-backup.tar.gz -C /var/lib/docker/volumes/serelane/_data .
docker start serelane

The stop is the point. Copying a running database without .backup is the one way to get a backup that restores into a corrupt instance.

docker stop serelane
# replace the volume's contents with the backup
docker start serelane

Serelane migrates the schema forward at boot, so a backup from an older release restores into a newer binary without a separate step.

The reverse is not true. A database written by a newer release refuses to start on an older binary, with database schema is newer than this binary. That is deliberate: running old code against a schema it does not understand corrupts data slowly rather than failing loudly.

docker compose pull
docker compose up -d

Migrations run automatically at boot, in version order, each in its own transaction, so a failure leaves no half-applied schema.

While any are pending, /readyz reports not-ready. A load balancer watching it keeps traffic off an instance that is still migrating.

If you would rather not discover a broken migration while already serving:

docker run --rm -v serelane:/data serelane/serelane:latest migrate

That applies what is pending and exits.

A migration that has shipped is never edited, only superseded by a new numbered one. This is enforced in the repository by a test and in CI, and it is why repeating an upgrade is safe: the file that ran on your instance is byte-for-byte the file that ran on everyone else’s.

If a release ever needs to change something an earlier migration did, it does so with a new migration.

There are two, and they answer different questions.

Checks Use it for
/healthz That the process can serve HTTP The container HEALTHCHECK, and any liveness probe
/readyz Also the database, and that no migration is pending A load balancer, and any readiness probe

Do not use /readyz as a liveness check. It reports not-ready during a migration, and a liveness probe reading that will restart the instance mid-migration, repeatedly.

The shipped container image already uses /healthz, which is why it stays healthy through an upgrade.