Docker 部署 WordPress + MySQL,如何用定时任务自动备份站点到 Cloudflare R2

手把手教你用 Docker Compose 部署 WordPress + MySQL,再用 mysqldump + rclone + crontab 定时将数据库和文件自动备份到 Cloudflare R2 对象存储,低成本且可恢复。

自己搭的 WordPress,备份是最容易忽略的事

很多人在 VPS 上用 Docker 跑起 WordPress,配好 MySQL,写完第一篇文章,就觉得万事大吉了。但实际运营一段时间后才会意识到一个问题:如果服务器挂了,数据怎么恢复?

数据库丢了,文章、评论、设置全部归零。wp-content/uploads 目录里的图片、附件丢一次就要命。而大多数 VPS 厂商的快照要么是额外收费的,要么恢复起来非常麻烦。

这篇文章会从零开始,讲清楚用 Docker 部署 WordPress + MySQL 的关键要点,然后重点解决一个实际问题:如何用定时任务自动把整个站点(数据库 + 文件)备份到 Cloudflare R2。

全部用免费或低成本方案,不需要额外买备份插件。

Docker Compose 部署 WordPress + MySQL

如果你已经在服务器上跑 WordPress 了,可以直接跳过这段。如果还没有,下面是最简单的 docker-compose 配置。

version: '3.8'

services:
  db:
    image: mysql:8.0
    container_name: wordpress_db
    restart: always
    volumes:
      - ./db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: your_root_password
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: your_db_password

  wordpress:
    image: wordpress:6-php8.1-apache
    container_name: wordpress_app
    restart: always
    depends_on:
      - db
    ports:
      - "80:80"
    volumes:
      - ./wp_data:/var/www/html
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: your_db_password
      WORDPRESS_DB_NAME: wordpress

几个关键点:

  • db_datawp_data 是命根子,备份的核心对象就是这两个目录。
  • MySQL 容器和 WordPress 容器通过 Docker 内部网络通信,域名直接写容器名 db 就可以。
  • 如果要做 HTTPS,前面加一层 Nginx 反代,或者用 Traefik / Caddy,这不是本文重点。

备份到底要背什么

一个 WordPress 站点有两部分数据:

  1. 数据库:文章内容、用户、评论、插件配置……全部在 MySQL 里。
  2. 文件:wp-content/uploads 下的图片、附件、主题文件、插件文件。虽然主题和插件可以从源重新下载,但上传的附件是不可恢复的。

所以备份方案必须同时覆盖这两块。

Cloudflare R2 为什么适合做备份存储

选择存储位置的时候,对比了几种方案:

  • 同一台服务器的另一个磁盘:服务器挂了就全没了,等于没备份。
  • GitHub Releases / Git LFS:单个文件有大小限制,不适合数据库 dump。
  • S3 / 对象存储:主流选择,但 AWS S3 的出站流量要收费。
  • Cloudflare R2:兼容 S3 API,存储和出站流量都不收费(免流量费是 R2 最大的优势)。

R2 的免费档是 10GB 存储 + 每月 1000 万次操作,个人博客完全够用。超出的部分价格也很低,每 GB 每月 $0.015。

思路:一行脚本 + crontab + rclone

整个方案的链路很简单:

crontab 定时触发 → 脚本执行 mysqldump 导出数据库 → tar 打包数据库 + wp-content → rclone 上传到 R2 → 清理本地临时文件

全部工具都是开源的,没有额外成本。

第一步:安装 rclone 并配置 R2

rclone 是一个命令行同步工具,支持几乎所有对象存储。

# 安装 rclone
curl https://rclone.org/install.sh | sudo bash

配置 R2 需要先拿到 Access Key 和 Secret Key:

  1. 登录 Cloudflare Dashboard,进入 R2 页面。
  2. 点击「管理 R2 API 令牌」→「创建 API 令牌」。
  3. 权限选「对象读写」,指定一个 Bucket(比如 blog-backup)。
  4. 记下 Access Key ID 和 Secret Access Key。

然后配置 rclone:

rclone config

按照交互提示:

  • name: r2
  • type: s3
  • provider: Cloudflare
  • access_key_id: 填入你的 Access Key
  • secret_access_key: 填入你的 Secret Key
  • endpoint: https://<你的账户ID>.r2.cloudflarestorage.com
  • 其余选项默认即可

配置完成后测试一下:

rclone ls r2:blog-backup

第二步:写备份脚本

在服务器上创建一个目录:

mkdir -p /opt/backup

创建 /opt/backup/backup.sh

#!/bin/bash

# 配置变量
BACKUP_DIR="/tmp/wordpress-backup"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
SITE_NAME="myblog"
R2_BUCKET="blog-backup"
R2_CONF="r2"

# Docker 容器名
DB_CONTAINER="wordpress_db"
WP_VOLUME="/root/wordpress/wp_data"

# 数据库配置(从 docker-compose 的环境变量保持一致)
DB_NAME="wordpress"
DB_USER="wpuser"
DB_PASSWORD="your_db_password"

