ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Docker容器中WordPress定时备份与恢复全攻略

Docker容器中WordPress定时备份与恢复全攻略 我一开始是在宿主机上直接装 WordPress后来嫌 PHP 环境折腾人干脆全部迁移到 Docker 容器里跑。容器化之后环境是清爽了但备份这件事一度让我非常头疼——以前在物理机上我习惯用宝塔面板或者写个 crontab 脚本直接打包目录和数据库换成容器之后数据都待在容器里不把卷映射出来、不做定时任务一旦容器挂了或者宿主机磁盘坏了真的就是欲哭无泪。这篇文章我就专门聊聊“容器启动的 WordPress 之定时备份与恢复”这件事。我会从整体设计思路说起讲清楚怎么规划数据目录、怎么写出靠谱的备份脚本、怎么用 cron 实现定时调度然后重点讲讲恢复的操作流程——尤其是当你把备份文件拿到一台全新的机器上怎么一步步把 WordPress 站点和数据库恢复出来。最后是我踩过的坑和排查经验希望能帮你少走弯路。适用对象用 Docker 或 docker compose 跑 WordPress 的个人站长、外包开发者、小团队运维。默认你有一台 Linux 服务器Ubuntu/Debian/CentOS 都行已经装好 Docker。1. 容器化 WordPress 的备份难点与整体设计思路先说一个很多人忽略的事实容器本身是无状态的容器里的任何数据都随着容器删除而消失。所以在设计备份方案之前第一步必须从架构层面解决“数据要不要留在容器内”的问题。1.1 为什么容器里的数据不能直接“打包了事”我见过不少新手这么干把容器跑起来后用docker cp把/var/www/html整个目录复制出来再配合 mysqldump 导出 SQL。这种方式确实能备份但问题很明显依赖手动操作没法自动化。docker cp只能备份文件系统数据库如果正在写入直接复制数据文件可能不一致甚至损坏。备份周期完全靠人记一旦忘记等于没备份。所以我的核心思路是把 WordPress 源码和 MySQL 数据都用卷volume或目录挂载的方式映射到宿主机然后定时任务直接操作宿主机上的文件再把备份产物传到异地比如另一台服务器或对象存储。这样设计的好处是显而易见的。MySQL 的数据文件放在宿主机目录里我可以随时用xtrabackup或mysqldump做物理/逻辑备份WordPress 的 wp-content 目录挂载出来之后即使容器全部删掉重建数据依然在宿主机上完全不慌。1.2 设计目标自动、可靠、可恢复备份方案不是“能导出一份文件”就行我的设计目标写在这里你也可以对照自己的需求自动化完全不需要人工介入cron 定时执行脚本。一致性数据库必须是在线备份且保证导出过程不影响站点访问或者短暂锁表可以接受。可恢复性任何时候拿备份文件能在 10 分钟内恢复到一个新容器环境里。版本管理保留最近 N 份备份避免磁盘被撑爆同时防止备份文件本身损坏导致没有可用版本。异地容灾除了宿主机本地最好同步一份到异地或对象存储防止服务器硬件故障。我实际经常遇到的情况是很多人备份脚本写好后从不测试恢复结果真出事时才发现备份是坏的。所以“可恢复性”这个目标必须通过定期演练来验证而不是写完了事。1.3 整体方案架构一览我的标准做法是docker compose 管理三个服务——wordpress应用容器、mysql数据库容器再加上一个backup服务可选用来跑备份脚本的专用容器。但更简单粗暴的方式是直接在宿主机上放一个 shell 脚本然后在 cron 里调用它。两种方案各有优劣。用专用容器跑备份的好处是环境隔离、不依赖宿主机有没有装 mysqldump缺点是增加了编排复杂度。我个人的建议是如果宿主机已经装了 mysql-client直接用宿主机脚本最省事如果不想污染宿主机环境就建一个备份容器。下面我会把两种方式都讲清楚。2. 备份脚本设计文件与数据库分离处理备份的核心其实就两块WordPress 程序文件以 wp-content 为主和 MySQL 数据库。这两者必须同时备份并且要保持时间点一致否则恢复出来可能出现文件是新的、数据库是旧的这种错乱。2.1 WordPress 文件备份的取舍WordPress 源码目录/var/www/html里核心文件wp-admin、wp-includes、根目录下的 php 文件其实都是官方原版只要版本号固定这些文件可以从 wordpress.org 重新下载。真正必须备份的是wp-content/uploads上传的图片、附件。wp-content/themes自定义主题如果你用的全是官方主题并固定版本也可以不备但上传目录一定要备。wp-content/plugins插件同理固定版本可重装但为了快速恢复还是备上。wp-config.php数据库连接配置和密钥这个必须备不然恢复后连不上数据库。可能还有.htaccess如果你用了 Apache 或者自定义了伪静态规则。很多老手为了省事儿直接把整个 html 目录全部打包。也不是不行只是每次都打几十上百 MB 的包浪费磁盘和时间。我的做法是用 tar 排除掉代码目录只打包 uploads 和配置文件再用 composer 或直接源码包方式恢复程序文件。不过这里有一个很重要的经验如果你不确定哪些文件被改过最稳妥的做法还是全量打包整个 html 目录。备份体积多几百 MB 不算什么但漏掉一个关键文件可能让你恢复时多花几个小时。2.2 MySQL 备份选择 mysqldump 还是物理备份MySQL 的备份方式分成逻辑备份和物理备份两大类。mysqldump是逻辑备份导出的是 SQL 语句。特点是文件体积小、可跨版本恢复、可以指定库表。对于 WordPress 这种小型站点通常几十 MB 到几个 GBmysqldump 完全够用。物理备份比如 Percona XtraBackup直接复制数据文件。优点是备份和恢复速度快适合几十 GB 以上的大库缺点是需要安装额外工具而且恢复时对目标实例的版本要求比较严格。WordPress 站点绝大多数是中小型数据库所以我默认直接用 mysqldump。使用方式是在宿主机上调用docker exec进入容器或者直接用 mysql 客户端连接容器的 3306 端口。命令大概长这样docker exec mysql容器名 sh -c \ exec mysqldump --single-transaction --quick --lock-tablesfalse \ -u$MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE backup.sql这里我强依赖容器环境变量来传数据库账号密码而不是硬编码在命令里这样脚本更通用、也更安全。--single-transaction参数很重要它能让 mysqldump 在 InnoDB 表上做一致性快照备份过程中不会长时间锁表。如果你的表用了 MyISAM那就要注意锁表问题备份期间站点可能会有短暂写入阻塞。2.3 一个完整的定时备份脚本示例下面这个脚本是我目前线上在用的精简版本完全基于宿主机 docker compose 环境注释写得比较全你也可以直接改一改拿去用。#!/usr/bin/env bash # wordpress_backup.sh # 功能备份 WordPress 容器挂载目录 MySQL 数据库 # 依赖宿主机安装 mysql-clientdocker 可正常执行 set -euo pipefail # ---------- 基础配置 ---------- BACKUP_DIR/data/backup/wordpress # 备份保存目录 KEEP_DAYS7 # 本地保留天数 WORDPRESS_ROOT/data/www/wordpress # WordPress 程序挂载目录 WORDPRESS_CONTAINERwp-wordpress-1 # wordpress 容器名 MYSQL_CONTAINERwp-mysql-1 # mysql 容器名 MYSQL_HOST127.0.0.1 MYSQL_PORT3306 MYSQL_USERwp_user MYSQL_PASSWORDyour_mysql_password # 建议改成从环境变量读取 MYSQL_DATABASEwordpress # ---------- 生成时间戳 ---------- DATE_TAG$(date %Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR/$DATE_TAG # ---------- 1. 备份 WordPress 文件 ---------- echo 开始备份 WordPress 文件... tar -czf $BACKUP_DIR/$DATE_TAG/wordpress_files.tar.gz \ -C $WORDPRESS_ROOT \ --excludewp-content/cache \ --excludewp-content/upgrade \ . # ---------- 2. 备份 MySQL 数据库 ---------- echo 开始备份 MySQL 数据库... mysqldump \ --host$MYSQL_HOST \ --port$MYSQL_PORT \ --user$MYSQL_USER \ --password$MYSQL_PASSWORD \ --single-transaction \ --quick \ --lock-tablesfalse \ $MYSQL_DATABASE $BACKUP_DIR/$DATE_TAG/wordpress_db.sql # ---------- 3. 压缩数据库备份可选实测 sql 文件压缩率很高 ---------- gzip $BACKUP_DIR/$DATE_TAG/wordpress_db.sql # ---------- 4. 清理过期备份 ---------- echo 清理 $KEEP_DAYS 天前的备份... find $BACKUP_DIR -type d -mtime $KEEP_DAYS -exec rm -rf {} \; # ---------- 5. 可选同步到异地或对象存储 ---------- # rclone copy $BACKUP_DIR/$DATE_TAG remote:backup/wordpress --progress echo 备份完成$BACKUP_DIR/$DATE_TAG你需要根据自己的 docker compose 项目名来改容器名。如果你用的是 docker compose 默认命名容器名通常就是“项目名-服务名-序号”。可以执行docker ps查看实际容器名然后在脚本里替换。脚本用set -euo pipefail的好处是任何一条命令失败都会让脚本立即退出避免“以为备份成功、实际导出失败”的情况。3. 定时调度从 cron 到 systemd timer备份脚本写好后怎么让它定时跑起来Linux 下最传统也是最稳的方案就是 crontab。但我要多说一句容器环境下cron 进程要在宿主机上跑不要塞进容器里除非你用的是专门跑 cron 的容器。3.1 宿主机 crontab 配置编辑 root 用户的 crontab因为备份目录通常是 root 权限crontab -e加入一行# 每天凌晨 3:30 执行备份 30 3 * * * /usr/local/bin/wordpress_backup.sh /var/log/wordpress_backup.log 21注意几点尽量把脚本放在固定路径比如/usr/local/bin/并且给脚本加执行权限。日志重定向一定要有不然备份失败了你都不知道。备份时间选凌晨低峰期避免影响业务同时尽量避开数据库自身的定时任务。写完 crontab 后可以用systemctl status cron确认 cron 服务在跑。3.2 为什么我不建议在容器里放 cron有人可能会问既然 WordPress 都在容器里了能不能在每个容器里直接装个 cron 来执行备份原则上可以但我不推荐原因有三容器是短暂的重建后 cron 任务就没了你还要在 Dockerfile 里维护。备份任务需要访问多个容器WP 目录 MySQL放宿主机上反而更容易协调。日志和排错更简单备份脚本产生的问题直接在宿主机上看不用进容器里翻。所以我的经验是备份调度放在宿主机备份的数据来源靠卷挂载。这是容器化环境里最朴素也最稳的实践。3.3 进阶用 systemd timer 替代 cron可选如果你用的是较新的 Linux 发行版可以考虑用 systemd timer。它比 cron 多出“错过执行时间自动补跑”“随机延迟”等能力对备份这类任务特别友好。简单示例# /etc/systemd/system/wordpress-backup.service [Unit] DescriptionWordPress backup [Service] Typeoneshot ExecStart/usr/local/bin/wordpress_backup.sh# /etc/systemd/system/wordpress-backup.timer [Unit] DescriptionRun WordPress backup daily at 3:30 [Timer] OnCalendar*-*-* 03:30:00 Persistenttrue [Install] WantedBytimers.target然后执行systemctl daemon-reload systemctl enable --now wordpress-backup.timer systemctl list-timers wordpress-backup.timerPersistenttrue的意思是如果服务器在设定时间点关机了下次开机后会自动补跑错过的备份。这一点对个人服务器来说非常实用。4. 恢复操作全流程从备份文件到新容器恢复是备份方案里最需要“演练”的部分。很多人会在站点挂了十几天之后才想起恢复结果发现完全不知道备份文件该怎么用。下面我把恢复流程拆成四个阶段每一步都讲清楚。4.1 阶段一拉取并启动新的容器环境我恢复的时候一般是把原来的 docker-compose.yml 拿过来只改掉数据目录指向保证不影响当前环境如果当前环境还是好的我不想覆盖它。假设你的备份文件在/data/backup/wordpress/20250401_033001/可以这样mkdir -p /data/recover/wordpress mkdir -p /data/recover/mysql然后新增一个 compose 文件# docker-compose-recover.yml version: 3.8 services: db: image: mysql:8.0 container_name: recover-db restart: always environment: MYSQL_ROOT_PASSWORD: rootpwd MYSQL_DATABASE: wordpress MYSQL_USER: wp_user MYSQL_PASSWORD: wp_password volumes: - /data/recover/mysql:/var/lib/mysql networks: - recover-net wordpress: image: wordpress:6.5-php8.2-apache container_name: recover-wp restart: always depends_on: - db ports: - 8088:80 environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wp_user WORDPRESS_DB_PASSWORD: wp_password WORDPRESS_DB_NAME: wordpress volumes: - /data/recover/wordpress:/var/www/html networks: - recover-net networks: recover-net: driver: bridge先不要急着启动 wordpress我们要先恢复数据然后再启动容器避免 WordPress 安装向导覆盖掉你恢复的文件。4.2 阶段二恢复 WordPress 文件并修复权限接下来把备份的wordpress_files.tar.gz解压到恢复目录cd /data/recover/wordpress tar -xzf /data/backup/wordpress/20250401_033001/wordpress_files.tar.gz --strip-components1--strip-components1是干嘛的这个取决于打包时指定的-C目录和路径结构。我的脚本里tar -czf ... -C /data/www/wordpress .是在 html 目录里打当前目录的内容所以解压到新目录时不需要--strip-components。如果你打包命令里带了完整路径比如/var/www/html/...解压时可能就需要 strip 掉第一层目录。建议先tar -tzf看下包内路径结构再决定。解压之后需要关注wp-config.php。这个文件在备份包里但里面的主机名、用户、密码都是旧环境的。需要修改define( DB_NAME, wordpress ); define( DB_USER, wp_user ); define( DB_PASSWORD, wp_password ); define( DB_HOST, db:3306 );这里 DB_HOST 用 compose 服务名db就行Docker 内部网络会自动解析。然后是权限问题。容器内的 Apache 进程跑在 www-data 用户下如果文件属主是宿主机用户可能导致容器写不了缓存目录。chown -R 33:33 /data/recover/wordpressUID 33 就是 Debian/Ubuntu 镜像里的 www-data 用户。这一步看起来不起眼但漏掉它恢复后十有八九会出现“上传文件失败”或“无法创建目录”的错误。4.3 阶段三恢复 MySQL 数据库启动一个临时 MySQL 容器先把数据库导进去。这里有个很关键的细节千万不要在 WordPress 容器起来之后再导数据库。WordPress 一旦发现有可用的数据库连接就可能初始化新的数据结构污染你正在恢复的数据。正确做法是先只启动数据库容器导入 SQL再启动 WordPress。docker compose -f docker-compose-recover.yml up -d db等待数据库就绪docker exec recover-db sh -c exec mysqladmin ping -uroot -prootpwd --silent然后导入备份的 SQLgunzip -c /data/backup/wordpress/20250401_033001/wordpress_db.sql.gz | \ docker exec -i recover-db sh -c exec mysql -uroot -prootpwd wordpress导入过程中如果发现报错多半是因为备份的 SQL 里包含了建库语句而目标库里已经有同名数据库存在。可以把 SQL 里的CREATE DATABASE和USE语句手动去掉或者用mysql -e DROP DATABASE wordpress;清掉重来。个人建议恢复时保持目标库干净避免残留表结构。4.4 阶段四启动 WordPress 容器并验证数据库就绪后再把 WordPress 服务也启动docker compose -f docker-compose-recover.yml up -d然后访问http://服务器IP:8088看看站点是否能正常打开、图片是否加载、后台能否登录。验证的时候别只看首页能打开就完事还要测一遍文章详情页是否正常伪静态是否生效。后台登录是否正常有缓存的话会不会报错。上传一张图片测试文件权限。检查 wp-content/uploads 下的图片是否都能访问。如果站点能正常打开但后台登录报数据库表不存在的错多半是导入的 SQL 不全或者导入时选了错误的数据库。5. 常见问题与排查技巧实录备份和恢复这种事平时用不上真用上就是救命的场景。我把自己踩过的一些坑整理成速查表你在实操中遇到类似问题时可以直接对照。问题可能原因排查思路与解决办法cron 定时任务没执行cron 服务没启动 / 脚本路径不对 / 文件没有执行权限先手动跑一遍脚本确认能成功再用journalctl -u cron看日志检查脚本是否有chmod xmysqldump 报Access denied数据库账号权限不足WordPress 容器默认创建的用户只有单库权限建议用 root 或单独建一个BACKUP专用账号并授予SELECT, LOCK TABLES, SHOW VIEW, TRIGGER备份文件体积过大撑爆磁盘未做容量规划备份保留时间过长调整KEEP_DAYS对wp-content/cache做 exclude考虑加--databases只导自己库而不是全部实例恢复后站点打不开超时容器端口暴露冲突 / WordPress 无法连接数据库检查docker ps端口映射查看 wordpress 容器日志docker logs recover-wp重点看数据库连接报错恢复后图片全部是裂图数据库中的站点 URL 还是旧地址在wp_options表里改siteurl和home或者在 wp-config.php 中临时定义WP_HOME和WP_SITEURL强制覆盖恢复后上传文件提示失败目录权限不对 / wp-content/uploads 缺失检查chown -R 33:33是否执行确认 uploads 目录确实从备份包恢复了备份 SQL 文件为空或者只有注释备份命令执行时环境变量为空 / 容器名字不对手动跑 mysqldump 命令观察是否报错容器里 echo 一下环境变量确认账号密码对得上5.1 恢复后站点 URL 不对的快速处理这是恢复场景里最高频的问题。你备份的时候域名是blog.example.com恢复之后变成http://IP:8088结果站内所有链接还是老域名。这很正常不要把 wp_options 表改到一半就慌。我一般的做法是先在命令行里直接改数据库把 siteurl 和 home 都换成新地址UPDATE wp_options SET option_valuehttp://IP:8088 WHERE option_namesiteurl; UPDATE wp_options SET option_valuehttp://IP:8088 WHERE option_namehome;如果主题、插件把老域名写死到代码里可能还要同步搜索替换整个数据库里的老域名。用 WP-CLI 的wp search-replace可以一步到位docker exec recover-wp sh -c wp search-replace http://blog.example.com http://IP:8088 --all-tables --precise这个命令会把所有表里出现在老域名改为新域名文章内容里的图片链接也会一起改。不过操作前一定要再导一份 SQL 出来防止改错回不去。5.2 演练每月一次“放火测试”比什么都强关于备份恢复我最想说的一点是备份方案不经过演练等于没做。我见过太多例子——定期备份执行得欢真到宕机那天发现某个备份文件是坏的、或者关键表没导全、或者脚本某天开始就没跑成功过但大家都没看日志。所以我给自己定了个规矩每个月挑一天把最新一份备份拿到一台临时服务器或本机 Docker 环境里完整恢复一遍。步骤就是我上面写的整个流程熟练之后 20 分钟就能跑完一遍。花不了多少时间但真的能救命。6. 补充异地容灾与备份文件管理本地备份只是第一道防线。如果宿主机硬盘直接损坏哪怕你做了定时备份数据也随磁盘一起没了。所以异地容灾和备份文件的生命周期管理值得单独说一说。6.1 备份留存策略定时清理 冷热分离在脚本里我用了find -mtime 7 -exec rm -rf来清理 7 天前的本地备份。这是一种很常见的“滚动删除”策略能保证磁盘不会无限增长。还可以进一步优化按天保留最近 7 天备份按周保留最近 4 周的周日备份按月保留最近几个月的月初备份。实现方式也不复杂比如周日备份的文件名加个weekly后缀清理时对不同前缀用不同保留时间。我自己的常用策略是本地保留 7 天、对象存储保留 30 天、再远端冷备 3 个月。分段留存的好处是既能快速回滚到最近任意一天又能防止早期数据被过期策略误删。6.2 用 rclone 定时同步到对象存储或另一台服务器rclone 是一个命令行工具可以把本地目录同步到各类对象存储、SFTP、网盘等。在我前面的备份脚本里加了一行注释实际使用的话把最后的选项去掉即可rclone copy $BACKUP_DIR/$DATE_TAG my-s3-bucket:wordpress-backup --progressrclone 的上传速度还不错而且支持增量同步和断点续传。需要注意的一点是先传小文件、再传大文件或多个副本的情况下恢复时要保证文件的完整性建议上传完后用rclone check校验一遍。另外如果公司有另一台内网服务器也可以用 rsync 直接同步rsync -avz --delete $BACKUP_DIR/ userbackup-server:/data/backup/wordpress/这样即使主服务器挂了备机上也有最新的完整备份。6.3 加密备份文件可选备份文件里如果包含数据库账号密码、用户邮箱等信息直接传到第三方对象存储时还是存在一定风险。如果在意这一点可以用gpg对称加密备份包gpg --batch --yes --passphrase-file /root/.backup_passphrase \ --symmetric --cipher-algo AES256 \ $BACKUP_DIR/$DATE_TAG/wordpress_db.sql.gz恢复时再解密gpg --batch --yes --passphrase-file /root/.backup_passphrase \ --decrypt wordpress_db.sql.gz.gpg | gzip -dc | docker exec -i recover-db mysql -uroot -prootpwd wordpress加密会增加一点点操作复杂度但对带敏感信息的备份文件来说我觉得这笔“安全税”值得交。7. 关于容器备份的最后一课我在大量备份恢复实践中最深的一个感悟是备份的本质不是“制造文件”而是“保证能恢复”。这句话听起来很平淡但真的执行起来你会发现难点往往不在技术上而在习惯上——你是不是每天看了日志确认备份成功了你是不是定期做了恢复演练你是不是给备份文件留了异地副本容器化给 WordPress 带来了部署上的便利也让备份这件事的边界变得更清晰程序文件、上传目录、数据库三者分工明确只要挂载目录规划合理备份和恢复的脚本写起来并不复杂。真正复杂的从来不是技术而是在故障发生之前你有没有把每一个环节都验证过一遍。如果这篇内容对你有帮助建议你现在就做两件事第一去服务器上把备份脚本跑一遍看看产物是否正常第二找一台临时机器把这份最新备份完整恢复一次确认站点能打开、图片能加载、后台能登录。这两步做完你就不会再为容器的数据安全睡不着觉了。
返回列表