//------------------------------------------------------------------- //-------------------------------------------------------------------
back up Docker volumes to S3 or MinIO using Restic

How to Back Up Docker Volumes to S3 or MinIO with Restic on a Linux VPS

If you run Docker containers on a Linux VPS and have not set up a real backup yet, this guide is for you. You will learn step by step how to back up Docker volumes to S3 or MinIO using Restic, an open-source backup tool that handles encryption, deduplication, and scheduling all in one place. Whether you push backups to AWS S3 or your own self-hosted MinIO server, the process is almost the same.

What Is Restic and Why Use It for Docker?

Restic is a fast and modern backup program that works on Linux, stores backups as encrypted snapshots, and supports any S3-compatible object storage as a backend. It only uploads the parts of files that changed since the last backup, so after the first run, daily backups are very fast and use little extra storage.

For Docker, this matters because:

  • Named volumes live under /var/lib/docker/volumes/ and are not backed up automatically.
  • Bind mounts live on a host path you control, but those paths are still not protected unless you have a backup plan.
  • Restic turns both types into encrypted, versioned snapshots you can restore in minutes.

Note: This guide focuses on backing up Docker volume data directly to an S3 or MinIO bucket. If your goal is backing up a whole server or multiple directories over SFTP to a Storage VPS, check the Restic Backup to Storage VPS guide instead, which covers a different target, including SFTP and Storage VPS.

Prerequisites to Back Up Docker Volumes to S3 or MinIO

Before you start, make sure you have:

A Linux VPS running Ubuntu, Debian, Rocky Linux, or AlmaLinux with Docker installed. You can easily get your server running from PerLod Linux VPS Hosting.

Root or sudo access to the server.

Either an AWS S3 bucket with an access key and secret key, or a self-hosted MinIO server with a bucket and credentials ready.

Step 1. Install Restic on Your Linux VPS

Restic is available in the default package repositories of most major Linux distributions.

On Ubuntu or Debian, you can install Restic with:

sudo apt update
sudo apt install restic -y

On Rocky Linux or AlmaLinux, you can use:

sudo dnf install epel-release -y
sudo dnf install restic -y

After installing, run the self-update command to make sure you have the latest version:

sudo restic self-update

Confirm it works:

restic version

Step 2. Prepare Your S3 or MinIO Bucket

You need a bucket before Restic can initialize a repository. The steps have some differences depending on which backend you use.

Option A: AWS S3

  1. Log in to your AWS console and go to the S3 service.
  2. Create a new bucket, for example, my-docker-backups.
  3. Create an IAM user with the following permissions on that bucket:
    • s3:ListBucket
    • s3:PutObject
    • s3:GetObject
    • s3:DeleteObject
  4. Save the Access Key ID and Secret Access Key for the next step.

Option B: Self-Hosted MinIO

Self-hosted MinIO is one of the best ways to back up Docker volumes to S3 or MinIO without relying on AWS. If you do not have MinIO set up yet, the Self-Hosting MinIO guide shows the full install process.

Also, you can put MinIO behind HTTPS with the Run MinIO Behind an HTTPS Reverse Proxy guide, which is strongly recommended for production use.

Once MinIO is running:

  1. Open the MinIO web console at http://YOUR_SERVER_IP:9001.
  2. Log in with your root credentials.
  3. Go to Buckets > Create Bucket and name it, for example, docker-backups.
  4. Go to Identity > Users > Create User, create a dedicated backup user.
  5. Create a policy that allows ListBucket, PutObject, GetObject, and DeleteObject on your bucket.
  6. Attach that policy to your backup user.
  7. Save the access key and secret key.

Also, you can do this from the command line with the mc client tool:

# Download and install mc
curl https://dl.min.io/client/mc/release/linux-amd64/mc -o /usr/local/bin/mc
chmod +x /usr/local/bin/mc

# Add your MinIO server as an alias
mc alias set myminio http://YOUR_MINIO_IP:9000 ROOTUSER ROOTPASSWORD

# Create bucket
mc mb myminio/docker-backups

Step 3. Set Up Your Environment Variables

It is always recommended to store the credentials in a dedicated environment file that only root can read.

Create the file with your desired text editor:

sudo nano /etc/restic-docker.env

For AWS S3, add:

export AWS_ACCESS_KEY_ID="your-access-key-id"
export AWS_SECRET_ACCESS_KEY="your-secret-access-key"
export RESTIC_REPOSITORY="s3:s3.amazonaws.com/my-docker-backups"
export RESTIC_PASSWORD="a-strong-passphrase-here"

For self-hosted MinIO, add:

