✦ For everyone, free.

Practical knowledge for real and everyday life

Home

14.3.2.5 Compose Backup Service

A focused guide to Compose Backup Service, connecting core concepts with practical Docker and container operations.

A Compose backup service is a dedicated, scheduled container defined within the same stack as the data it protects, responsible for producing backups of volumes or databases and shipping them off the host, integrated into the stack's own deployment so that backup behavior travels with the application rather than being maintained as a separate, easily forgotten script on the host.

Defining a periodic backup container

Because Compose itself does not include a native scheduler, a backup service typically runs a long-lived container that sleeps and triggers the backup logic on an interval internally, rather than relying on the host's own cron:

services:
  backup:
    image: alpine:3
    volumes:
      - pgdata:/data:ro
      - ./backups:/backups
    entrypoint: >
      sh -c "while true; do
        tar czf /backups/pgdata-$(date +%Y%m%d-%H%M).tar.gz -C /data .;
        sleep 86400;
      done"
    networks:
      - internal

volumes:
  pgdata:
networks:
  internal:

Keeping the backup logic inside the stack itself, rather than as a host-level cron job calling docker exec from outside, means the backup behavior is captured in version control alongside everything else the stack defines, and travels automatically to any environment the stack is deployed to.

Backing up a database with its native tools

For databases, a backup service typically uses the database's own export tooling rather than a raw filesystem copy, since application-aware backups are generally more reliable and easier to restore selectively than a raw volume archive:

services:
  db-backup:
    image: postgres:16
    networks:
      - internal
    volumes:
      - ./backups:/backups
    secrets:
      - db_password
    entrypoint: >
      sh -c "while true; do
        PGPASSWORD=$$(cat /run/secrets/db_password) pg_dump -h db -U app_user app > /backups/app-$(date +%Y%m%d).sql;
        sleep 86400;
      done"

secrets:
  db_password:
    file: ./secrets/db_password.txt

Using the same Compose network and secrets the rest of the stack already defines keeps the backup service's credential handling consistent with every other service, rather than requiring a separately managed credential path.

Shipping backups off the host as part of the same service

A backup that remains only on the same host as the data it protects does not guard against the host itself failing; the backup service should also copy the resulting archive to a separate location as part of its own routine:

services:
  backup:
    image: amazon/aws-cli
    volumes:
      - ./backups:/backups:ro
    environment:
      - AWS_ACCESS_KEY_ID_FILE=/run/secrets/aws_key
    entrypoint: >
      sh -c "while true; do
        aws s3 sync /backups s3://my-backup-bucket/docker-volumes/;
        sleep 3600;
      done"

Running the off-host shipping step as its own service, separate from the service producing the backup, keeps each container's responsibility narrow and makes it easier to reason about and test each step independently.

Using the host's scheduler instead of a long-running loop

An alternative to a perpetually running, sleep-looping container is invoking the backup as a one-off Compose service from the host's own cron or systemd timer, which avoids keeping an idle container running between backup runs:

0 2 * * * docker compose -f /srv/myapp/docker-compose.yml run --rm backup
services:
  backup:
    image: alpine:3
    volumes:
      - pgdata:/data:ro
      - ./backups:/backups
    entrypoint: ["sh", "-c", "tar czf /backups/pgdata-$(date +%Y%m%d).tar.gz -C /data ."]
    profiles:
      - backup

Using a Compose profile to keep the backup service out of the default up invocation, and only running it explicitly through run --rm, keeps the regular stack lifecycle (up, down, restart) unaffected by the backup service's own separate schedule.

Retention handled within the same service definition

A backup service should also be responsible for its own retention policy, removing old backups on the same schedule rather than letting the backup directory grow indefinitely:

find /backups -name "pgdata-*.tar.gz" -mtime +30 -delete
entrypoint: >
  sh -c "tar czf /backups/pgdata-$(date +%Y%m%d).tar.gz -C /data . &&
  find /backups -name 'pgdata-*.tar.gz' -mtime +30 -delete"

Bundling retention cleanup into the same invocation as the backup itself ensures the two stay consistent, rather than risking a separate cleanup script falling out of sync with the backup naming convention or schedule.

Monitoring the backup service's own health

A backup service that fails silently is functionally equivalent to having no backup service at all until the moment a restore is actually attempted. Alerting on failure should be part of the service's own definition or the script it runs:

if ! tar czf /backups/pgdata-$(date +%Y%m%d).tar.gz -C /data .; then
  curl -X POST https://alerts.example.com/webhook -d "Backup failed for pgdata"
fi
docker compose logs backup | tail -50

Verifying backups produced by the service

The backup service's output should periodically be tested through an actual restore, ideally as its own automated companion service or scheduled job, rather than trusted purely because the backup job itself exits without error:

services:
  restore-test:
    image: alpine:3
    volumes:
      - pgdata-restore-test:/data
      - ./backups:/backups:ro
    entrypoint: ["sh", "-c", "tar xzf /backups/pgdata-latest.tar.gz -C /data"]
    profiles:
      - restore-test

volumes:
  pgdata-restore-test:

Common mistakes

  • Running a backup script entirely outside of the Compose stack, leaving it undocumented in version control and unlikely to be replicated correctly in a new environment.
  • Storing backups only within the same Compose-managed volume or host the data came from, leaving no protection against a full host failure.
  • Letting a sleep-looping backup container run perpetually without any restart or failure alerting, masking a silently broken backup process for an extended period.
  • Allowing the backup directory to grow unbounded with no retention policy, eventually exhausting host disk space.
  • Never running an actual restore from the service's output, leaving its real usability unverified.

A Compose backup service earns its place in the stack the same way any other service does: version-controlled, using the same network and secrets conventions as the rest of the stack, shipping its output off the host, enforcing its own retention, and monitored closely enough that a failure is noticed immediately rather than discovered during an actual recovery attempt.