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_dump of 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's secret_key_base (and app_secrets_key if 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 from secret_key_base), make sure the new server also has none, so it uses the restored secret_key_base:
    docker service update --env-rm APP_SECRETS_KEY gitblixt
    (also delete the line from /opt/gitblixt/.env so 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.