在VPS上备份和恢复Docker卷

2 分钟阅读·Matthieu·cronrclones3mysqlpostgresqlbackupdocker-composedocker|

VPS上Docker卷的三种备份策略:tar快照、数据库原生dump,以及使用offen/docker-volume-backup的自动加密备份。包含cron定时任务、rclone远程复制到S3,以及在全新服务器上的完整恢复测试。

Docker命名卷保存着你的生产数据:数据库、上传文件、配置状态。容器是可以丢弃的,卷不行。如果磁盘故障或迁移配置出错,你需要恢复的就是这些卷。

本指南介绍三种备份策略,用cron实现自动化,用rclone将副本传输到服务器外,然后在全新VPS上重建来证明恢复确实有效。如果你没有测试过恢复流程,你就没有真正的备份。

你将学到:

  • 基于tar的卷快照(简单、通用)
  • 使用pg_dumpmysqldump的数据库原生dump(事务一致、无停机)
  • 使用offen/docker-volume-backup的自动加密备份(定时执行、支持S3)
  • cron自动化与保留策略
  • 通过rclone将副本传输到S3兼容存储
  • 在全新VPS上进行完整灾难恢复

前提条件

你需要一台运行Debian 12或Ubuntu 24.04的VPS,已安装Docker和Docker Compose v2。本指南假设你有一个正在运行的Compose栈,包含至少一个命名卷。如果你需要先搭建环境,请参阅我们的Docker Compose多服务VPS部署指南。

验证你的环境:

docker --version
# Docker version 28.x or newer
docker compose version
# Docker Compose version v2.x or newer

查看现有卷:

docker volume ls

输出会列出系统上的每个命名卷。确定哪些包含你关心的数据。使用docker system df -v查看每个卷的空间占用,这有助于估算备份大小和存储需求。

创建一个权限受限的备份目录:

mkdir -p /opt/backups/docker
chmod 700 /opt/backups/docker

只有root能读取这个目录。备份通常包含数据库凭据、会话令牌或用户数据。

如何在VPS上备份Docker卷?

有三种策略,各有不同的取舍。根据卷的内容和你能容忍的停机时间来选择。

方法 停机时间 数据一致性 自动化 加密 适用场景
Tar快照 短暂(容器停止) 文件系统级 手动或cron脚本 无(需另外添加GPG) 静态文件、上传文件、配置
数据库dump 事务一致 手动或cron脚本 无(需另外添加GPG) PostgreSQL、MySQL、MariaDB
offen/docker-volume-backup 可选(可配置) 文件系统级 内置调度器 内置GPG 任何卷,无人值守运行

如何创建Docker卷的tar备份?

停止使用该卷的容器,运行一个临时Alpine容器将卷内容打包为tar,然后重启。对大多数卷来说这只需几秒钟,适用于任何数据类型。

1. 停止写入该卷的容器:

# Replace "app" with your service name from docker-compose.yml
docker compose stop app

停止写入方可防止归档过程中出现部分写入。对于只读卷或静态文件,可以跳过这一步。

2. 创建tar归档:

docker run --rm \
  -v myapp_data:/source:ro \
  -v /opt/backups/docker:/backup \
  alpine tar czf /backup/myapp_data-$(date +%Y%m%d-%H%M%S).tar.gz -C /source .

这条命令的作用:启动一个临时Alpine容器,将你的卷以只读方式挂载到/source,将备份目录挂载到/backup,然后创建一个gzip压缩的tar归档。--rm标志在容器完成后将其删除。:ro标志防止备份过程意外写入你的数据。

3. 重启容器:

docker compose start app

4. 验证归档:

ls -lh /opt/backups/docker/myapp_data-*.tar.gz

输出显示一个大小合理的归档文件。500 MB的卷通常压缩到60-120 MB,具体取决于数据类型。

列出归档内容以确认文件存在:

tar tzf /opt/backups/docker/myapp_data-20260319-120000.tar.gz | head -20

注意观察:路径应以./开头(没有前导目录名)。这是因为我们在tar命令中使用了-C /source .。这在恢复时很重要。

如何备份Docker中运行的PostgreSQL数据库?

在运行中的容器内使用pg_dump。这会产生一个事务一致的dump,无需停止数据库。自定义格式(-Fc)会压缩输出并支持选择性恢复。

