Docker部署WordPress:docker-compose.yml配置Nginx+PHP+MySQL完整代码
想自建 WordPress,docker-compose 编排 Nginx+PHP+MySQL 比手工装 LNMP 更省事,代价是多一层网络与数据卷的抽象。下面用对比的方式说清编排文件的关键字段与常见坑。
先比整体:Compose 编排与手工 LNMP 的取舍
手工 LNMP 指用系统包管理器装 Nginx、PHP-FPM、MySQL,配置散落在 /etc/nginx、/etc/php、/etc/mysql 这些目录里。它的优点是性能路径最短,能和 systemd、系统日志、现成监控直接对接;缺点是换一台机器就得把这些步骤重走一遍,机器一多,环境版本很容易走样。
Compose 编排是另一条路:把 Nginx、PHP-FPM、MySQL 分别装进容器,用一个 YAML 文件描述端口、目录、网络和启动顺序。新机器上装好 Docker 与 Compose 插件,进入项目目录执行:
docker compose up -d
三个服务就一起起来了,配置随文件走,复制环境只要复制目录。代价是要多一层容器网络转发,绑定挂载在读写大量小文件时有额外开销,排错入口也从系统日志换成了 docker compose logs。
怎么选:单机跑几个 WordPress、希望"配置即代码"、经常换机器或要开测试副本,选 Compose;对磁盘 I/O 极敏感、已经有成熟的自动化运维体系,或者服务器上还跑着不方便容器化的老服务,手工 LNMP 更顺手。两者也能混用,比如外层用系统装的 Nginx 做反代,后面接容器里的一整套 WordPress。
目录结构:一体化镜像与分离镜像的对比
一体化写法直接用官方 wordpress 镜像,它自带 web server,再配一个数据库服务就能跑通,YAML 很短。问题是 Nginx 层被跳过了,缓存策略、伪静态细节、HTTPS 配置都不好插手。
分离写法用 nginx + php-fpm + mysql 三个服务,目录大致长这样:
docker-compose.yml:编排文件本体nginx/conf.d/default.conf:站点配置,挂进 Nginx 容器html/:WordPress 根目录,同时挂给 Nginx 和 PHP 容器mysql-data/:数据库文件,或者改用命名卷php/:需要自定义镜像时才建,用来装扩展
对比的核心在挂载方式。用绑定挂载把 ./html:/var/www/html 同时映射进 Nginx 与 PHP,改主题、传插件直接落盘,备份就是复制目录;换成命名卷,路径交给 Docker 管,跨系统权限问题少一些,但想看文件得先进容器或 docker compose cp。数据库目录同理:绑定挂载方便冷备,命名卷更适合完全交给 Docker 维护。
docker-compose.yml 里必须写清的字段
骨架如下。镜像 tag 请先去 Docker Hub 看当前的稳定版本再填,不要照抄旧文章里的版本号:
services:
nginx:
image: nginx:stable
ports:
- "80:80"
volumes:
- ./html:/var/www/html
- ./nginx/conf.d:/etc/nginx/conf.d
depends_on:
- php
restart: unless-stopped
networks: [wpnet]
php:
image: php:8.2-fpm
volumes:
- ./html:/var/www/html
networks: [wpnet]
mysql:
image: mysql:8
volumes:
- ./mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: 换成强密码
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: 再换一个
restart: unless-stopped
networks: [wpnet]
networks:
wpnet:
几个容易被忽略的对比点:
端口映射这一行,宿主机 80 已经被占用就写 8080:80,外层再拿系统 Nginx 反代过去,不要硬抢端口。
depends_on 只保证启动顺序,不代表 MySQL 已经能接受连接。手工装的时候这个问题靠 systemd 的 After 同样解决不了,正确做法是给 MySQL 配 healthcheck,同时让 WordPress 侧容忍重试。
MySQL 的 MYSQL_* 环境变量只在数据目录为空时生效。改了 YAML 再重启,已有密码不会跟着变,这和裸机上执行 ALTER USER 完全是两件事,是新手最常卡住的地方。
restart: unless-stopped 对应裸机上的 systemd 开机自启,机器重启后容器会被拉起来。
Nginx 转发给 PHP:地址写错是主要故障源
容器里 Nginx 和 PHP-FPM 是两个独立的网络命名空间,fastcgi_pass 127.0.0.1:9000 在 Nginx 容器里找不到 PHP,必须写服务名 php:9000,Compose 自带的服务发现会把服务名解析成容器 IP。同理,root 目录要和 PHP 容器的挂载点保持一致,否则不是 404 就是 "File not found"。
站点配置可以这样写:
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.php index.html;
client_max_body_size 64m;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass php:9000;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
fastcgi_read_timeout 300;
}
}
改完配置不用整栈重启,执行 docker compose exec nginx nginx -s reload 就生效。伪静态仍然走 try_files 那一套,和裸机 Nginx 没有区别,出问题时先确认容器名和服务名没写错。
PHP 扩展与 MySQL 参数:容器里的做法不同
裸机装扩展是 apt install php-mysql 这类包管理命令,容器里则要自己构建镜像:
FROM php:8.2-fpm
RUN docker-php-ext-install pdo_mysql mysqli
把上面的 Dockerfile 放进 php/ 目录,compose 里把 image 换成 build: ./php 即可。多这一步换来的是环境可复制;如果用的是自带 web server 的官方 wordpress 镜像,扩展已经打包好了,反而不用折腾。
MySQL 侧建议在 command 里显式指定字符集,例如加 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci,避免中文内容出现乱码。数据库端口默认只在内部网络可见,生产环境不必把 3306 发布到宿主机;需要连就 docker compose exec mysql mysql -u root -p,或临时走 SSH 隧道。
在 wp-config.php 里,DB_HOST 要填服务名 mysql。填 localhost 会尝试走 socket 连接,在容器里必然失败,这是从手工 LNMP 迁移过来时最常见的改写点。
排错、备份与迁移:两条路线各动哪里
排错先看状态和日志:
docker compose ps
docker compose logs -f php
docker compose exec php bash
常见故障集中在三处。一是绑定挂载目录的属主不对,php-fpm 以 www-data(uid 33)运行,写不了上传目录,需要 chown -R 33:33 html,对比裸机则是改 /var/www 的属主。二是宿主机端口冲突,容器起不来或起了但访问不到。三是云服务器安全组没放行 80、443,容器日志一切正常,外面却打不开。
备份上,容器路线是 docker compose exec mysql mysqldump -u root -p wordpress > backup.sql 再打包 html 目录;裸机路线是 mysqldump 加 rsync。入口不同,产物一样。
迁移时把编排文件、Nginx 配置、html 和数据库目录整体复制到新机,docker compose up -d 就完成切换。要留意 docker compose down -v 会连数据卷一起删掉,没有备份不要执行。换域名之后,还得改数据库里的 siteurl 和 home,用 wp-cli 的 wp search-replace 批量替换更稳妥,改完再检查一遍解析是否已经指向新机器。