Backups
Back up databases and volumes with restic, encrypted and deduplicated, on the server or off-site, with retention, and without stopping a game server.
Dockflow backs up the stateful services of an app with restic. Each backup is a snapshot: encrypted, deduplicated (a second backup stores only what changed) and checked.
To back up a service, declare x-dockflow.backup on it, then run dockflow backup run:
services:
db:
image: postgres:17
environment:
POSTGRES_DB: shop
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes: [pgdata:/var/lib/postgresql/data]
x-dockflow:
stateful: true
backup:
type: postgres
keep: { daily: 14, weekly: 8 }
volumes:
pgdata: {}dockflow backup run production
dockflow backup list productionSNAPSHOT TIME SERVICE RELEASE SIZE
3f1c2a9b 2026-10-27 18:02:11Z db 12 4.2 MiBWhat is backed up
A service is backed up when it is stateful and declares backup. Its type says how:
type | How it is captured |
|---|---|
postgres | pg_dump --format=custom, run in the service's own container, so the dump tool is the server's version. The user is POSTGRES_USER (default postgres), the database database or POSTGRES_DB |
volume | The service's volumes and bind mounts, or those listed in volumes, read-only, with its writes stopped as quiesce says |
command | The dump command, run in the service's container: its standard output is the backup. restore reads it back on its standard input |
mysql | mysqldump --single-transaction --routines --events (or mariadb-dump) of database, MYSQL_DATABASE or MARIADB_DATABASE, or every database. The credentials are the image's: MYSQL_ROOT_PASSWORD or MARIADB_ROOT_PASSWORD, else MYSQL_USER and MYSQL_PASSWORD |
mongo | mongodump --archive, of database or every database, as MONGO_INITDB_ROOT_USERNAME |
redis | redis-cli --rdb, with REDIS_PASSWORD when it is set |
The options of backup:
| Option | Type | Default | Description |
|---|---|---|---|
type | string | required | See above. |
database | string | the image's | postgres, mysql, mongo: the database to dump. |
dump_options, restore_options | string | none | Extra arguments of the dump tool (postgres, mysql, mongo, redis) and of the restore tool (postgres, mysql, mongo; a redis restore replaces the data file and takes none), split on spaces, like "--no-owner --no-acl". |
volumes | list | every volume and bind mount | volume: the volumes, by their key in the Compose file, and bind mounts, by their path on the server, to copy. |
quiesce | stop, none or map | stop | volume: how writes stop during the copy. stop stops the service's containers, then starts them again; none copies while it runs; { exec: { pre: [...], post: [...] } } runs pre in the running container before the copy and post after, even when the copy fails or the run is cut: post may then run twice, so it must be harmless to repeat. |
dump, restore | argument lists | required for command | The commands of the command type. |
keep | map | { last: 7, daily: 14, weekly: 8, monthly: 6 } | The snapshots kept: last, hourly, daily, weekly, monthly, as restic's forget --keep-*. |
To back up a game server without stopping it, pause its saves around the copy with quiesce.exec:
services:
game:
image: itzg/minecraft-server
volumes: [world:/data]
x-dockflow:
stateful: true
backup:
type: volume
quiesce:
exec:
pre: ["sh", "-c", "rcon-cli save-off && rcon-cli save-all flush"]
post: ["rcon-cli", "save-on"]
keep: { hourly: 24, daily: 14 }Where backups go
Without configuration, backups go to a restic repository on the server, in the dockflow-backups volume.
Its password is generated on the first backup. It is stored in /var/lib/dockflow/backups/local.password, readable by root only, and the first backup tells you so. Keep a copy of that password elsewhere: the backups cannot be read without it.
A repository on the same disk as the data does not survive the loss of the server. To send backups off-site, set backups in dockflow.yml, with the storage's credentials taken from your secrets:
backups:
repository: s3:s3.eu-west-1.amazonaws.com/acme-backups/${DOCKFLOW_APP}
password: ${RESTIC_PASSWORD}
env:
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}repositoryis any restic repository reachable from the server:s3:(AWS and S3-compatible storage such as OVH, Scaleway or MinIO),b2:,azure:,gs:,sftp:,rest:.envis passed to restic.- The first backup creates the repository, and its bucket when the credentials allow it.
- Without
password, Dockflow generates one, as for the local repository, in/var/lib/dockflow/backups/<id>.password.
Where credentials go
The databases' credentials never leave their containers. The dump and restore commands read them from the image's variables, and pass them to the tool in a temporary file, never on a command line.
The repository's password and the storage's credentials reach restic through a file on the server's tmpfs, readable by root and removed at the end of the run. They never appear on a command line, in a container's configuration or on disk: when /run/dockflow is not a tmpfs, the backup fails with secret.not_tmpfs before writing them.
How a backup runs
dockflow backup run [<env>] [<service>]takes the environment's lock. A backup and a deploy never run at the same time (--wait-lockon a deploy waits for the backup).- The agent opens the repository, and creates it on the first backup.
- It backs up each service of the current release that declares
backup, or the one you named. This is what the server runs, not your working tree. - A dump streams from the service's container into restic, without a temporary file. restic saves it only once the dump has succeeded and all of it has arrived: a dump that fails, or a run that is cut, leaves no snapshot.
- Each snapshot is tagged with the app, the environment, the service and the release. Its host is the server's name.
- The service's retention is applied (
restic forget). Once a day, the space of forgotten snapshots is reclaimed (--prune). restic checkreads 1% of the repository's data, chosen at random, to catch corruption early.
When a service's backup fails, the other services are still backed up, and the command exits with code 12.
backup run needs only the connection string and the repository's secrets. It reads dockflow.yml, not the Compose files.
Scheduling
Nothing runs on a schedule on the server. Run dockflow backup run on a schedule from your CI, or from any cron that has the connection string.
The GitHub workflow that dockflow init --ci github writes runs it every night. The GitLab pipeline that --ci gitlab writes runs it when a pipeline schedule triggers it. To add it to an existing workflow:
on:
schedule: [{ cron: "17 3 * * *" }]
jobs:
backup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: curl -fsSL https://raw.githubusercontent.com/Shawiizz/dockflow/main/install.sh | sh
- run: dockflow backup run production && dockflow backup run production --server
env:
DOCKFLOW_SECRETS: ${{ toJSON(secrets) }}Restoring
dockflow backup restore production db # the latest snapshot
dockflow backup restore production db --snapshot 3f1c2a9b # one snapshot, by its id
dockflow backup restore production db --snapshot 2026-10-20 # the newest one taken by that day
dockflow backup restore staging db --from production # production's data into stagingbackup restore restores a snapshot into the service of the current release, under the environment's lock. It asks for confirmation on a terminal; without a terminal, it needs --yes.
How each kind of backup is restored:
-
A dump streams from the snapshot into the running service, with options that stop at the first error:
- Postgres:
pg_restore --clean --if-exists --single-transaction --exit-on-error, which also rolls back what it did; - MySQL: the
mysql(ormariadb) client; - MongoDB:
mongorestore --archive --drop --stopOnError; - the
commandtype: itsrestorecommand.
restore_optionsadds arguments. MySQL and MongoDB change the data as they read the dump, so the dump is first written whole to a temporary file in the container, and its size checked, before the client reads it: a dump cut short changes nothing. A restore that the database itself stops half-way (an SQL error) is not rolled back, except by Postgres. - Postgres:
-
Redis loads its data when it starts. The dump is written beside its data while it runs; then the service is stopped, the dump replaces
/data/dump.rdbin its container (the official image's data directory) in one step, and the service starts again. Withappendonly yes, Redis loads its append-only file instead: turn it off, or back Redis up as avolume. -
Volumes are restored into new staging volumes first,
dockflow-restore-<run>-<n>, while the service runs. Then the service is stopped, each volume's files are replaced with its staging volume's, and the service starts again. Each volume gets the snapshot's files, and loses those the snapshot does not have. The server needs free space for a copy of the snapshot during the restore; the staging volumes are removed at the end.
--from <env> restores a snapshot of another environment of the same app. It reads that environment's repository, with that environment's secrets: the repository on the server when both run on the same server, or its off-site backups.repository.
A restore that fails exits with code 12, and says why.
When a run is cut
A backup or a restore can be cut at any point: the connection drops, the agent is killed, the server reboots. Dockflow records what it changes before it changes it, in /var/lib/dockflow/apps/<app>-<env>/backup.json, and the watchdog, within seconds, or the next deploy, backup run or backup restore, finishes it:
- a service stopped or quiesced for a backup or a restore starts again, or gets its
postcommand; - a volume restore cut before its copy began leaves the volumes as they were; one cut during the copy is copied again from the staging volumes before the service starts, so the service never runs on a volume half-restored;
- a backup cut short leaves no snapshot;
backup listandbackup restoreonly see complete ones.
If the copy of a volume restore cannot finish (a full disk), the service stays stopped rather than start on a part of the snapshot, and the watchdog tries again: free the space, or run backup restore again.
Retention and pruning
After each backup, the service's keep is applied: restic forgets the snapshots it does not keep. Once a day, it reclaims their space.
backup prune does it now, with each service's retention or the one you give:
dockflow backup prune production # each service's keep
dockflow backup prune production db --keep-last 3 # db keeps its 3 most recent snapshotsOptions: --keep-last, --keep-hourly, --keep-daily, --keep-weekly, --keep-monthly.
The server's state
/var/lib/dockflow holds the server's state: every release record (the history that rollback and releases read), the uploads, the routes and the server's settings. --server backs it up to the environment's repository:
dockflow backup run production --server # /var/lib/dockflow, to production's repository
dockflow backup list production --server
dockflow backup prune production --server --keep-last 7- The snapshots are tagged with the server's name, separately from the services' snapshots.
- The default retention applies.
- The passwords in
/var/lib/dockflow/backups/are left out: a snapshot that holds its own repository's password protects nothing.
These backups exist to rebuild a lost server, so send them off-site.
To rebuild, run dockflow server init on the new server, then put the state back with restic:
restic -r <repository> restore latest --tag server=<name> --target /The next dockflow deploy of each app then runs on its history.
Without Dockflow
The repository is a standard restic repository. With its password, restic reads it on its own:
restic -r <repository> snapshotslists the snapshots;restic restore <id> --target <dir>restores one;restic dump <id> /db.pgdump > db.pgdumpextracts a dump.
The repository on the server is in the dockflow-backups volume.
Several apps on one server
Run several apps on one server, let them talk through shared networks, mount remote storage with Docker volume plugins, and keep one app from starving the others.
AI agents (MCP)
Let an AI assistant read an app's status, releases and logs, plan a deploy, and, when allowed, deploy the reviewed plan or roll back, through dockflow mcp.