docker compose exec -T postgres pg_dump \
  -U "$POSTGRES_USER" \
  -Fc \
  --no-owner \
  --no-acl \
  mydb > /opt/backups/docker/mydb-$(date +%Y%m%d-%H%M%S).dump

这条命令的作用:exec -T在运行中的postgres容器内执行命令,不分配TTY(这是管道输出所必需的)。-Fc选择自定义格式,已压缩且支持pg_restore--no-owner--no-acl使dump在不同数据库用户之间可移植。

$POSTGRES_USER变量应来自你的环境文件,而不是硬编码。如果你的Compose栈使用env文件:

source /opt/myapp/.env
docker compose exec -T postgres pg_dump \
  -U "$POSTGRES_USER" \
  -Fc \
  --no-owner \
  --no-acl \
  "$POSTGRES_DB" > /opt/backups/docker/"$POSTGRES_DB"-$(date +%Y%m%d-%H%M%S).dump

通过容器内的pg_restore验证dump:

docker compose exec -T postgres pg_restore --list < /opt/backups/docker/mydb-20260319-120000.dump | head -10

这会打印目录而不实际恢复任何内容。如果文件损坏,pg_restore会报错。我们使用docker compose exec -T是因为pg_restore在容器内部,不在宿主机上(除非你单独安装了postgresql-client)。

如何备份Docker中运行的MySQL数据库?

对InnoDB表使用mysqldump配合--single-transaction。这能获得一致的快照而不锁定数据库。

docker compose exec -T mysql mysqldump \
  -u root \
  -p"$MYSQL_ROOT_PASSWORD" \
  --single-transaction \
  --routines \
  --triggers \
  mydb > /opt/backups/docker/mydb-$(date +%Y%m%d-%H%M%S).sql

-p标志和密码之间没有空格。--single-transaction对InnoDB表使用一致性读取。--routines--triggers包含存储过程和触发器,mysqldump默认会跳过这些。

验证dump不为空且以完成标记结尾:

tail -5 /opt/backups/docker/mydb-20260319-120000.sql

输出显示-- Dump completed on YYYY-MM-DD HH:MM:SS。如果文件被截断或为空,dump失败了。

如何使用offen/docker-volume-backup自动化Docker卷备份?

offen/docker-volume-backup作为Compose栈中的sidecar容器运行。它按计划备份挂载的卷,可选择使用GPG加密归档,并能直接上传到S3兼容存储。它还能在备份期间停止容器以确保一致性。

将备份服务添加到你的docker-compose.yml

services:
  # ... your existing services ...

  backup:
    image: offen/docker-volume-backup:v2.47.2
    restart: unless-stopped
    env_file:
      - ./backup.env
    volumes:
      - myapp_data:/backup/myapp_data:ro
      - myapp_db:/backup/myapp_db:ro
      - /opt/backups/docker:/archive
      - /var/run/docker.sock:/var/run/docker.sock
    labels:
      - docker-volume-backup.stop-during-backup=false

这段配置的作用:将每个需要备份的卷以只读方式挂载到/backup/下,将/archive挂载为本地备份存储,并挂载Docker socket以便工具在配置时能停止和重启容器。镜像标签v2.47.2锁定了版本。不要在生产环境中使用latest

**安全提示:**挂载Docker socket赋予了备份容器对宿主机Docker的完全控制权。这是备份期间停止容器功能所必需的。如果你不需要这个功能,可以以只读方式挂载(/var/run/docker.sock:/var/run/docker.sock:ro),这允许工具读取容器标签但阻止它停止或启动容器。

创建权限受限的环境文件:

touch /opt/myapp/backup.env
chmod 600 /opt/myapp/backup.env
# /opt/myapp/backup.env
BACKUP_CRON_EXPRESSION=0 3 * * *
BACKUP_RETENTION_DAYS=7
BACKUP_COMPRESSION=gz
BACKUP_FILENAME=backup-%Y%m%dT%H%M%S.tar.gz

# GPG encryption (generate a strong passphrase)
GPG_PASSPHRASE=your-generated-passphrase-here

# S3-compatible storage (optional, see rclone section for alternative)
# AWS_S3_BUCKET_NAME=my-backups
# AWS_S3_PATH=myapp
# AWS_ENDPOINT=s3.eu-central-1.amazonaws.com
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=