# 1. 创建临时目录
mkdir -p "$BACKUP_DIR/$TIMESTAMP"

# 2. 导出数据库
docker exec "$DB_CONTAINER" mysqldump -u"$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" > "$BACKUP_DIR/$TIMESTAMP/db.sql"

# 3. 压缩 wp-content(上传的文件主要在这里)
tar -czf "$BACKUP_DIR/$TIMESTAMP/wp-content.tar.gz" -C "$WP_VOLUME" wp-content/ 2>/dev/null

# 4. 生成一份简单的备份信息
echo "Backup time: $(date)" > "$BACKUP_DIR/$TIMESTAMP/backup-info.txt"
echo "Site: $SITE_NAME" >> "$BACKUP_DIR/$TIMESTAMP/backup-info.txt"
echo "DB size: $(du -sh "$BACKUP_DIR/$TIMESTAMP/db.sql" | cut -f1)" >> "$BACKUP_DIR/$TIMESTAMP/backup-info.txt"

# 5. 上传到 R2
rclone copy "$BACKUP_DIR/$TIMESTAMP" "$R2_CONF:$R2_BUCKET/$SITE_NAME/$TIMESTAMP" --progress

# 6. 清理临时文件
rm -rf "$BACKUP_DIR/$TIMESTAMP"

# 7. 可选:清理 R2 上 30 天前的旧备份,节省存储
rclone delete "$R2_CONF:$R2_BUCKET/$SITE_NAME" --min-age 30d --quiet 2>/dev/null

echo "Backup completed: $TIMESTAMP"

给脚本执行权限:

chmod +x /opt/backup/backup.sh

先手工跑一遍测试能不能用:

bash /opt/backup/backup.sh

如果一切正常,去 Cloudflare R2 的 Dashboard 应该能看到备份文件了。

第三步:配置 crontab 定时执行

crontab -e

加入以下行:

# 每天凌晨 3 点执行备份
0 3 * * * /opt/backup/backup.sh >> /var/log/wordpress-backup.log 2>&1

这样每天凌晨 3 点服务器会自动执行一次完整的备份。日志写入 /var/log/wordpress-backup.log,方便排查问题。

如果想调整频率:

  • 每 6 小时:0 */6 * * *
  • 每周日凌晨 4 点:0 4 * * 0
  • 每天早晚各一次:0 3,15 * * *

第四步:验证恢复流程

备份做得再好,如果恢复不了就是废的。建议至少在部署完成后手动验证一次:

# 从 R2 下载备份
rclone copy r2:blog-backup/myblog/20250315_030002 /tmp/restore-test/

# 恢复数据库
cat /tmp/restore-test/db.sql | docker exec -i wordpress_db mysql -uwpuser -pyour_db_password wordpress

# 恢复文件
tar -xzf /tmp/restore-test/wp-content.tar.gz -C /root/wordpress/wp_data/

验证完毕别忘了删掉测试目录。

容易踩坑的几个地方

mysqldump 在 Docker 环境中的路径

上面脚本里用的是 docker exec 直接在 MySQL 容器里执行 mysqldump,这样不用在宿主机装 MySQL 客户端,也不会出现版本不兼容的问题。如果你的 MySQL 不是用 Docker 跑的,改成宿主机路径即可。

数据库密码在脚本里明文

为了简洁,上面脚本直接把密码硬编码了。生产环境建议用环境变量或者用 .env 文件加载:

source /opt/backup/.env
docker exec "$DB_CONTAINER" mysqldump -u"$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" > backup.sql

然后把 .env 的权限设为 600:

chmod 600 /opt/backup/.env

备份文件越来越大怎么办

R2 存储价格很低,但如果你每天备份、保留所有版本,还是会上涨的。脚本里已经加了一句 --min-age 30d 自动清理 30 天前的备份。你也可以改成 14d7d,看你自己的需求。

R2 的 endpoint 地址

Cloudflare R2 的 endpoint 格式固定:https://<account-id>.r2.cloudflarestorage.com。account ID 在 Cloudflare Dashboard 的右侧边栏可以找到,写的是「账户 ID」。

最终的备份目录结构

在 R2 里备份的目录结构大概是这样:

blog-backup/
  myblog/
    20250315_030002/
      db.sql
      wp-content.tar.gz
      backup-info.txt
    20250316_030002/
      db.sql
      wp-content.tar.gz
      backup-info.txt

每次备份是一个独立的时间戳目录,包含三个文件。如果需要回滚到某一天,直接下载对应的目录就行。

总结

用 Docker 部署 WordPress 本身不难,麻烦的是后续的维护。备份这件事,早做比晚做好,自动做比手动做好。整套方案用到的工具(Docker、mysqldump、rclone、crontab)都是免费且成熟的,Cloudflare R2 在前 10GB 以内也是免费的。对个人博客来说,这是成本最低、可靠性也足够高的方案。

另外提一句:上面这个备份脚本是针对单机运行的 WordPress 做的。如果你的站点用到了 Redis 缓存、Elasticsearch 或者外部 CDN,需要根据情况调整备份范围。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注