Backup & Restore #

In the container world, data is the most critical asset. Docker containers are ephemeral — easy to create and destroy — but data must never disappear with them. Every lost database row, every lost uploaded file, every lost log entry can mean financial losses, lost customer trust, or even regulatory sanctions.

Unfortunately, many engineers (and even engineering teams) don’t have a proper backup strategy for their Docker data. They back up images, or back up containers, or don’t back up at all. All of these approaches are flawed.

This article covers the correct backup & restore strategy for Docker data: what to back up, the right tooling, how to automate, and (often forgotten) how to test that restore actually works. After reading this, you’ll have a solid foundation for protecting your Docker data from every eventuality.

The Right Mindset: Back Up Data, Not Containers #

Before diving into the technical details, there’s one mindset you must hold:

Containers = disposable. Data = precious. Backup = data, not containers.

This sounds simple, but many engineers still get it wrong. They think: “I’ll back up the container; if it breaks, I’ll just restore the container.” That isn’t a backup — it’s a runtime snapshot that:

  • Contains runtime data that may be inconsistent.
  • Contains configuration that should be in version control.
  • Contains secrets that must never be committed.
  • Is large, slow, and not portable.
  • Can’t be selectively restored (one database table, one upload folder).
flowchart TB
    APP[App Container<br/>stateless] -->|" data "/| DATA[(Data Layer<br/>volume/database)]
    DATA -->|" backup target "| BACKUP[Backup File<br/>.tar / .sql]
    
    APP -.->|" ✗ DON'T back up "| CONTAINER[Container Snapshot]
    CONTAINER -.->|" ✗ Inconsistent "| PROBLEM[Corrupt data]
    
    style BACKUP fill:#51cf66,color:#fff
    style CONTAINER fill:#ff6b6b,color:#fff
    style PROBLEM fill:#ff6b6b,color:#fff

The right way: back up data (volumes, database dumps, files), not containers. Containers can be recreated from images; data must be backed up explicitly.

Critical anti-pattern: Never back up Docker containers with docker commit or docker save. A committed image contains: runtime data (possibly inconsistent), secrets, configuration that should be in Git, and noise that complicates audits. Back up data, not containers.

What Should Be Backed Up in Docker? #

Not everything needs backing up. Backups aimed at the wrong targets waste storage and time. Backups that miss their targets mean data loss. Understand the following classification:

ItemMust Back Up?Reason
Dockerfile❌ NoAlready in version control (Git)
docker-compose.yml❌ NoAlready in version control
Docker images❌ NoRebuildable from Dockerfiles, or pullable from a registry
Container snapshots (commit)❌ NoAnti-pattern, contains runtime data
Docker Volumes✅ YesPersistent data not found anywhere else
Database dumps✅ YesLogical backup, safer than volume files
User file uploads✅ YesUser assets, can’t be recreated
Configuration (.env, secrets)✅ YesSource of truth, but must be encrypted
Application logs⚠️ Depends on SLAUsually aggregated to a central log system
TLS certificates✅ YesMust not be lost, hard to replace quickly
flowchart TB
    A[Item in Docker Environment] --> B[In Version Control]
    A --> C[In Registry Images]
    A --> D[MUST be backed up]
    
    B --> B1[Dockerfile]
    B --> B2[docker-compose.yml]
    B --> B3[Non-secret configuration]
    
    C --> C1[Docker images]
    
    D --> D1[Data volumes]
    D --> D2[Database dumps]
    D --> D3[File uploads]
    D --> D4[Secrets and certificates]
    D --> D5[Important logs]
    
    style D fill:#ffd43b
    style D1 fill:#ffd43b
    style D2 fill:#ffd43b
    style D3 fill:#ffd43b
    style D4 fill:#ffd43b
    style D5 fill:#ffd43b
The 3-2-1 backup principle: Keep 3 copies of your data, on 2 different media types, 1 in an off-site location. For Docker data: 1 copy in a volume (active location), 1 copy on different local storage (NFS, second disk), 1 copy in the cloud (S3, GCS, Azure Blob).

