No description
  • Shell 89.4%
  • Just 3.7%
  • JavaScript 3.7%
  • Dockerfile 3.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Eddy Hintze 2f9f59eb94
All checks were successful
Build and Test / Lint Shell Scripts (push) Successful in 27s
Build and Test / Build Multi-Platform Image (push) Successful in 26s
Enhance service management in backup scripts by adding container ID resolution and improving cleanup logic
2026-02-15 16:40:39 -05:00
.forgejo/workflows Added GFS Retention Policy. cleanup and other improvements 2025-10-21 23:13:30 -04:00
scripts Enhance service management in backup scripts by adding container ID resolution and improving cleanup logic 2026-02-15 16:40:39 -05:00
tests Refactor backup and restore configuration to simplify BACKUP_DIRS format and improve documentation 2026-02-15 11:39:11 -05:00
.env.example Refactor backup and restore configuration to simplify BACKUP_DIRS format and improve documentation 2026-02-15 11:39:11 -05:00
.gitignore Updated CI. Updated Docs. 2025-10-21 21:57:26 -04:00
.markdownlint.json Initial commit 2025-10-21 21:41:51 -04:00
CHANGELOG.md Enhance backup scripts to support Redis and improve logging 2026-02-15 10:11:06 -05:00
Dockerfile Enhance backup scripts to support Redis and improve logging 2026-02-15 10:11:06 -05:00
justfile Enhance backup scripts to support Redis and improve logging 2026-02-15 10:11:06 -05:00
LICENSE Initial commit 2025-10-21 21:41:51 -04:00
README.md Enhance service management in backup scripts by adding container ID resolution and improving cleanup logic 2026-02-15 16:40:39 -05:00
TESTING.md Added GFS Retention Policy. cleanup and other improvements 2025-10-21 23:13:30 -04:00

Docker Backup Sidecar

A flexible, production-ready backup sidecar container for Docker Compose applications. Backup your databases, files, and volumes using restic for incremental, deduplicated, encrypted backups to any storage backend.

Features

  • Simple Configuration: Just set BACKUP_NAME and RESTIC_REPOSITORY to get started
  • Multiple Database Support: PostgreSQL, MongoDB, Redis, SQLite
  • File & Directory Backups: Backup any files or directories from mounted volumes
  • Restic-based Backups: Incremental, deduplicated, content-addressed storage
  • Built-in Encryption: AES-256 encryption via restic (no separate GPG step)
  • Flexible Storage Backends: Local, S3, B2, SFTP, REST server, and more via restic
  • Automated Scheduling: Cron-based scheduled backups
  • Retention Management: Automatic cleanup via restic forget --prune
  • Container Management: Stop/start containers before/after backup (essential for SQLite)
  • Health Monitoring: Webhook notifications for success/failure
  • Easy Restore: Snapshot-based restore with automatic database/file recovery

Quick Start

1. Add backup service to your docker-compose.yml

services:
  # Your existing services...
  app:
    image: myapp:latest
    volumes:
      - app-data:/data

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql/data

  # Add the backup sidecar
  backup:
    build:
      context: .
      dockerfile: Dockerfile
    # Or pull from registry:
    # image: your-registry/docker-backup-sidecar:latest
    restart: unless-stopped
    env_file:
      - .env
    environment:
      # BACKUP_NAME is required to identify your backups
      BACKUP_NAME: myapp-production
      TZ: America/New_York
    volumes:
      # Required: Docker socket for container management
      - /var/run/docker.sock:/var/run/docker.sock:ro
      # Mount data you want to backup (read-only recommended)
      - app-data:/data:ro
      # Local backup storage (restic repo)
      - ./backups:/backups-local
      # Restic cache (speeds up repeated operations)
      - restic-cache:/root/.cache/restic
    # No command needed! The entrypoint handles cron setup automatically
    # based on BACKUP_SCHEDULE environment variable

volumes:
  app-data:
  db-data:
  restic-cache:

2. Configure your .env file

# Copy the example
cp .env.example .env

# Edit with your configuration
nano .env

Minimum configuration:

# Backup name (REQUIRED)
BACKUP_NAME=myapp-production

# Restic repository (REQUIRED)
RESTIC_REPOSITORY=/backups-local/myapp

# Restic password (REQUIRED - store securely!)
RESTIC_PASSWORD=your-very-strong-passphrase-here