生成GPG密码短语:

openssl rand -base64 32

将这个密码短语安全地保存在服务器之外的地方。如果丢失,加密的备份将无法恢复。

如果你希望备份工具在备份期间停止某些容器以确保文件系统一致性,请给那些服务添加标签:

services:
  app:
    # ... your config ...
    labels:
      - docker-volume-backup.stop-during-backup=true

启动备份服务:

docker compose up -d backup

验证它正在运行:

docker compose logs backup

输出显示一条确认cron计划的日志。等待第一次计划执行,或手动触发备份:

docker compose exec backup backup

检查归档是否已生成:

ls -lh /opt/backups/docker/

如何使用cron调度Docker备份?

对于tar和数据库dump策略,shell脚本配合cron处理调度和保留。offen工具有自己的调度器,如果你只用它则跳过本节。

创建备份脚本:

touch /opt/backups/docker-backup.sh
chmod 700 /opt/backups/docker-backup.sh
#!/usr/bin/env bash
# /opt/backups/docker-backup.sh
# Backs up Docker volumes and databases, removes old archives.

set -euo pipefail

BACKUP_DIR="/opt/backups/docker"
RETENTION_DAYS=7
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
COMPOSE_DIR="/opt/myapp"

# Load database credentials from env file
source "${COMPOSE_DIR}/.env"

cd "$COMPOSE_DIR"

# --- Tar backup of application data volume ---
docker compose stop app
docker run --rm \
  -v myapp_data:/source:ro \
  -v "${BACKUP_DIR}":/backup \
  alpine tar czf "/backup/myapp_data-${TIMESTAMP}.tar.gz" -C /source .
docker compose start app

# --- PostgreSQL dump (no downtime) ---
docker compose exec -T postgres pg_dump \
  -U "$POSTGRES_USER" \
  -Fc --no-owner --no-acl \
  "$POSTGRES_DB" > "${BACKUP_DIR}/${POSTGRES_DB}-${TIMESTAMP}.dump"

# --- Retention: delete backups older than N days ---
find "$BACKUP_DIR" -type f -name "*.tar.gz" -mtime +${RETENTION_DAYS} -delete
find "$BACKUP_DIR" -type f -name "*.dump" -mtime +${RETENTION_DAYS} -delete

echo "[$(date -Iseconds)] Backup completed successfully"

验证脚本能无错运行:

/opt/backups/docker-backup.sh

检查输出文件:

ls -lh /opt/backups/docker/

添加一个每天03:00执行并记录输出的cron条目:

crontab -e
0 3 * * * /opt/backups/docker-backup.sh >> /var/log/docker-backup.log 2>&1

2>&1将stderr重定向到同一个日志文件,这样错误也会被捕获。第一次运行后检查日志:

cat /var/log/docker-backup.log

如果脚本失败,cron会静默吞掉错误,除非你重定向了输出。要在失败时收到邮件告警,添加这个wrapper:

0 3 * * * /opt/backups/docker-backup.sh >> /var/log/docker-backup.log 2>&1 || echo "Docker backup failed on $(hostname)" | mail -s "BACKUP FAILED" you@example.com

这需要mailutils或类似的包。调整收件人地址。

如何使用rclone将Docker备份复制到S3兼容存储?

本地备份防护的是应用程序故障,无法防护磁盘故障或服务器被入侵。你需要异地副本。rclone适用于任何S3兼容存储:AWS S3、Backblaze B2、Wasabi、MinIO、OVH Object Storage、Scaleway等。

安装rclone:

apt update && apt install -y rclone

配置S3兼容的remote:

rclone config

按照交互提示操作:

  1. 输入n创建新remote
  2. 命名为s3backup
  3. 选择s3(Amazon S3 Compliant Storage Providers)
  4. 选择你的供应商(或"Any other S3 compatible provider")
  5. 输入你的access key和secret key
  6. 设置供应商的region和endpoint URL
  7. 其他选项保持默认

验证remote是否可用:

rclone lsd s3backup:

这会列出你的bucket。如果失败,说明凭据或endpoint有误。

创建备份用的bucket(如果你的供应商支持通过rclone创建):

rclone mkdir s3backup:my-docker-backups