Backing Up Docker Volumes #

Volumes are host directories. Backing up a volume = backing up that directory’s contents. But there are more elegant patterns than manual cp -r.

Listing and Inspecting Volumes #

# List all volumes
docker volume ls

# Filter dangling (unused) volumes
docker volume ls -f dangling=true

# Inspect volume details
docker volume inspect mysql-data

The inspect output shows the Mountpoint on the host:

[
  {
    "Name": "mysql-data",
    "Driver": "local",
    "Mountpoint": "/var/lib/docker/volumes/mysql-data/_data",
    "CreatedAt": "2026-06-06T12:00:00Z"
  }
]

Backing Up with a Helper Container #

The most common pattern: run an ephemeral container that mounts the volume, archives its contents, and saves to the host.

# Back up a volume to a tar.gz file
docker run --rm \
  -v mysql-data:/source:ro \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/mysql-data-$(date +%F).tar.gz -C /source .

Explanation:

  • --rm — the container is deleted after finishing (ephemeral).
  • -v mysql-data:/source:ro — mount the volume read-only for backup.
  • -v $(pwd):/backup — mount the working directory for the output file.
  • alpine — a small image (5 MB) as the helper.
  • tar czf ... -C /source . — compress the entire volume contents.

Result: a mysql-data-2026-06-06.tar.gz file in the host working directory.

Restoring from a Backup #

# 1. Create a new volume (if it doesn't exist)
docker volume create mysql-data

# 2. Extract the backup into the volume
docker run --rm \
  -v mysql-data:/target \
  -v $(pwd):/backup \
  alpine \
  tar xzf /backup/mysql-data-2026-06-06.tar.gz -C /target
flowchart LR
    BACKUP[backup.tar.gz<br/>on the host] -->|tar xzf| CONT[Helper Container]
    CONT -->|extract to /target| VOL[Volume<br/>mysql-data]
    
    style BACKUP fill:#a8d8a8
    style VOL fill:#ffd8a8

Backing Up Multiple Volumes at Once #

For setups with many volumes:

#!/bin/bash
# backup-all-volumes.sh
BACKUP_DIR=/backup/volumes
DATE=$(date +%F)

mkdir -p $BACKUP_DIR/$DATE

for vol in $(docker volume ls -q); do
  echo "Backing up volume: $vol"
  docker run --rm \
    -v $vol:/source:ro \
    -v $BACKUP_DIR/$DATE:/backup \
    alpine \
    tar czf /backup/$vol-$DATE.tar.gz -C /source .
done

# Clean up old backups (keep only 30 days)
find $BACKUP_DIR -type d -mtime +30 -exec rm -rf {} \;

Save it as a script, run via cron. Automatic backup, 30-day retention.


Backing Up Databases with Logical Dumps #

For databases, backing up only the volume files is often not enough. Volume files contain database data that may be inconsistent when copied (if the database is running). Safer to use logical backups (SQL dumps).

MySQL / MariaDB #

# Backup
docker exec mysql-container \
  mysqldump -u root -psecret --all-databases > /backup/mysql-$(date +%F).sql

# Restore
docker exec -i mysql-container \
  mysql -u root -psecret < /backup/mysql-2026-06-06.sql

Or back up only a specific database:

docker exec mysql-container \
  mysqldump -u root -psecret mydb > /backup/mydb-$(date +%F).sql

PostgreSQL #

# Backup
docker exec postgres-container \
  pg_dump -U postgres mydb > /backup/mydb-$(date +%F).sql

# Restore
docker exec -i postgres-container \
  psql -U postgres mydb < /backup/mydb-2026-06-06.sql

Or compress directly:

docker exec postgres-container \
  pg_dump -U postgres -Fc mydb > /backup/mydb-$(date +%F).dump

The -Fc (custom) format is smaller and supports selective restore.

MongoDB #