# What to backup
BACKUP_POSTGRES=postgresql://postgres:yourpassword@db:5432/myapp
BACKUP_DIRS=/data

# Schedule (daily at 2 AM) - omit for manual mode
BACKUP_SCHEDULE=0 2 * * *

Special Characters in Passwords

If your password contains special characters, you need to URL-encode them:

Character Encoded
@ %40
: %3A
/ %2F
? %3F
# %23
& %26
= %3D
% %25

Example:

# Password: my@pass:word
BACKUP_POSTGRES=postgresql://user:my%40pass%3Aword@db:5432/mydb

Or use an online URL encoder or the following command:

python3 -c "import urllib.parse; print(urllib.parse.quote('my@pass:word', safe=''))"

3. Start your stack

docker compose up -d

4. Test manual backup

docker compose exec backup /backup-scripts/backup-now.sh

Configuration Guide

Backup Name (Required)

Set BACKUP_NAME to uniquely identify your backups. This is used to tag snapshots in the restic repository.

# Required: Unique identifier for your backups
BACKUP_NAME=myapp-production

Examples:

  • myapp-prod for production environment
  • website-staging for staging environment
  • api-v2-prod for specific services

Restic Repository (Required)

Restic supports many storage backends natively:

Local Storage

RESTIC_REPOSITORY=/backups-local/myapp

Make sure to mount this path in docker-compose.yml:

volumes:
  - ./backups:/backups-local

S3-compatible Storage

RESTIC_REPOSITORY=s3:s3.us-east-1.amazonaws.com/my-bucket/myapp
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key

Database Configuration

PostgreSQL

PostgreSQL uses the standard connection URI format:

# Format: postgresql://[user[:password]@][host][:port][/database][?options]
BACKUP_POSTGRES=postgresql://postgres:mypassword@db:5432/mydb

# With environment variable for password
BACKUP_POSTGRES=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp

# Databases (one per line in your .env file)
BACKUP_POSTGRES=postgresql://postgres:pass@db:5432/db1

# With SSL options
BACKUP_POSTGRES=postgresql://user:pass@db:5432/mydb?sslmode=require

MongoDB

MongoDB uses the standard connection URI format:

# Format: mongodb://[user:password@]host[:port][/database][?options]
BACKUP_MONGODB=mongodb://root:mypassword@mongo:27017/?authSource=admin

# Backup specific database
BACKUP_MONGODB=mongodb://root:${MONGO_PASSWORD}@mongo:27017/mydb?authSource=admin

# Backup all databases (omit database name or use /)
BACKUP_MONGODB=mongodb://user:pass@mongo:27017/?authSource=admin

Redis

Redis RDB snapshots are backed up by triggering a SAVE command and copying the RDB file:

# Redis connection URI
BACKUP_REDIS=redis://redis:6379

# Where Redis data is mounted in the backup container (default: /redis-data)
BACKUP_REDIS_DATA_DIR=/redis-data

In your docker-compose.yml, mount the Redis data directory into the backup container:

backup:
  volumes:
    - redis-data:/redis-data:ro

SQLite

SQLite databases are just files, so back them up using BACKUP_FILES. Make sure to stop the container that uses the database first to avoid corruption:

# Format: /path/to/db.sqlite:name
BACKUP_FILES=/data/app.db:appdb

# MUST stop the container using the database
BACKUP_STOP_SERVICES=app
BACKUP_STOP_WAIT=10

Files and Directories

Directories

Directories are backed up directly by restic for efficient deduplication:

# Format: /path,/path2
BACKUP_DIRS=/data,/config,/uploads

Individual Files

Files are copied to a staging area before backup. The name after the colon is used to identify files in the backup and is required for restore to work correctly.

# Format: /path/file:name,/path2/file2:name2
BACKUP_FILES=/config/app.json:config,/secrets/api-key:apikey

For example, with BACKUP_FILES=/app/config.json:app-config, the file is stored in the backup as staging/files/app-config. During restore, it is looked up by this name and copied back to /app/config.json.

Container Management

For databases like SQLite that require exclusive access:

# Stop these services before backup (comma-separated)
BACKUP_STOP_SERVICES=app,worker

# Wait time after stopping (seconds, default: 2)
# Set to 0 to skip the wait - safe for most apps!
BACKUP_STOP_WAIT=2