export AWS_ACCESS_KEY_ID="your-minio-access-key"
export AWS_SECRET_ACCESS_KEY="your-minio-secret-key"
export RESTIC_REPOSITORY="s3:http://YOUR_MINIO_IP:9000/docker-backups"
export RESTIC_PASSWORD="a-strong-passphrase-here"

Important Note: If MinIO is behind HTTPS, use https:// in the URL instead of http://. Restic uses path-style S3 URLs; the bucket name comes after the endpoint, not as a subdomain. This applies whether you back up Docker volumes to S3 or MinIO; the URL format is the only real difference.

Then, restrict the file permissions with:

sudo chmod 600 /etc/restic-docker.env
sudo chown root:root /etc/restic-docker.env

Step 4. Initialize the Restic Repository

In this step, Restic creates the repository structure in your bucket and sets up the encryption keys. You can use the commands below to load your variables and run init:

source /etc/restic-docker.env
restic init

In the output, you should see something similar to this:

created restic repository abc123def at s3:...

Note: Do not lose your RESTIC_PASSWORD. If you lose it, your backups are permanently unreadable. Restic’s encryption means nobody can recover data without the password.

Step 5. Understand Named Volumes vs Bind Mounts

Before writing your backup script, you need to know what kind of volumes your containers use.

Named Volumes

These are managed by Docker and stored at /var/lib/docker/volumes/ on the host. You can list them with:

docker volume ls

To find the exact path of a specific volume:

docker volume inspect -f '{{ .Mountpoint }}' my_volume_name

To back up Docker volumes to S3 or MinIO, you can easily point Restic at the parent directory:

sudo restic backup /var/lib/docker/volumes/ --tag docker-volumes

Bind Mounts

If your docker-compose.yml uses a path like ./data:/app/data or /opt/myapp:/app/data, that path on the host is your backup target. You can back up Docker volumes to S3 or MinIO for bind mounts just as easily:

restic backup /opt/myapp /opt/compose --tag docker-bind-mounts

Step 6. Turn Off Databases Before Backing Up

This is one of the most important things you must consider. Never back up database volume files while the database container is running. Copying live PostgreSQL or MySQL data files will produce a corrupted backup. This is true regardless of whether you back up Docker volumes to S3 or MinIO; this step is always required for databases.

For this purpose, you have two safe options:

Option A: Stop the Container First (Safest)

docker compose stop myapp_db
restic backup /var/lib/docker/volumes/myapp_db_data/ --tag db-backup
docker compose start myapp_db

This guarantees a clean backup at the cost of a short downtime window.

Option B: Use a Database Dump (No Downtime)

For PostgreSQL, you can run a dump inside the container and back up the dump file:

docker exec my_postgres_container pg_dumpall -U postgres > /tmp/pg_backup_$(date +%Y%m%d).sql
restic backup /tmp/pg_backup_$(date +%Y%m%d).sql --tag postgres-dump
rm /tmp/pg_backup_$(date +%Y%m%d).sql

For MySQL:

docker exec my_mysql_container mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" --all-databases > /tmp/mysql_backup_$(date +%Y%m%d).sql
restic backup /tmp/mysql_backup_$(date +%Y%m%d).sql --tag mysql-dump
rm /tmp/mysql_backup_$(date +%Y%m%d).sql

For MongoDB:

docker exec my_mongo_container mongodump --archive --gzip > /tmp/mongo_backup_$(date +%Y%m%d).gz
restic backup /tmp/mongo_backup_$(date +%Y%m%d).gz --tag mongo-dump
rm /tmp/mongo_backup_$(date +%Y%m%d).gz

Option B is recommended for production because it keeps services running while still providing a consistent, restorable snapshot.

Step 7. Write the Backup Script

At this point, you can put all things together in one script. Create the script file with:

sudo nano /usr/local/bin/docker-volume-backup.sh

Paste the following content and adjust the paths and container names to match your setup:

#!/bin/bash
set -euo pipefail

# Load credentials and repo config
source /etc/restic-docker.env

LOG="/var/log/restic-docker-backup.log"
DATE=$(date +%Y%m%d_%H%M%S)

echo "[$DATE] Starting Docker volume backup..." | tee -a "$LOG"

# --- Step 1: Database dump (no downtime) ---
# PostgreSQL example — adjust container name and user
if docker ps --format '{{.Names}}' | grep -q "postgres"; then
  docker exec my_postgres_container pg_dumpall -U postgres > /tmp/pg_dump_${DATE}.sql
  echo "[$DATE] PostgreSQL dump done." | tee -a "$LOG"
fi

# --- Step 2: Back up named volumes ---
restic backup /var/lib/docker/volumes/ \
  --tag docker-volumes \
  --exclude="*/buildkit" \
  --verbose >> "$LOG" 2>&1