# Backup
docker exec mongo-container \
  mongodump --archive=/tmp/backup.archive

docker cp mongo-container:/tmp/backup.archive /backup/

# Restore
docker cp /backup/backup.archive mongo-container:/tmp/

docker exec mongo-container \
  mongorestore --archive=/tmp/backup.archive

SQLite #

# Backup
docker cp app-container:/app/data.db /backup/data-$(date +%F).db

# Restore
docker cp /backup/data-2026-06-06.db app-container:/app/data.db

SQLite is a single file — backup = copy the file. Make sure there are no active transactions (sqlite3 .backup is safer than cp).

Logical backup (dump) advantages: consistent at the transaction level (the database is snapshotted at one point in time), portable to different database versions, selectively restorable, and independent of the database’s internal filesystem.

Backing Up with Docker Compose #

In Compose, backups are usually done with a dedicated service run on demand or on a schedule.

A Backup Service #

version: "3.9"
services:
  app:
    image: my-app:1.2
    volumes:
      - app-data:/app/data

  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data

  backup:
    image: alpine
    profiles: ["backup"]    # only runs with --profile backup
    volumes:
      - pgdata:/source:ro
      - app-data:/source2:ro
      - ./backups:/backup
    command: >
      sh -c "
        tar czf /backup/pgdata-$$(date +%F).tar.gz -C /source . &&
        tar czf /backup/app-data-$$(date +%F).tar.gz -C /source2 . &&
        echo 'Backup complete'
      "      

volumes:
  app-data:
  pgdata:
# Run a manual backup
docker compose --profile backup run --rm backup

The advantages of this pattern:

  • Declarative — the backup config lives in the compose file.
  • Profiles — the backup service doesn’t auto-run on compose up, only when needed.
  • Ephemeral--rm ensures the backup container is removed after finishing.

Scheduled Backups with Cron #

# /etc/cron.d/docker-backup
# Daily backup at 2 AM
0 2 * * * root cd /opt/myapp && /usr/local/bin/docker compose --profile backup run --rm backup >> /var/log/docker-backup.log 2>&1
# Weekly cleanup of old backups (>30 days)
0 4 * * 0 root find /opt/myapp/backups -type f -mtime +30 -delete

A host cron job runs the backup service via Compose, logging to a file for auditing.


Backing Up to Cloud Storage #

A backup on the same host as the data = a single point of failure. Backups must be copied to another independent location. Cloud storage (S3, GCS, Azure Blob) is the most common choice.

Backing Up to AWS S3 #

#!/bin/bash
# backup-to-s3.sh
DATE=$(date +%F)
BACKUP_FILE="db-backup-$DATE.sql"
S3_BUCKET="s3://my-app-backups"

# 1. Dump the database
docker exec postgres-container \
  pg_dump -U postgres mydb > /tmp/$BACKUP_FILE

# 2. Compress
gzip /tmp/$BACKUP_FILE

# 3. Upload to S3
aws s3 cp /tmp/$BACKUP_FILE.gz $S3_BUCKET/$BACKUP_FILE.gz

# 4. Clean up local
rm /tmp/$BACKUP_FILE.gz

# 5. Log
echo "Backup uploaded: $S3_BUCKET/$BACKUP_FILE.gz" >> /var/log/backup.log
# Cron: run daily
0 3 * * * root /opt/scripts/backup-to-s3.sh

Backing Up to S3 Directly from a Container #

Without copying to the host first:

docker run --rm \
  -v pgdata:/source:ro \
  -v ~/.aws:/root/.aws:ro \
  amazon/aws-cli:latest \
  sh -c "tar czf - -C /source . | aws s3 cp - s3://my-backups/pgdata-$(date +%F).tar.gz"

The amazon/aws-cli container ships the AWS CLI. Mount the volume (read-only) and AWS credentials, stream directly to S3.

Backing Up to Google Cloud Storage #