# Wait time after starting (seconds, default: 3)
# Recommended to ensure services are fully initialized
BACKUP_START_WAIT=3

The backup sidecar will:

  1. Stop the specified containers
  2. Wait for BACKUP_STOP_WAIT seconds (allows graceful shutdown)
  3. Perform backup
  4. Start the containers
  5. Wait for BACKUP_START_WAIT seconds (allows services to initialize)

Usage

Manual Backup

docker compose exec backup /backup-scripts/backup-now.sh

List Available Snapshots

docker compose exec backup /backup-scripts/list-backups.sh

Restore from Snapshot

# List snapshots first to find the snapshot ID
docker compose exec backup /backup-scripts/list-backups.sh

# Restore (replace <snapshot-id> with actual ID, or use 'latest')
docker compose exec backup /backup-scripts/restore.sh <snapshot-id>
docker compose exec backup /backup-scripts/restore.sh latest

The restore script will:

  1. Restore the snapshot to a temporary directory
  2. Show a 10-second warning
  3. Stop configured services
  4. Restore databases, files, and directories
  5. Start services

View Logs

# View backup log
docker compose exec backup cat /var/log/backup.log

# Follow backup log
docker compose exec backup tail -f /var/log/backup.log

Storage Structure

Restic uses a content-addressed storage format with deduplication:

./backups/
  └── docker-backup-sidecar-test/
      ├── config          # Repository configuration
      ├── data/           # Deduplicated data packs
      ├── index/          # Index files
      ├── keys/           # Encryption keys
      ├── locks/          # Lock files
      └── snapshots/      # Snapshot metadata

Each snapshot contains the full backup tree. Restic handles deduplication automatically, so unchanged data is not stored twice.

Common Scenarios

Scenario 1: Web App with PostgreSQL

# docker-compose.yml
services:
  web:
    image: mywebapp:latest
    volumes:
      - uploads:/app/uploads

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql/data

  backup:
    # ... (backup service config)
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - uploads:/app/uploads:ro
      - ./backups:/backups-local

volumes:
  uploads:
  db-data:
# .env
RESTIC_REPOSITORY=/backups-local/myapp
RESTIC_PASSWORD=strong-passphrase-here
BACKUP_POSTGRES=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
BACKUP_DIRS=/app/uploads

Scenario 2: App with SQLite Database

# docker-compose.yml
services:
  app:
    image: myapp:latest
    container_name: myapp
    volumes:
      - ./data:/app/data

  backup:
    # ... (backup service config)
    environment:
      BACKUP_NAME: mywebapp-prod
      BACKUP_STOP_SERVICES: myapp
      BACKUP_STOP_WAIT: 0  # Can be 0 - safe once container is stopped
      BACKUP_START_WAIT: 2  # Brief wait to ensure app initializes
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data:/app/data:ro
      - ./backups:/backups-local
# .env
RESTIC_REPOSITORY=/backups-local/mywebapp
RESTIC_PASSWORD=strong-passphrase-here
BACKUP_FILES=/app/data/app.db:database
BACKUP_DIRS=/app/data/uploads

Note: SQLite databases should be backed up using BACKUP_FILES with the container stopped via BACKUP_STOP_SERVICES.

Scenario 3: Multi-DB Setup with S3

# .env
# S3 Configuration
RESTIC_REPOSITORY=s3:s3.us-east-1.amazonaws.com/my-company-backups/myapp
RESTIC_PASSWORD=strong-passphrase-here
AWS_ACCESS_KEY_ID=xxx
AWS_SECRET_ACCESS_KEY=xxx

# Multiple databases
BACKUP_POSTGRES=postgresql://postgres:${PG_PASSWORD}@postgres:5432/maindb
BACKUP_MONGODB=mongodb://root:${MONGO_PASSWORD}@mongo:27017/?authSource=admin

# Files
BACKUP_DIRS=/config,/uploads
BACKUP_FILES=/secrets/api-key.txt:apikey

# Monitoring
BACKUP_SUCCESS_WEBHOOK=https://hc-ping.com/your-uuid
BACKUP_FAILURE_WEBHOOK=https://hc-ping.com/your-uuid/fail

Scenario 4: App with Redis Cache + PostgreSQL

# docker-compose.yml
services:
  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    command: redis-server --save 60 1

  backup:
    # ... (backup service config)
    volumes:
      - redis-data:/redis-data:ro
      - ./backups:/backups-local