将本地备份目录同步到bucket:

rclone sync /opt/backups/docker s3backup:my-docker-backups/$(hostname)/ \
  --transfers 4 \
  --checkers 8 \
  --log-file /var/log/rclone-backup.log \
  --log-level INFO

这条命令的作用:sync使远端与本地目录保持一致。本地删除的文件(因保留策略删除的)也会在远端删除。$(hostname)前缀用于在你有多台服务器时区分备份。

验证上传:

rclone ls s3backup:my-docker-backups/$(hostname)/

输出显示备份文件及其大小,与本地副本一致。

将rclone sync添加到备份脚本中,或作为备份后运行的单独cron条目:

30 3 * * * rclone sync /opt/backups/docker s3backup:my-docker-backups/$(hostname)/ --transfers 4 --log-file /var/log/rclone-backup.log --log-level INFO

这在03:30执行,给03:00的备份任务留出完成时间。

**保护rclone配置:**它包含你的S3凭据。

chmod 600 ~/.config/rclone/rclone.conf
ls -la ~/.config/rclone/rclone.conf

输出显示-rw-------权限。只有root能读取此文件。

备份Docker卷前是否需要停止容器?

这取决于卷的内容。判断错误是备份损坏最常见的原因。

**数据库(PostgreSQL、MySQL、MongoDB):**永远不要对运行中的数据库卷执行tar。磁盘上的文件代表正在进行的事务状态。对这些文件做tar就像在别人改写章节时复印一本书,结果在内部是不一致的。应该使用pg_dumpmysqldumpmongodump。这些工具在数据库继续运行的同时生成事务一致的快照。

**应用数据(上传文件、静态文件、配置):**如果应用能容忍短暂停止,tar是安全的。如果应用持续写入且你无法停止它,tar可能包含写入不完整的文件。对大多数Web应用来说,凌晨3点备份时停止2秒是可以接受的。

**Redis、键值存储:**Redis会定期将RDB快照写入磁盘。在对卷做tar之前触发BGSAVE,然后等待完成。这能在不停止Redis的情况下获得一致的快照。

docker compose exec redis redis-cli BGSAVE
# Wait a few seconds
docker compose exec redis redis-cli LASTSAVE

**安全的默认选择:**如果不确定,就停止容器、备份、重启。短暂停机比损坏的备份要好。

如何在新VPS上恢复Docker卷?

这是证明你的备份有效的流程。在全新服务器上安装Docker,传输备份文件,重建卷,恢复数据,验证应用正常运行。

1. 在新VPS上安装Docker

apt update && apt install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  | tee /etc/apt/sources.list.d/docker.list > /dev/null

apt update && apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

检查:

docker --version && docker compose version

2. 将备份文件传输到新服务器

从你的本地机器或旧服务器:

rsync -avz --progress /opt/backups/docker/ root@NEW_SERVER_IP:/opt/backups/docker/

或从S3下载:

# On the new server, install and configure rclone first
apt install -y rclone
# Re-run rclone config with the same credentials
rclone copy s3backup:my-docker-backups/OLD_HOSTNAME/ /opt/backups/docker/

验证文件已到达:

ls -lh /opt/backups/docker/

3. 复制Compose文件和env文件

rsync -avz /opt/myapp/ root@NEW_SERVER_IP:/opt/myapp/

或从Git仓库恢复。你的docker-compose.yml.env应该做版本控制。.env文件应该在.gitignore中并单独备份。

4. 恢复基于tar的卷

# Create the volume (Docker Compose will also do this on first `up`,
# but creating it explicitly lets us restore data before starting services)
docker volume create myapp_data

# Restore from archive
docker run --rm \
  -v myapp_data:/target \
  -v /opt/backups/docker:/backup:ro \
  alpine sh -c "cd /target && tar xzf /backup/myapp_data-20260319-030000.tar.gz"

这条命令的作用:创建命名卷,然后运行一个临时容器将归档解压到其中。备份目录以只读方式挂载以防止意外。

验证恢复的数据:

docker run --rm -v myapp_data:/data:ro alpine ls -la /data/

输出显示与原始卷中相同的文件。

5. 恢复PostgreSQL数据库

只启动数据库容器:

cd /opt/myapp
docker compose up -d postgres

等待它就绪:

