Docker Compose backup strategy: volumes, databases, and restore order
Build a Docker Compose backup strategy around named volumes, bind mounts, database-consistent exports, off-host storage, and a tested restore order.

Start with a recovery inventory, not a Compose file
A Compose project can recreate containers, networks, and much of the deployment shape. It cannot recreate the PostgreSQL rows, customer uploads, TLS material, generated application keys, or external object-store objects that made the service useful. Treat compose.yaml as a recovery map, not as the backup itself. The fastest safe path is to inventory every persistent dependency, assign a recovery method to each one, and test that the pieces restore into one working application.
Review every service mount and every connection to state outside the project. Classify each item as rebuildable, recoverable from another system, or unique production data. A pinned image that can be built from source is usually rebuildable. A cache may be disposable if the application can refill it. A database, uploaded files, locally generated keys, queue messages that represent accepted work, and host-mounted configuration usually are not. This exercise also exposes services that write important files into their writable container layer; those files can disappear when the container is replaced because they were never attached to durable storage.
For a practical baseline, keep an inventory with one row per recovery target: service, source location, data owner, backup method, schedule, retention, restore destination, required credentials, and validation check. The broader [Linux server backup scope guide](/blog/what-to-back-up-linux-server) can help identify host-level configuration and recovery information that sits outside Compose.
- Rebuildable: Compose definitions, Dockerfiles, pinned image references, and documented build inputs.
- Persistent file state: named volumes, bind-mounted uploads, generated assets, certificates, and application configuration.
- Database state: database-aware logical dumps, physical backups, or a coordinated storage snapshot—not an arbitrary copy of live database files.
- External state: object storage, managed databases, email providers, DNS, secret managers, and third-party queues, each with its own recovery route.
Named volumes and bind mounts need different recovery records
Docker-managed volumes persist independently of the containers that use them. Docker deliberately abstracts their host location, so do not make a production backup plan depend on a guessed path beneath Docker’s internal data directory. Instead, record the resolved volume name and inspect it through Docker. Compose-generated names can change with the project name, while external: true volumes may be owned by another project or team. Docker’s [volume documentation](https://docs.docker.com/engine/storage/volumes/) explains this lifecycle distinction.
A bind mount exposes an explicit host file or directory to a container. It is therefore a normal host backup target, but recovery must preserve the path, ownership, permissions, and any platform-specific security labeling expected by the container. Backing up the Compose repository does not protect a bind-mounted /srv/app/uploads directory elsewhere on the host. Likewise, restoring its bytes with the wrong ownership can leave the service unable to read them. Docker’s [bind mount documentation](https://docs.docker.com/engine/storage/bind-mounts/) covers the operational differences.
Anonymous volumes deserve special attention. They are easy to create accidentally and hard to associate with a service after container replacement. Prefer explicit named volumes for durable application state, and keep a short manifest that maps each service mount to its source, recovery method, and validation check. After deployments, compare the resolved mounts with that manifest; a green backup can still miss data if a deployment moved writes to a newly introduced volume or bind mount. See [how to detect Docker volume and mount backup coverage drift](/blog/detect-docker-volume-mount-backup-coverage-drift) for that review process.
- Inspect the effective project before relying on it:
docker compose configrenders Compose variables and merged configuration. - List durable Docker volumes with
docker volume ls, then inspect a known volume withdocker volume inspect VOLUME_NAME. - List each running container’s actual mounts with
docker inspect CONTAINER_NAME --format '{{json .Mounts}}'and compare them with the intended inventory.
Use database-aware backups for live database state
A stopped ordinary file tree can often be copied safely. The active data directory of PostgreSQL, MySQL, MariaDB, MongoDB, or Redis follows database-specific consistency rules: multiple files can change as one logical transaction. Mounting that directory into a helper container and archiving it does not make the result transactionally consistent. For a running database, use a logical dump, a database-aware physical backup, or a storage snapshot coordinated according to the engine’s documented procedure.
Logical exports are often the practical default for a small application database because they are portable and straightforward to inspect in an isolated restore. They can be slower to restore and may not include cluster-wide identities, roles, or configuration unless you export those separately. Physical backups can be a better fit for larger databases or specific recovery requirements, but they have engine-version, preparation, and restore constraints. PostgreSQL documents these trade-offs in its [backup and restore documentation](https://www.postgresql.org/docs/current/backup.html).
Database backup timing is only one part of consistency. An order row may reference an invoice in object storage, and a media record may refer to a file in an uploads volume. If those targets are captured at unrelated times while writes continue, each backup can be valid on its own but still disagree after recovery. Decide whether the application can reconcile that gap. If it cannot, use a maintenance window, application quiescing, or an application-level capture process that creates a known boundary.
- PostgreSQL: use
pg_dumpfor a single application database, and account separately for globals such as roles when they matter to recovery. - MySQL or MariaDB: use an engine-appropriate logical dump or physical backup method; do not assume a live copy of
/var/lib/mysqlis a safe backup. - For any engine: restore into an isolated target, check engine output, then run application-aware queries or smoke tests before declaring the backup usable.
Make each recovery point identifiable and complete
A recovery point needs enough context to select it under pressure: Compose project, environment, source host, completion time, application release, database engine version, and the artifacts that belong together. A timestamp alone is weak evidence when several projects use similar volume names or when a database dump finishes after a filesystem archive. Mark a recovery point complete only after every required component succeeds. A missing database dump must remain visible as an incomplete set rather than looking like a routine successful file backup.
Configuration has its own recovery path. Keep Compose definitions, pinned image references, deployment notes, and migration history in version control or another protected system. Keep secrets and private keys in a recoverable secret-management process with access independent of the failed Docker host. Do not solve secret recovery by placing plaintext credentials beside a data archive: during an incident you need both authorized secret access and data recovery, but they do not need the same access boundary.
If you use restic, repository integrity checks add useful evidence about stored backup structure, but they do not prove an application starts correctly. Review [what restic repository health checks prove—and what they do not](/blog/restic-repository-health-integrity-checks), then pair checks with an isolated restore test.
- Record a manifest beside backup artifacts: project, host, release, timestamp, database dump filename, volume or bind-mount archive names, and any intentional gaps.
- Keep recovery credentials, storage location, repository password or recovery key, and a least-privilege storage access route available to authorized responders.
- Treat failed, skipped, or stale scheduled work as an operational event; a missing backup is different from a backup that completed with an empty or incorrect scope.
A practical backup workflow for a Compose application
The following illustrative workflow protects a typical Compose application with an application container, a PostgreSQL or MySQL database, and an uploads directory. Adapt names, paths, retention, credentials, and maintenance requirements to your service, and test first in non-production. Run commands from a controlled administrative shell on the Docker host. Do not put passwords in shell history or Compose files committed to source control; use protected environment files, a secret manager, or the database client’s supported credentials mechanism.
Prerequisites are Docker Compose access, sufficient read permission for the selected files, a writable local staging directory outside the application’s data paths, and a configured off-host backup repository. The commands create database-consistent logical artifacts before backing up the resulting artifacts and file targets. Expected success signals are a non-empty dump, a completed backup run, and a snapshot or manifest that contains the intended paths. A command returning zero does not prove that its scope was correct, so inspect the artifact and perform the restore procedure in the next section.
For teams that want schedules, run outcomes, snapshots, and restore work visible rather than distributed across invisible host jobs, [Rested Backups](https://restedbackups.com) runs encrypted restic workflows close to the connected Linux server while keeping storage ownership, recovery keys, and restore validation as explicit operator responsibilities.
- 1. Render and save the effective Compose configuration, then record resolved volumes and bind mounts.
- 2. Create a database-aware export into a dedicated staging directory.
- 3. Back up the export, declared file targets, and recovery configuration to off-host storage.
- 4. Verify artifact presence and repository health, then restore the selected recovery point into an isolated Compose project.
Copyable commands: inventory, export, back up, and verify
**Illustrative commands — adapt and test outside production first.** Replace myapp, postgres, db, /srv/myapp/uploads, /var/backups/myapp, and repository settings with values from your inventory. These examples intentionally write restores and dumps to separate paths; they do not overwrite a live database or volume. Before automating, confirm that the application’s database container and service names match docker compose config --services.
First, collect the effective deployment record. This detects variable expansion and helps prevent backing up an assumed volume name while the running project uses another one. Review the resulting files for secrets before copying them into a broader backup scope; keep sensitive configuration under the access controls your organization expects.
For PostgreSQL, this example creates a custom-format dump that pg_restore can inspect and restore selectively. The database user must have the privileges required for the intended scope. Use a protected PGPASSWORD delivery mechanism or a .pgpass file with restrictive permissions instead of placing a password in the command. The expected output is a non-empty .dump file; pg_restore --list should print its table of contents.
For MySQL or MariaDB, this example uses --single-transaction, which is commonly appropriate for InnoDB tables because it obtains a consistent read without a full write lock. It is not a universal consistency guarantee: non-transactional tables, certain administrative operations, and application-level relationships can require a maintenance window or a different tool. Use an option file or other protected credential mechanism rather than putting the password in the command. The expected output is a non-empty SQL file that begins with dump metadata and can be restored into a disposable server.
bash # Safe context: controlled shell on the Docker host; run from the Compose project directory. # Prerequisite: create a restricted staging directory outside Docker volumes. set -eu umask 077 STAMP="$(date -u +%Y%m%dT%H%M%SZ)" BACKUP_DIR="/var/backups/myapp/${STAMP}" mkdir -p "${BACKUP_DIR}" docker compose config > "${BACKUP_DIR}/compose.rendered.yaml" docker compose config --services > "${BACKUP_DIR}/compose.services.txt" docker volume ls > "${BACKUP_DIR}/docker-volumes.txt" docker compose ps > "${BACKUP_DIR}/compose-status.txt" # Expected result: the directory contains the rendered configuration and inventory records. ls -lah "${BACKUP_DIR}"bash # PostgreSQL logical export. Replace postgres and appdb with your Compose service and database. # Credentials: arrange .pgpass or other protected authentication inside the database container. docker compose exec -T postgres \ pg_dump --format=custom --no-owner --file=- appdb \ > "${BACKUP_DIR}/appdb-${STAMP}.dump" # Validation: prints the dump catalog; failure or an empty file stops the workflow. test -s "${BACKUP_DIR}/appdb-${STAMP}.dump" docker compose exec -T postgres pg_restore --list \ < "${BACKUP_DIR}/appdb-${STAMP}.dump" \ > "${BACKUP_DIR}/appdb-${STAMP}.toc"bash # MySQL/MariaDB logical export. Use this instead of the PostgreSQL command above. # Replace mysql and appdb. Provide credentials through a protected client option file. docker compose exec -T mysql \ mysqldump --single-transaction --routines --events --databases appdb \ > "${BACKUP_DIR}/appdb-${STAMP}.sql" # Validation: confirms the artifact exists and is not empty; inspect it during a test restore. test -s "${BACKUP_DIR}/appdb-${STAMP}.sql" head -n 5 "${BACKUP_DIR}/appdb-${STAMP}.sql"bash # Illustrative restic backup after RESTIC_REPOSITORY and RESTIC_PASSWORD_FILE are set securely. # Include only known recovery targets. Add a named-volume export only after coordinating writers. restic backup \ "${BACKUP_DIR}" \ /srv/myapp/uploads \ /srv/myapp/compose.yaml # Inspect snapshots and check repository metadata. A successful check is not an application restore test. restic snapshots restic check
Technical basis
First-party references
Technical claims and limitations in this guide were checked against these primary sources. Confirm version-specific behavior when designing a production recovery process.
Related from Rested
Common questions
Frequently asked questions
Does backing up compose.yaml back up my application?
No. The Compose file describes services and mounts, but it does not include the data in named volumes, bind-mounted host paths, databases, object storage, or external secret systems. Protect the definition and each non-rebuildable state dependency.
Can I back up every Docker volume in the same way?
Not safely. Ordinary files may be copied when their writer is stopped or coordinated. A live database needs a database-aware logical export, physical backup, or supported snapshot procedure. Your application also determines whether several targets must represent one coordinated recovery point.
Should I stop the whole Compose project during backup?
A maintenance stop can simplify consistency for a small service with an acceptable outage window, but it is not always required. Database-native tools can capture live systems. The important decision is how you coordinate state that must agree across databases, files, queues, and external stores.
What should be restored first in a Docker Compose stack?
Provision the replacement host and empty storage first. Restore and validate the database and referenced files before starting write-capable workers or application services. Admit public traffic only after dependency and application-level checks pass.
Are named volumes safe if containers are recreated?
They normally persist across container recreation, but they remain on the Docker host and can be deleted or lost with that host. This persistence is useful behavior, not an off-host backup or a tested recovery plan.