# .env
RESTIC_REPOSITORY=/backups-local/myapp
RESTIC_PASSWORD=strong-passphrase-here
BACKUP_POSTGRES=postgresql://postgres:pass@db:5432/myapp
BACKUP_REDIS=redis://redis:6379
BACKUP_REDIS_DATA_DIR=/redis-data

Health Monitoring

Integrate with services like healthchecks.io:

  1. Create a check with your backup schedule (e.g., daily at 2 AM)
  2. Set grace period for backup completion (e.g., 1 hour)
  3. Add webhook URLs to .env:
BACKUP_SUCCESS_WEBHOOK=https://hc-ping.com/your-uuid
BACKUP_FAILURE_WEBHOOK=https://hc-ping.com/your-uuid/fail

The sidecar will ping on success and failure automatically. Can be set to use any webhook.

Security Best Practices

  1. Restic Password

    • Use a strong, random passphrase
    • Store in a password manager
    • Never commit to git
    • Without this password, backups cannot be restored!
  2. S3 Credentials

    • Use IAM policies to restrict bucket access
    • Consider separate IAM user for backups
    • Enable S3 bucket versioning
    • Enable S3 bucket encryption at rest
  3. File Permissions

    • Mount volumes as read-only when possible (:ro), remove if you need to do a restore
    • Backup container needs docker socket access (required for stopping/starting)
  4. Network Security

    • Backup container can be on internal network only
    • No ports need to be exposed

Troubleshooting

Backups not running

# Check container is running
docker compose ps backup

# Check cron is configured
docker compose exec backup crontab -l

# Check logs
docker compose logs backup
docker compose exec backup cat /var/log/backup.log

Restic repository issues

# Check restic repo status
docker compose exec backup restic snapshots

# Check repo integrity
docker compose exec backup restic check

# Unlock a stale lock
docker compose exec backup restic unlock

Redis backup issues

# Verify Redis data directory is mounted
docker compose exec backup ls -la /redis-data/

# Test Redis connectivity from backup container
docker compose exec backup redis-cli -u redis://redis:6379 ping

# Check if RDB file exists
docker compose exec backup ls -la /redis-data/dump.rdb

Container stop/start not working

# Check docker socket is mounted
docker compose exec backup ls -la /var/run/docker.sock

# Test docker commands
docker compose exec backup docker ps

# Check BACKUP_NAME is set
docker compose exec backup env | grep BACKUP

Advanced Configuration

Custom Backup Schedule

Use standard cron format, or omit BACKUP_SCHEDULE entirely for manual-only mode:

# Every 6 hours
BACKUP_SCHEDULE=0 */6 * * *

# Weekly on Sunday at 3 AM
BACKUP_SCHEDULE=0 3 * * 0

# Every 15 minutes
BACKUP_SCHEDULE=*/15 * * * *

# Manual mode only (no automatic backups)
# Just comment out or don't set BACKUP_SCHEDULE

Retention Policy Configuration

Restic manages retention via restic forget --prune:

# Defaults if not set
RESTIC_KEEP_LAST=14      # Keep the last N snapshots
RESTIC_KEEP_DAILY=7      # Keep one snapshot per day for N days
RESTIC_KEEP_WEEKLY=4     # Keep one snapshot per week for N weeks
RESTIC_KEEP_MONTHLY=0    # Keep one snapshot per month for N months (0 = disabled)
RESTIC_KEEP_YEARLY=0     # Keep one snapshot per year for N years (0 = disabled)

Debug Mode

Enable verbose logging:

BACKUP_DEBUG=true

Disaster Recovery

In case of complete infrastructure loss:

  1. Setup new server with Docker and docker-compose

  2. Deploy backup container with same .env configuration

  3. List snapshots:

    docker compose exec backup /backup-scripts/list-backups.sh
    
  4. Restore:

    # Restore latest snapshot
    docker compose exec backup /backup-scripts/restore.sh latest
    
    # Or restore a specific snapshot
    docker compose exec backup /backup-scripts/restore.sh abc1234
    
  5. Verify application is working correctly

  6. Update DNS/networking as needed

Testing

This project includes comprehensive automated tests. See TESTING.md for details.

# Run all tests
just test

# Quick smoke tests
just test-quick

# Try the example
just up
just backup
just list

License

This project is licensed under the MIT License - see the LICENSE file for details.