docker compose logs -f postgres
# Wait until you see "database system is ready to accept connections"

恢复dump:

docker compose exec -T postgres pg_restore \
  -U "$POSTGRES_USER" \
  -d "$POSTGRES_DB" \
  --clean \
  --if-exists \
  --no-owner \
  --no-acl \
  < /opt/backups/docker/mydb-20260319-030000.dump

这条命令的作用:--clean在重建前删除现有对象。--if-exists在对象不存在时防止报错。这使恢复操作幂等。

验证数据:

docker compose exec postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "\dt"

输出显示表的列表。在已知表上快速统计行数:

docker compose exec postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT count(*) FROM your_table;"

6. 启动所有服务并验证

docker compose up -d

检查所有容器是否在运行:

docker compose ps

每个服务应显示Uprunning。如果有容器在不断重启,检查其日志:

docker compose logs --tail 50 service_name

从服务器外部测试应用。从你的本地机器:

curl -I http://NEW_SERVER_IP:PORT

你应该得到有效的HTTP响应。如果应用有健康检查端点,请访问它:

curl http://NEW_SERVER_IP:PORT/health

关于健康检查的更多信息,请参阅我们的Docker Compose资源限制与健康检查指南。

如何验证Docker卷备份是否有效?

从未测试过的备份是一种隐患。定期执行这些检查,不要只在灾难恢复时才做。

检查归档完整性:

# For tar.gz files
gzip -t /opt/backups/docker/myapp_data-20260319-030000.tar.gz && echo "OK" || echo "CORRUPT"

检查归档内容:

tar tzf /opt/backups/docker/myapp_data-20260319-030000.tar.gz | wc -l

将文件数与已知良好的备份进行比较。文件数突然下降表示有问题。

测试恢复到临时卷:

docker volume create test_restore
docker run --rm \
  -v test_restore:/target \
  -v /opt/backups/docker:/backup:ro \
  alpine sh -c "cd /target && tar xzf /backup/myapp_data-20260319-030000.tar.gz"

# Inspect the restored data
docker run --rm -v test_restore:/data:ro alpine ls -la /data/

# Clean up
docker volume rm test_restore

验证数据库dump:

docker compose exec -T postgres pg_restore --list < /opt/backups/docker/mydb-20260319-030000.dump | wc -l

如果返回对象数量(表、索引、序列),dump可读。如果报错,文件损坏。

生成用于长期存储的校验和:

sha256sum /opt/backups/docker/*.tar.gz /opt/backups/docker/*.dump > /opt/backups/docker/checksums-$(date +%Y%m%d).sha256

将校验和文件与备份一起上传。恢复前检查:

sha256sum -c /opt/backups/docker/checksums-20260319.sha256

故障排除

创建tar归档时"Permission denied": 临时容器默认以root身份运行,所以这通常意味着备份目录不存在或权限错误。运行ls -la /opt/backups/,确认docker子目录存在且权限为700

pg_dump/pg_restore挂起: 你可能忘了在docker compose exec中使用-T标志。没有-T时,exec会尝试分配TTY,在管道输出时会阻塞。使用docker compose exec -T

备份文件大小为0字节: 容器写入了与预期不同的路径。仔细检查docker volume ls中的卷名是否与-v标志中使用的一致。命名卷区分大小写。

rclone sync超时: 大型初始同步可能超过默认超时时间。在rclone命令中添加--timeout 30m--retries 3

offen/docker-volume-backup未按计划运行: 检查BACKUP_CRON_EXPRESSION的语法。该工具使用标准的5字段cron语法。运行docker compose logs backup查找解析错误。

恢复的数据库权限错误: 你使用了没有--no-owner的dump。Dump会尝试将所有权设为原始用户,而该用户在新服务器上可能不存在。重新用--no-owner --no-acl执行dump,或在psql中执行REASSIGN OWNED BY old_user TO new_user;

后续阅读

  • 完整的Docker生产环境配置
  • 恢复后确认服务存活的健康检查
  • Docker CLI基础

版权所有 2026 Virtua.Cloud。保留所有权利。 本内容为 Virtua.Cloud 团队原创作品。 未经书面许可,禁止复制、转载或再分发。

准备好亲自尝试了吗?

在VPS上部署Docker并自动备份。

查看 VPS 方案