Backup & Restore
The app runs as a Docker Swarm service named gitblixt, not a
Compose service
— only Postgres and Caddy are managed by Compose. So you run bin/gitblixt
commands by exec-ing into the running task container, not with docker compose exec:
docker exec $(docker ps -q -f name=gitblixt.1) bin/gitblixt ...
What's in a Backup
-
A full
pg_dumpof the database (users, repos metadata, issues, MRs, settings…) - All bare git repositories (
/data/repos) - Uploads — avatars and attachments (
/data/uploads)
And, when encryption is enabled, a second object carrying the secret material needed to decrypt the encrypted-at-rest database columns (see below).
Not included:
SSH host keys (/data/ssh) and your .env. After a restore onto a new host, users will get a one-time SSH
"host key changed" warning, and you re-enter host-level config (domain, Postgres
password).
SMTP set via Admin → Settings is stored in the database, so it does come back
with the restore; SMTP set only via .env
does not.
Creating a Backup
The recommended path is Admin → Backups — configure a schedule, trigger on-demand backups, and store locally or to S3. To run one from the shell:
docker exec $(docker ps -q -f name=gitblixt.1) bin/gitblixt eval "Gitblixt.Backups.trigger_backup(nil)"
Backups land in /data/backups
(i.e. /opt/gitblixt/data/backups
on the host, via the bind mount) or in your configured S3 bucket.
Encrypted Backups
Enable encryption under Admin → Backups → Encryption. GitBlixt generates a keypair and shows you the private key once — save it immediately (e.g. in 1Password). The server keeps only the public key, so it can encrypt new backups but can never decrypt its own backups. A stolen server therefore cannot reveal any past backup — but if you lose the private key, the backups are unrecoverable. There is no recovery path; that is the point.
Each encrypted backup is two objects:
gitblixt-backup-TIMESTAMP-ID.tar.gz.enc— database, repos, and uploads-
gitblixt-backup-TIMESTAMP-ID.key.enc— the source server'ssecret_key_base(andapp_secrets_keyif set), required to decrypt the encrypted-at-rest columns
You need both objects plus the private key to restore.
Critical: the private key must be PEM/PKCS#1
The key must look like -----BEGIN RSA PRIVATE KEY-----. If you saved an
OpenSSH-format key instead (-----BEGIN OPENSSH PRIVATE KEY-----), the
restore fails with a :public_key.pem_entry_decode error. Convert it:
chmod 600 backup-private.pem
ssh-keygen -p -m PEM -N "" -f backup-private.pem
head -1 backup-private.pem # should now read: -----BEGIN RSA PRIVATE KEY-----
Restoring a Backup
Restore runs through a running GitBlixt instance — so the app must already be installed and running (see Getting Started). For a full new-server move (DNS, secrets, PaaS apps), follow Migrating to a New Server; the mechanics below are the core of it.
Always restore into an EMPTY database. If the app has already booted
against the target DB, it has run migrations, and pg_restore --clean only
drops objects present in the (possibly older) dump — leaving newer tables orphaned and
reverting schema_migrations, producing a broken hybrid schema. Drop and
recreate the database first, restore, then run migrations.
1.
Stage all three files where the container can read them — under /opt/gitblixt/data
(the bind mount, visible as /data
inside the container):
sudo mkdir -p /opt/gitblixt/data/backups
sudo cp gitblixt-backup-TIMESTAMP-ID.tar.gz.enc /opt/gitblixt/data/backups/
sudo cp gitblixt-backup-TIMESTAMP-ID.key.enc /opt/gitblixt/data/backups/
sudo cp backup-private.pem /opt/gitblixt/data/backup-private.pem
2. Reset the database to empty (the app must be stopped so it releases connections):
cd /opt/gitblixt
docker service scale gitblixt=0
PG=$(docker ps -q -f name=postgres)
docker exec "$PG" psql -U gitblixt -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='gitblixt' AND pid <> pg_backend_pid();"
docker exec "$PG" psql -U gitblixt -d postgres -c "DROP DATABASE gitblixt;"
docker exec "$PG" psql -U gitblixt -d postgres -c "CREATE DATABASE gitblixt OWNER gitblixt;"
3. Run the restore in a one-off container against the empty DB:
set -a; . /opt/gitblixt/.env; set +a
docker run --rm --network gitblixt -v /opt/gitblixt/data:/data \
-e DATABASE_URL="ecto://gitblixt:$POSTGRES_PASSWORD@postgres/gitblixt" \
willc0de4food/gitblixt:latest \
bin/gitblixt eval 'Gitblixt.Release.restore("/data/backups/gitblixt-backup-TIMESTAMP-ID.tar.gz.enc", "/data/backups/gitblixt-backup-TIMESTAMP-ID.key.enc", "/data/backup-private.pem")'
This decrypts both objects, restores the database, writes the source server's
secret_key_base
into /data/config, and unpacks repos and uploads.
4. Bring the app back; its entrypoint migrates the freshly-restored DB cleanly:
docker service scale gitblixt=1
docker service ps gitblixt # wait for Running
5. Shred the private key:
shred -u /opt/gitblixt/data/backup-private.pem
Encryption-key continuity (or your secrets won't decrypt)
Encrypted-at-rest columns — AI provider keys, app env vars, OAuth tokens, webhook secret
tokens, CI variables, mirror credentials — are encrypted with a key derived from the
source server's secret_key_base
(or an explicit APP_SECRETS_KEY).
The restore brings the source secret_key_base
across, but an explicit APP_SECRETS_KEY set in the environment overrides it.
-
If your source server used no
APP_SECRETS_KEY(the default — the key is derived fromsecret_key_base), make sure the new server also has none, so it uses the restoredsecret_key_base:
(also delete the line fromdocker service update --env-rm APP_SECRETS_KEY gitblixt/opt/gitblixt/.envso a restart doesn't re-add it). -
If your source server set an explicit
APP_SECRETS_KEY, set the same value on the new server before relying on encrypted features.
Verify by opening something backed by an encrypted column after restore — e.g. an AI provider key in Settings, or Send Test on the SMTP settings page. A clean result means the key lines up.