# --- Step 3: Back up any database dumps ---
if ls /tmp/pg_dump_*.sql 1>/dev/null 2>&1; then
  restic backup /tmp/pg_dump_${DATE}.sql --tag postgres-dump >> "$LOG" 2>&1
  rm /tmp/pg_dump_*.sql
fi

# --- Step 4: Apply retention policy ---
restic forget \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 3 \
  --prune >> "$LOG" 2>&1

echo "[$DATE] Backup complete." | tee -a "$LOG"

Once you are done, save and close the file. Make it executable and restrict permissions:

sudo chmod 700 /usr/local/bin/docker-volume-backup.sh
sudo chown root:root /usr/local/bin/docker-volume-backup.sh

Test it manually with the command below:

sudo /usr/local/bin/docker-volume-backup.sh

Step 8. Set a Retention Policy

The restic forget command removes old snapshots based on the rules you define. The --prune flag tells Restic to delete the actual data that those snapshots referenced.

The script above uses a solid default policy, including:

FlagMeaning
–keep-daily 7Keep one snapshot per day for the last 7 days
–keep-weekly 4Keep one snapshot per week for the last 4 weeks
–keep-monthly 3Keep one snapshot per month for the last 3 months
–pruneDelete data blocks no longer referenced by any kept snapshot

You can run restic forget --dry-run with the same flags to see what would be deleted before actually deleting anything.

Step 9. Schedule with Systemd Timer

Systemd timers are more reliable than plain cron because they log to the journal, handle missed runs, and give you clear status information.

Create the service unit file with:

sudo nano /etc/systemd/system/docker-volume-backup.service

Add:

[Unit]
Description=Restic Docker Volume Backup to S3/MinIO

[Service]
Type=oneshot
ExecStart=/usr/local/bin/docker-volume-backup.sh
User=root
StandardOutput=journal
StandardError=journal

Create the timer unit file:

sudo nano /etc/systemd/system/docker-volume-backup.timer

Add:

[Unit]
Description=Run Docker Volume Backup Daily at 2 AM

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target

Once you are done, enable and start the timer:

sudo systemctl daemon-reload
sudo systemctl enable --now docker-volume-backup.timer

Check the timer status:

systemctl list-timers docker-volume-backup.timer

View the backup logs:

journalctl -u docker-volume-backup.service -f

Step 10. Test Your Restore

A backup you have never tested is not a backup. You can run a restore test to confirm everything works.

First, list all snapshots with:

source /etc/restic-docker.env
restic snapshots

Restore the latest snapshot to a test directory:

restic restore latest --target /tmp/restore-test
ls /tmp/restore-test/var/lib/docker/volumes/

Restore a specific snapshot by ID:

restic restore a1b2c3d4 --target /tmp/restore-test

To actually restore a volume for a live app, you must:

# Stop the container
docker compose stop myapp

# Clear the old volume data
sudo rm -rf /var/lib/docker/volumes/myapp_data/_data/*

# Restore from snapshot
restic restore latest \
  --include /var/lib/docker/volumes/myapp_data \
  --target /

# Restart the container
docker compose start myapp

Run a repository integrity check:

restic check

For deeper verification, you can run the command below to read all data blocks from the bucket:

restic check --read-data

For this setup to run consistently, you need a VPS with stable uptime and enough disk to handle staging database dumps before they are sent to the bucket. For larger setups with heavy Docker workloads, a high-performance dedicated server gives you more CPU, RAM, and storage space to run backup jobs without affecting your production containers.

Conclusion

Setting up a reliable way to back up Docker volumes to S3 or MinIO with Restic is one of the most useful things you can do for any self-hosted stack. Once the environment file is in place and the first restic init succeeds, everything else, including daily snapshots, retention cleanup, and restore testing, runs automatically with minimal overhead.

We hope you enjoy this guide. If you want to go deeper on S3 backend options and advanced repository settings, the Restic Official Documentation for Amazon S3 Backend is a good option.

FAQs

Do I need to stop my containers before running Restic?

Only for databases. For regular app data, you can back up Docker volumes to S3 or MinIO while containers are running without issue. For PostgreSQL, MySQL, or MongoDB, use a dump or stop the container first to get a consistent backup.

What is the difference between restic forget and restic prune?

forget removes the snapshot record. prune removes the actual data blocks no longer referenced by any snapshot. Run them together using restic forget --prune to free up storage on your S3 or MinIO bucket.

Can I back up Docker volumes to S3 or MinIO from multiple servers into one repository?

Restic supports multiple hosts writing to the same repository. Each snapshot is tagged with a hostname, so they do not overwrite each other. However, for simplicity, separate repositories per server are usually easier to manage.

Post Your Comment

PerLod delivers high-performance hosting with real-time support and unmatched reliability.

Contact us

Payment methods

payment gateway