docker run --rm \
  -v pgdata:/source:ro \
  -v /path/to/gcp-key.json:/key.json:ro \
  google/cloud-sdk:latest \
  sh -c "tar czf - -C /source . | gsutil cp - gs://my-backups/pgdata-$(date +%F).tar.gz"

Backing Up to Azure Blob #

docker run --rm \
  -v pgdata:/source:ro \
  -v ~/.azure:/root/.azure:ro \
  mcr.microsoft.com/azure-cli:latest \
  sh -c "tar czf - -C /source . | az storage blob upload -c mybackup -n pgdata-$(date +%F).tar.gz --account-name mystorageaccount --stdin"
Important detail: Mount credentials (~/.aws, ~/.azure) read-only and make sure their permissions are right. Never bake credentials into images or commit them to Git.

Restore — the Most Often Forgotten Part #

A backup that’s never restored = a backup that doesn’t exist. Many teams back up routinely but never actually perform a restore. When disaster strikes, they’re shocked because:

  • The backup file is corrupt.
  • The backup format is incompatible with the new database version.
  • The restore procedure was never tested, and takes hours when finally attempted.
  • Restore credentials or dependencies have expired.

Restores must be tested regularly. This isn’t optional.

A Test Restore Procedure #

TEST RESTORE PROCEDURE (RUN MONTHLY):

1. Pick the latest backup at random
2. Spin up a separate environment (not production!)
   - docker compose -f docker-compose.test-restore.yml up -d
3. Restore the backup into this environment
   - Tar extract into the volume
   - Or psql/mysql restore into the database container
4. Verify data:
   - Count rows in critical tables (users, orders, products)
   - Compare with production row counts
   - Check sample data for consistency
5. Smoke test the application:
   - Log in as a user
   - Fetch sample data
   - Run critical queries
6. Record results in a log:
   - Restore date
   - Backup size
   - Restore time
   - Row counts before/after
   - Issues found
7. Clean up:
   - docker compose -f docker-compose.test-restore.yml down -v

A Restore Runbook #

Every backup should have a runbook explaining the restore steps step-by-step:

# Runbook: Restore PostgreSQL from a Backup

## Prerequisites
- SSH access to the production server
- Backup file in S3: s3://my-backups/postgres/
- Database credentials (check the secret manager)

## Restore Steps

### 1. Stop the Application
\`\`\`bash
docker compose stop app
\`\`\`
The app is stopped to prevent writes to the database during restore.

### 2. Identify the Backup
\`\`\`bash
# View the latest backups
aws s3 ls s3://my-backups/postgres/ | sort | tail -5
\`\`\`

### 3. Download the Backup
\`\`\`bash
aws s3 cp s3://my-backups/postgres/postgres-2026-06-06.sql.gz /tmp/
gunzip /tmp/postgres-2026-06-06.sql.gz
\`\`\`

### 4. Restore to the Database
\`\`\`bash
docker exec -i postgres-container \
  psql -U postgres mydb < /tmp/postgres-2026-06-06.sql
\`\`\`

### 5. Start the Application
\`\`\`bash
docker compose start app
\`\`\`

### 6. Smoke Test
- Log in to the application
- Fetch sample data
- Check logs for errors

## Time Estimates
- Backup download: 5-15 minutes (depending on size)
- Restore: 10-60 minutes (depending on database size)
- Total: 30-90 minutes

## Contacts
- On-call DBA: ...
- On-call DevOps: ...

A runbook must:

  • Be recorded in a repo or wiki.
  • Be reviewed and updated every quarter.
  • Be tested by an engineer different from the one who wrote it.
  • Be known to every on-call team member.

Backup Automation #

Manual backups = missed backups. Backups must be automatic, with monitoring so you know when one fails.

Pattern 1: Cron on the Host #

# /etc/cron.d/docker-backup
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Daily volume backup at 2 AM
0 2 * * * root /opt/scripts/backup-volumes.sh >> /var/log/backup.log 2>&1

# Database backup at 3 AM
0 3 * * * root /opt/scripts/backup-database.sh >> /var/log/backup.log 2>&1

# Upload to S3 at 4 AM
0 4 * * * root /opt/scripts/backup-to-s3.sh >> /var/log/backup.log 2>&1

Pattern 2: A Cron Container #

services:
  cron:
    image: alpine
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro   # to control Docker
      - pgdata:/source:ro
      - ./backups:/backup
      - ./scripts:/scripts:ro
    command: >
      sh -c "
        echo '0 2 * * * /scripts/backup.sh' > /etc/crontabs/root &&
        crond -f -L /dev/stdout
      "      

A container running cron inside Docker. More portable, but more complex (must handle signals, logs, monitoring).

Pattern 3: A Centralized Backup Service #

For environments with many applications, use a centralized backup service (like Velero for Kubernetes, or a custom orchestrator for Docker). This service:

  • Schedules backups for all applications.
  • Handles retention.
  • Monitors backup success.
  • Alerts the team on failure.

Backup Monitoring #

A failed backup must alert, not fail silently. Tools:

# Script with Slack notification
#!/bin/bash
BACKUP_RESULT=$(/opt/scripts/backup-database.sh 2>&1)

if [ $? -ne 0 ]; then
  curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
    -H 'Content-type: application/json' \
    -d "{\"text\": \"❌ Backup FAILED on $(hostname)\n$BACKUP_RESULT\"}"
else
  curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
    -H 'Content-type: application/json' \
    -d "{\"text\": \"✅ Backup successful on $(hostname)\"}"
fi
flowchart LR
    A[Cron Job] --> B[Backup Script]
    B --> C{Backup<br/>successful?}
    C -- Yes --> D[Log success]
    C -- No --> E[Alert Slack/PagerDuty]
    
    style D fill:#51cf66,color:#fff
    style E fill:#ff6b6b,color:#fff

Backup Retention and Rotation #

Backups without retention = a full disk. Backups without rotation = the oldest restorable backup is the newest one, even though the oldest is the most relevant during audits or forensics.

Retention Patterns #

PatternDescriptionUse Case
Daily 7, weekly 4, monthly 12The last 7 daily backups, 4 weekly, 12 monthlyCommon, meets most compliance
GFS (Grandfather-Father-Son)Daily, weekly, monthly, yearlyStrict compliance (finance regulations)
Custom per SLA30/60/90/365-day retentionDepends on SLA and compliance
Immutable backupsWORM storage, undeletableAnti-ransomware

Example: 30-day retention with automatic cleanup:

#!/bin/bash
# retention-30-days.sh
BACKUP_DIR=/backup/volumes

# Delete volume backups older than 30 days
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete

# Delete database backups older than 90 days
find /backup/db -name "*.sql.gz" -mtime +90 -delete
The 3-2-1 rule with retention: 3 copies of data, on 2 media types, 1 off-site, with compliance-satisfying retention. Example: 1 copy on a local volume, 1 copy on a local NAS (weekly), 1 copy in S3 (90-day retention), 1 copy in S3 Glacier (7-year retention for compliance).

Encrypting Backups #

Backups contain sensitive data. Backing up to cloud storage without encryption = data exposure if the S3 bucket is misconfigured.

Encrypting at Backup Time #

# Backup + encrypt with GPG
docker exec postgres-container \
  pg_dump -U postgres mydb | \
  gpg --symmetric --cipher-algo AES256 --output /backup/db-$(date +%F).sql.gpg

Restore:

gpg --decrypt /backup/db-2026-06-06.sql.gpg | \
  docker exec -i postgres-container psql -U postgres mydb

Encryption in Cloud Storage #

S3, GCS, and Azure Blob support server-side encryption:

# S3: server-side encryption with KMS
aws s3 cp /backup/db.sql.gz s3://my-backups/db.sql.gz \
  --server-side-encryption aws:kms \
  --ssekms-key-id arn:aws:kms:...

# GCS: server-side encryption (enabled by default)
gsutil cp /backup/db.sql.gz gs://my-backups/

# Azure: server-side encryption (enabled by default)
az storage blob upload -f /backup/db.sql.gz -c mybackup -n db.sql.gz

For extra security, use client-side encryption (GPG, age, sops) before uploading.


Backup Anti-Patterns #

1. Backing Up Docker Images #

# ANTI-PATTERN
docker commit running-container backup:v1
docker save backup:v1 > backup.tar

Images contain inconsistent runtime data, are bloated, and unsafe. Back up data, not images.

2. Backing Up Containers (docker export) #

# ANTI-PATTERN
docker export container-name > backup.tar

A container snapshot — the container’s filesystem at one point in time. Contains noise (temporary files, caches), is inconsistent for databases, and can’t be selectively restored.

3. Backing Up to the Same Location #

# ANTI-PATTERN
# Backup on the same disk as the volume
docker run -v pgdata:/source -v /backup:/target alpine tar czf /target/db.tar.gz -C /source .
# /backup is on the same disk as /var/lib/docker/volumes
# Disk failure = data AND backup lost

Backups must go to a different disk, or to the cloud.

4. Never Testing Restores #

# ANTI-PATTERN
# "The backup succeeded, and I restored it last year, it must still work"

Restores must be tested regularly. Software upgrades, changing backup formats, lost dependencies, etc.

5. Backups Without Monitoring #

# ANTI-PATTERN
# "The cron runs every day, the backup must be succeeding"

Backup scripts can fail due to a full disk, wrong permissions, network outages, or command errors. Without monitoring, you won’t know until a disaster happens.

6. A Single Account for Backups #

# ANTI-PATTERN
# Backup uses the same access key as production
# If the account is compromised, production AND backups are lost

Use a separate IAM account for backups. Limit permissions to write-only on the backup bucket.

7. Undocumented Procedures #

# ANTI-PATTERN
# "Wait, let me find the command first..."

During a disaster, you need a restore in minutes, not hours of exploring scripts. A runbook must be ready and tested.


Backups for Specific Use Cases #

WordPress / CMS #

# Back up files + database
docker exec wordpress-db mysqldump -u root -p$DB_PASSWORD wordpress > /backup/wp-db.sql
docker run --rm -v wp-uploads:/source:ro -v /backup:/target alpine \
  tar czf /target/wp-uploads.tar.gz -C /source .

Elasticsearch #

# Snapshot to a shared filesystem
curl -X PUT "localhost:9200/_snapshot/my_backup/snap_$(date +%F)" \
  -H 'Content-Type: application/json' -d '{}'

Elasticsearch has a native snapshot API. More reliable than copying data files.

Redis #

# Trigger a save and copy the dump file
docker exec redis-container redis-cli BGSAVE
docker cp redis-container:/data/dump.rdb /backup/redis-$(date +%F).rdb

Or enable AOF (Append Only File) in redis.conf for durability.

MinIO / Object Storage #

MinIO has an mc client for mirroring to another bucket. Back up object storage by mirroring to another S3/GCS bucket.


Disaster Recovery — More Than Just Backups #

Backup is one component of disaster recovery, but not the only one. Comprehensive DR includes:

flowchart TB
    A[Disaster Recovery] --> B[Backup]
    A --> C[Replication]
    A --> D[Monitoring]
    A --> E[Runbook]
    A --> F[Testing]
    
    B --> B1[Routine off-site backups]
    C --> C1[Multi-region/AZ]
    D --> D1[Alerts + healthchecks]
    E --> E1[Restore procedures]
    F --> F1[Regular restore tests]
    
    style A fill:#ffd43b
  • Backup — copy data to another location.
  • Replication — live data in several places (multi-AZ, multi-region).
  • Monitoring — alert if backup or replication fails.
  • Runbook — documented restore procedures.
  • Testing — regular verification that restore actually works.

For most applications, backup + monitoring + testing is enough. For mission-critical applications, add replication and automated failover.


Tools and Utilities #

For Backup #

ToolFunction
tarArchive files/directories
pg_dump, mysqldumpDatabase logical backups
mongodumpMongoDB backups
redis-cli BGSAVERedis snapshots
aws s3 cpUpload to S3
gsutil cpUpload to GCS
az storage blob uploadUpload to Azure Blob
resticEncrypted, deduplicated backups
borgbackupDeduplicated, compressed backups
duplicityEncrypted, bandwidth-efficient backups

For Monitoring #

ToolFunction
Cron + log + alert scriptSimple monitoring
CronitorExternal cron job monitoring
Healthchecks.ioPing-based monitoring
Prometheus + AlertmanagerCentralized monitoring
Datadog, New RelicSaaS monitoring

For Restore Testing #

ApproachFunction
Separate test environmentRestore to a separate env, verify
Automated restore + verifyRestore script + row count checks
Chaos engineeringTest failure scenarios regularly

Backup & Restore Checklist #

Use this checklist to make sure you have solid backups:

BACKUP STRATEGY:
  □ Data is backed up, not containers/images
  □ Daily backups for active data
  □ Weekly/monthly backups for long-term retention
  □ Backups to off-site (cloud / remote location)
  □ Backups encrypted (in transit + at rest)
  □ Retention meets compliance (30/90/365 days)

AUTOMATION:
  □ Backups run via cron / scheduler
  □ Backups monitored (alert on failure)
  □ Backup logs archived for audit
  □ Automatic cleanup of old backups

RESTORE:
  □ Restore procedure documented in a runbook
  □ Restore test run monthly
  □ Restore time (RTO) measured
  □ Restore point (RPO) measured and met

SECURITY:
  □ Backup files encrypted
  □ Backup access keys separate from production
  □ Backup buckets not public
  □ Retention policy for backup access

DRILL:
  □ A different engineer can perform a restore
  □ Runbook reviewed quarterly
  □ On-call team knows the restore procedure
  □ Backups insured (for critical data)

Summary #

  • Back up data, not containers. Containers = disposable, data = precious. Backing up images or containers is an anti-pattern — they contain inconsistent data, secrets, and noise.
  • What must be backed up: volumes (persistent data), database dumps (logical backups), user file uploads, secrets and TLS certificates, and critical configuration. What doesn’t need it: Dockerfiles, compose files (already in Git), images (rebuildable).
  • Backup tools: tar for volume archives, pg_dump/mysqldump for database logical backups, mongodump for MongoDB. Always mount volumes read-only during backup.
  • Docker Compose backup services: use profiles: ["backup"] so the backup service only runs when needed. Run with docker compose --profile backup run --rm backup.
  • Cloud backups: upload backups to S3, GCS, or Azure Blob for durability. Use a separate IAM account with limited permissions. Encrypt with KMS or client-side.
  • The 3-2-1 principle: 3 copies of data, 2 media types, 1 off-site. With compliance-satisfying retention (30/90/365 days).
  • Restores must be tested regularly. A backup never restored = a backup that doesn’t exist. Test restore monthly to a separate environment, verify row counts, and smoke test the application.
  • A restore runbook must be documented, reviewed quarterly, and known to every on-call team member. Restore time (RTO) and restore point (RPO) must be measured.
  • Backup monitoring: alert to Slack/PagerDuty if a backup fails. Silent failure = data loss without realizing it. A cron job without monitoring is a time bomb.
  • Anti-patterns to avoid: backing up images, backing up to the same location, never testing restores, a single IAM account, no monitoring, no documentation.
  • Useful tools: restic, borgbackup, duplicity for encrypted backups. Cronitor, Healthchecks.io for cron monitoring. A separate test environment for restore verification.
  • Remember: Containers can be recreated; data cannot. Backup is the last safety net — invest enough time and storage.

← Previous: Volume vs Bind Mount   Next: Sharing Data Between Containers →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact