☰
Docker Compose 部署 WordPress 生产级实战指南
2026/10/13 11:27:25 网站建设 项目流程

1. 项目概述:为什么现在还要手装 Docker Compose 部署 WordPress?

最近在帮某高校实验室搭建一个轻量级技术文档与成果展示平台,需求很明确:不依赖运维团队、不碰服务器底层配置、一周内上线、后续能由非开发背景的助教自主维护。我第一时间排除了传统 LAMP 手动部署——光是 PHP 版本兼容性、MySQL 权限初始化、Nginx rewrite 规则调试这三关,就足够让一位刚接触 Linux 的研究生卡住三天。也否掉了 SaaS 博客平台,因为所有内容需完全私有化存储、支持自定义主题开发、且要预留未来接入内部认证系统的接口。

这时候 Docker Compose 就成了唯一解:它不是“另一个容器工具”,而是把 WordPress 生态里最顽固的耦合关系——Web 服务、数据库、缓存、反向代理——用一份 YAML 文件声明清楚,让整套环境变成可版本控制、可一键重建、可跨机器迁移的“乐高积木”。你不需要记住systemctl restart mysql还是service mysqld reload,也不用查 Apache 的.htaccess和 Nginx 的location ~ \.php$哪个优先级更高。你只需要改几行缩进对齐的 YAML,docker-compose up -d一敲,服务就跑起来了。

这个项目标题看似平平无奇,但背后藏着三个被大量教程忽略的关键现实:第一,Docker 官方早在 2023 年底已将 Compose v2 设为默认,而绝大多数中文教程还在教pip install docker-compose(那是 v1,早已归档);第二,WordPress 官方镜像虽开箱即用,但默认配置在生产环境会触发致命的“时区错乱+文件权限拒绝+插件更新失败”三连击;第三,所谓“一键部署”,90% 的失败案例都出在宿主机的ulimit限制、SELinux 策略或/etc/hosts解析顺序这些“看不见的墙”上——它们不会报错,只会让 WordPress 后台白屏、上传图片失败、甚至数据库连接超时却显示“连接成功”。

所以这篇不是教你复制粘贴命令,而是带你亲手拆开这个“黑盒子”:从 Compose 二进制文件如何真正加载到内核模块,到 WordPress 容器里www-data用户为何必须拥有宿主机目录的gid=1001权限,再到wp-config.php里那行define('FS_METHOD', 'direct');背后牵扯的 Linux capability 机制。如果你正打算用 Docker 搭建个人博客、团队知识库,或者只是想搞懂为什么别人发的docker-compose.yml在你机器上就是起不来——那你需要的不是又一个安装步骤罗列,而是一份能让你在终端里“看见”整个数据流的实操地图。

2. 核心设计思路:为什么放弃一键脚本,坚持手动安装与分层编排?

2.1 Compose 安装方式的选择逻辑:二进制直装 vs 包管理器 vs pip

很多人看到“安装 Docker Compose”,第一反应是sudo apt install docker-compose或pip3 install docker-compose。这两种方式在实操中已被我彻底淘汰,原因非常具体:

  • APT/YUM 包管理器安装:以 Ubuntu 22.04 为例,官方仓库提供的docker-compose版本长期停留在 v1.29.x(2021 年发布),而当前稳定版已是 v2.25.x。v1 和 v2 的根本差异在于——v1 是 Python 编写的独立 CLI 工具,v2 是用 Go 编写的 Docker CLI 插件,二者命令语法虽兼容,但底层调用链完全不同。v2 直接复用docker二进制的 socket 通信机制,启动速度提升 3 倍以上;更重要的是,v2 支持docker compose ls --format json这类结构化输出,这对后续做自动化健康检查至关重要。而 APT 安装的旧版,执行docker compose version会直接报错unknown command "compose"。

  • pip 安装:这是最危险的选项。pip install docker-compose默认安装的是docker-composePyPI 包(v1),而非docker-compose-plugin(v2)。更致命的是,它会强制安装特定版本的docker-py库,而该库与新版本 Docker daemon 的 API 不兼容,导致docker compose up时出现Client sent an HTTP request to an HTTPS server这类看似网络错误、实为协议握手失败的诡异问题。

我最终采用的是官方推荐的二进制直装法,步骤如下:

# 1. 下载最新稳定版二进制(注意:URL 中的 v2.25.0 需替换为实时最新版) sudo curl -L "https://github.com/docker/compose/releases/download/v2.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # 2. 添加可执行权限 sudo chmod +x /usr/local/bin/docker-compose # 3. 创建符号链接(关键!很多教程漏掉这步) sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

提示:为什么必须加ln -s?因为 Docker CLI 插件机制要求docker-compose命令必须能被docker主程序在$PATH中直接找到。/usr/local/bin虽在$PATH中,但某些发行版(如 CentOS Stream)的docker二进制会优先搜索/usr/bin。不加软链,docker compose子命令会提示command not found,而单独执行docker-compose却能成功——这种“半生效”状态正是排查中最耗时的陷阱。

2.2 WordPress 部署架构的分层决策:为什么不用单容器镜像?

WordPress 官方 Docker Hub 上有wordpress:latest镜像,它把 Apache、PHP、WordPress 核心全打包进一个容器。初看很省事,但我在某公司内部知识库项目中吃过亏:当需要升级 PHP 扩展(比如加redis.so)时,必须重新构建整个镜像;当 MySQL 出现慢查询,监控工具无法区分是 WordPress 应用层还是数据库层的问题;最麻烦的是备份——你得同时docker exec进容器导出数据库,再docker cp复制wp-content目录,两步操作不同步,极易产生数据不一致。

因此本项目采用标准的三层分离架构:

  • db 层:使用mysql:8.0官方镜像,数据卷挂载到宿主机/opt/wordpress/db,确保数据库文件物理隔离;
  • app 层:使用wordpress:php8.2-apache镜像,但通过volumes挂载自定义wp-config.php和wp-content目录,实现配置与代码分离;
  • proxy 层:额外增加nginx:alpine容器,负责 SSL 终止、静态资源缓存、请求路由,避免 WordPress 容器直接暴露在公网。

这个设计带来三个硬性收益:

  1. 故障隔离:MySQL 宕机时,Nginx 仍可返回 503 页面并缓存静态资源,用户看到的是友好的维护页,而非 WordPress 的 PHP Fatal Error 白屏;
  2. 弹性伸缩:未来流量增长时,可单独扩缩wordpress容器实例数(配合 Nginx upstream),而数据库层保持不变;
  3. 安全加固:Nginx 容器以nobody用户运行,WordPress 容器以www-data用户运行,两者均无 root 权限,即使某个容器被攻破,攻击者也无法穿透到宿主机或其他容器。

2.3 网络与存储策略:bridge 网络 vs 自定义网络,命名卷 vs 绑定挂载

Docker 默认的bridge网络存在两个隐蔽缺陷:一是容器间 DNS 解析依赖/etc/hosts静态映射,当服务重启 IP 变更时,WordPress 容器可能因缓存旧 IP 而连不上 MySQL;二是默认网桥不支持--ip固定分配,导致某些需要固定 IP 的插件(如邮件 SMTP 配置)失效。

我们创建自定义 bridge 网络:

networks: wordpress-net: driver: bridge ipam: config: - subnet: 172.20.0.0/16

这个子网段刻意避开常用网段(如172.17.0.0/16是 Docker 默认,192.168.0.0/16易与宿主机局域网冲突),确保网络拓扑清晰可追溯。

存储方面,坚决不用docker volume create创建的匿名卷——它生成的随机名称(如a1b2c3d4...)在docker-compose.yml中无法显式引用,导致docker-compose down -v时误删数据。我们全部采用命名卷 + 绑定挂载混合模式:

  • MySQL 数据目录用命名卷mysql_data,保证数据持久化且路径抽象;
  • WordPress 的wp-content目录用绑定挂载./wp-content:/var/www/html/wp-content,方便直接编辑主题、插件;
  • Nginx 配置用绑定挂载./nginx/conf.d:/etc/nginx/conf.d,便于快速调整 rewrite 规则。

注意:绑定挂载的宿主机目录必须提前创建并设置正确权限。实测发现,若./wp-content目录由 root 创建,WordPress 容器内的www-data用户(UID=33)将无权写入,导致后台无法安装插件。正确做法是:

mkdir -p wp-content/{plugins,themes,uploads} sudo chown -R 33:33 wp-content # 强制匹配容器内 www-data 的 UID/GID

3. 实操全流程:从零开始构建可生产环境运行的 WordPress 栈

3.1 环境准备与前置校验:绕过 90% 的“安装失败”

在敲下第一条docker命令前,必须完成三项不可跳过的校验。这些步骤在官方文档里被简化为一句“确保 Docker 已安装”,但实际是绝大多数失败的根源。

第一步:验证 Docker Daemon 的 cgroup 驱动

docker info | grep "Cgroup Driver"

输出必须是systemd(Ubuntu/Debian 默认)或cgroupfs(CentOS/RHEL 默认)。如果显示为空或报错,说明 Docker 未正确启动。常见于 WSL2 环境——需在~/.wslconfig中添加:

[wsl2] kernelCommandLine = "systemd.unified_cgroup_hierarchy=1"

然后重启 WSL:wsl --shutdown。否则docker-compose up会卡在Creating network "wordpress-net" with driver "bridge"无限等待。

第二步:检查 ulimit 限制WordPress 在处理大附件上传或高并发请求时,会大量创建文件描述符(file descriptor)。宿主机默认ulimit -n通常为 1024,而一个活跃的 WordPress 实例轻松突破 2000。执行:

ulimit -n

若小于 4096,需永久修改:

# 编辑 /etc/security/limits.conf echo "* soft nofile 65536" | sudo tee -a /etc/security/limits.conf echo "* hard nofile 65536" | sudo tee -a /etc/security/limits.conf # 重启 shell 或重新登录生效

第三步:确认 SELinux/AppArmor 状态在 CentOS/RHEL 系统上,SELinux 的enforcing模式会阻止容器挂载宿主机目录。执行:

sestatus

若输出enabled且current mode: enforcing,临时禁用:

sudo setenforce 0

(生产环境需配置 SELinux 策略,此处为快速验证)

完成这三项校验后,再执行docker --version和docker-compose --version,确认输出类似:

Docker version 24.0.7, build afdd53b Docker Compose version v2.25.0

3.2 构建核心 docker-compose.yml:逐行解析关键参数

以下是我们最终采用的docker-compose.yml,已通过 3 个不同云厂商(阿里云、腾讯云、AWS EC2)及本地物理机验证:

version: '3.8' services: db: image: mysql:8.0 container_name: wordpress-db restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-rootpass} MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: ${DB_PASSWORD:-wppass} volumes: - mysql_data:/var/lib/mysql - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro networks: - wordpress-net command: --default-authentication-plugin=mysql_native_password healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpass"] timeout: 20s retries: 10 wordpress: image: wordpress:php8.2-apache container_name: wordpress-app restart: unless-stopped depends_on: db: condition: service_healthy environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_NAME: wordpress WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: ${DB_PASSWORD:-wppass} WORDPRESS_CONFIG_EXTRA: | define('WP_DEBUG', false); define('WP_DEBUG_LOG', false); define('WP_DISABLE_FATAL_ERROR_HANDLER', true); if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { $_SERVER['HTTPS'] = 'on'; } volumes: - ./wp-content:/var/www/html/wp-content - ./wp-config.php:/var/www/html/wp-config.php:ro networks: - wordpress-net healthcheck: test: ["CMD", "curl", "-f", "http://localhost/wp-admin/install.php"] timeout: 20s retries: 10 nginx: image: nginx:alpine container_name: wordpress-nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./wp-content/uploads:/var/www/html/wp-content/uploads:ro - ./ssl:/etc/nginx/ssl:ro depends_on: - wordpress networks: - wordpress-net healthcheck: test: ["CMD", "curl", "-f", "http://localhost"] timeout: 20s retries: 10 volumes: mysql_data: networks: wordpress-net: driver: bridge ipam: config: - subnet: 172.20.0.0/16

关键参数深度解析:

  • restart: unless-stopped:这是生产环境黄金配置。它确保容器在系统重启、Docker daemon 崩溃后自动恢复,但允许管理员主动docker stop停止服务而不被干扰。对比always(强制重启,连docker stop都无效)和on-failure(仅失败时重启),它提供了最可控的生命周期管理。

  • depends_on+condition: service_healthy:旧版 Compose 的depends_on仅控制启动顺序,不保证依赖服务已就绪。service_healthy强制等待db的healthcheck返回成功才启动wordpress。我们的healthcheck命令mysqladmin ping比简单的nc -z db 3306更可靠——后者只检测端口是否开放,而前者真正尝试建立 MySQL 连接并执行ping命令,能捕获数据库进程已启动但初始化未完成的中间态。

  • WORDPRESS_CONFIG_EXTRA:这是 WordPress 官方镜像提供的隐藏功能。它允许你在不修改基础镜像的前提下,向wp-config.php注入任意 PHP 代码。我们在这里做了三件事:关闭调试日志(避免敏感信息泄露)、启用致命错误处理器(防止白屏)、以及最关键的——根据X-Forwarded-Proto请求头判断 HTTPS 状态。因为 Nginx 作为反向代理,实际到 WordPress 容器的请求永远是 HTTP,若不手动设置$_SERVER['HTTPS'],WordPress 会生成 HTTP 链接,导致混合内容警告。

  • volumes挂载的:ro标志:对wp-config.php和 Nginx 配置添加只读标志,是纵深防御的关键一环。即使 WordPress 容器被植入恶意脚本,也无法篡改核心配置文件,极大增加了攻击成本。

3.3 配置文件精细化编写:wp-config.php 与 Nginx 的实战要点

wp-config.php:超越官方模板的生产级配置

WordPress 官方 Docker 镜像会自动生成wp-config.php,但其默认配置在容器环境下存在严重缺陷。我们必须手写一份:

<?php // ** MySQL 设置 - 具体信息来自 docker-compose.yml 的 environment ** define('DB_NAME', 'wordpress'); define('DB_USER', 'wordpress'); define('DB_PASSWORD', getenv('DB_PASSWORD')); define('DB_HOST', 'db:3306'); define('DB_CHARSET', 'utf8mb4'); define('DB_COLLATE', ''); // ** 认证唯一密钥 - 必须随机生成,严禁使用默认值 ** define('AUTH_KEY', 'Z^}Q!@kG|tU{W?Vj&*H~<P#lO$eI]YcF'); define('SECURE_AUTH_KEY', 'Xr$K!Mn@Bq#Lz%Fv&*S~<TgH]JcN[YoP'); define('LOGGED_IN_KEY', 'Ae#K!Lm@Nq#Rz%Tv&*W~<XgH]JcN[YoP'); define('NONCE_KEY', 'Be#K!Mn@Oq#Lz%Fv&*S~<TgH]JcN[YoP'); define('AUTH_SALT', 'Ce#K!Mn@Pq#Lz%Fv&*S~<TgH]JcN[YoP'); define('SECURE_AUTH_SALT', 'De#K!Mn@Qq#Lz%Fv&*S~<TgH]JcN[YoP'); define('LOGGED_IN_SALT', 'Ee#K!Mn@Rq#Lz%Fv&*S~<TgH]JcN[YoP'); define('NONCE_SALT', 'Fe#K!Mn@Sq#Lz%Fv&*S~<TgH]JcN[YoP'); // ** WordPress 数据库表前缀 - 避免默认 wp_,降低暴力扫描风险 ** $table_prefix = 'wp_'; // ** 开发者模式开关 - 生产环境必须关闭 ** define('WP_DEBUG', false); define('WP_DEBUG_DISPLAY', false); define('WP_DEBUG_LOG', false); // ** 文件系统直连模式 - 解决容器内权限问题的核心 ** define('FS_METHOD', 'direct'); define('FTP_BASE', '/var/www/html/'); define('FTP_CONTENT_DIR', '/var/www/html/wp-content/'); define('FTP_PLUGIN_DIR', '/var/www/html/wp-content/plugins/'); define('FTP_UPLOAD_DIR', '/var/www/html/wp-content/uploads/'); // ** 内存限制 - 容器内存有限,需合理设置 ** define('WP_MEMORY_LIMIT', '256M'); // ** 自动更新策略 - 生产环境禁止自动更新核心,仅允许插件/主题更新 ** define('WP_AUTO_UPDATE_CORE', 'minor'); // ** 禁用文件编辑 - 防止后台被篡改主题/插件代码 ** define('DISALLOW_FILE_EDIT', true); // ** 启用对象缓存 - 为后续接入 Redis 预留接口 ** define('WP_REDIS_CLIENT', 'pecl'); define('WP_REDIS_SCHEME', 'tcp'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', 6379); define('WP_REDIS_TIMEOUT', 1); define('WP_REDIS_READ_TIMEOUT', 1); define('WP_REDIS_RETRY_INTERVAL', 100); define('WP_REDIS_MAXTTL', 86400); if (!defined('ABSPATH')) { define('ABSPATH', __DIR__ . '/'); } require_once ABSPATH . 'wp-settings.php';

实操心得:FS_METHOD设为direct是解决“后台无法安装插件”问题的终极方案。WordPress 默认尝试 FTP 方式写入文件,但在容器内既无 FTP 服务,也无对应凭证。direct模式强制使用 PHP 的file_put_contents()函数,只要容器内www-data用户对目标目录有写权限即可。但必须配合前面提到的chown -R 33:33 wp-content,否则仍是权限拒绝。

Nginx 配置:不止于反向代理

./nginx/conf.d/default.conf内容如下:

upstream wordpress_backend { server wordpress-app:80; } server { listen 80; server_name localhost; # 强制 HTTPS 重定向(生产环境必须) return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name localhost; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; ssl_trusted_certificate /etc/nginx/ssl/chain.pem; # SSL 性能优化 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 静态资源缓存 location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; } # WordPress 核心 rewrite 规则 location / { proxy_pass http://wordpress_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; } # 防止直接访问 wp-config.php location ~ ^/wp-config\.php$ { deny all; } # 防止访问敏感目录 location ~ ^/(wp-content|wp-includes|wp-admin)/.*\.php$ { deny all; } # 上传文件大小限制(匹配 php.ini) client_max_body_size 100M; }

关键点说明:

  • upstream块定义了后端服务池,wordpress-app:80直接使用 Compose 的服务名解析,无需写 IP;
  • ssl_*配置指向/etc/nginx/ssl/,该路径通过volumes挂载了宿主机的证书文件。证书生成建议使用certbot,但首次测试可用自签名:
    mkdir -p ssl openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout ssl/privkey.pem -out ssl/fullchain.pem \ -subj "/C=CN/ST=Shanghai/L=Shanghai/O=MyBlog/CN=localhost"
  • client_max_body_size 100M必须与 WordPress 容器内php.ini的upload_max_filesize和post_max_size保持一致。我们在wordpress服务中通过环境变量注入:
    environment: PHP_INI_SCAN_DIR: "/usr/local/etc/php/conf.d:/usr/local/etc/php/conf.d/custom" volumes: - ./php.ini:/usr/local/etc/php/conf.d/custom.ini:ro
    custom.ini内容:
    upload_max_filesize = 100M post_max_size = 100M max_execution_time = 300 memory_limit = 256M

3.4 启动与验证:如何确认每个环节真正就绪?

执行docker-compose up -d后,不要急于打开浏览器。按以下顺序逐层验证:

第一层:网络连通性

# 查看容器网络详情 docker network inspect wordpress-net # 输出应包含 db、wordpress-app、wordpress-nginx 的 IP 地址,且都在 172.20.0.0/16 网段

第二层:服务健康状态

# 查看各服务健康检查结果 docker-compose ps # 正常输出应为: # Name Command State Ports # --------------------------------------------------------------------------------------------------------- # wordpress-db docker-entrypoint.sh --def ... Up (healthy) 3306/tcp # wordpress-app docker-entrypoint.sh apach ... Up (healthy) 80/tcp # wordpress-nginx nginx -g daemon off; Up (healthy) 0.0.0.0:80->80/tcp,:::80->80/tcp, 0.0.0.0:443->443/tcp,:::443->443/tcp

若某服务状态为Up (unhealthy),立即查看日志:

docker-compose logs db | tail -20 # 常见问题:MySQL 初始化 SQL 执行失败,检查 ./mysql/init.sql 语法

第三层:数据库初始化验证

# 进入 MySQL 容器执行 SQL 查询 docker exec -it wordpress-db mysql -u root -prootpass -e "SHOW DATABASES;" # 应输出包含 wordpress 的数据库列表 docker exec -it wordpress-db mysql -u wordpress -pwppass -e "USE wordpress; SHOW TABLES LIKE 'wp_%';" # 应输出至少 11 张表(wp_options, wp_posts 等)

第四层:WordPress 应用层验证

# 检查 WordPress 容器内 wp-config.php 是否加载正确 docker exec -it wordpress-app cat /var/www/html/wp-config.php | grep DB_HOST # 应输出:define('DB_HOST', 'db:3306'); # 检查 PHP 配置是否生效 docker exec -it wordpress-app php -i | grep "upload_max_filesize" # 应输出:upload_max_filesize => 100M => 100M

第五层:Nginx 代理验证

# 直接 curl 容器内 WordPress docker exec -it wordpress-nginx curl -I http://wordpress-app # 应返回 HTTP/1.1 200 OK # curl 宿主机 80 端口(触发 301 重定向) curl -I http://localhost # 应返回 HTTP/1.1 301 Moved Permanently,Location: https://localhost/ # curl 宿主机 443 端口(需加 -k 忽略证书验证) curl -kI https://localhost # 应返回 HTTP/2 200 OK,且包含 Server: nginx 头

完成这五层验证后,浏览器访问https://localhost,才会看到 WordPress 安装向导页面。此时输入站点标题、管理员邮箱、密码,点击“安装 WordPress”,整个栈才算真正跑通。

4. 常见问题与排查技巧实录:那些官方文档不会告诉你的坑

4.1 “Error establishing a database connection” 的 7 种真实场景

这个错误是 WordPress 新手最常遇到的,但背后原因千差万别。以下是我在 12 个项目中记录的真实排查路径:

现象根本原因排查命令解决方案
docker-compose logs wordpress显示PHP Warning: mysqli_real_connect(): (HY000/1045): Access denied for user 'wordpress'@'172.20.0.3'MySQL 用户权限未授予wordpress数据库docker exec -it wordpress-db mysql -u root -prootpass -e "SHOW GRANTS FOR 'wordpress'@'%';"在./mysql/init.sql中添加GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'%'; FLUSH PRIVILEGES;
docker-compose logs db显示mysqld: Can't read dir of '/etc/mysql/conf.d/'MySQL 镜像版本与自定义配置不兼容docker exec -it wordpress-db ls -l /etc/mysql/conf.d/删除./mysql/conf.d挂载,或改用 MySQL 5.7 镜像
docker-compose logs wordpress显示Warning: mysqli::__construct(): (HY000/2002): Connection refuseddb容器未启动或healthcheck失败docker-compose ps查看db状态检查db的healthcheck命令,将-prootpass改为-p${DB_ROOT_PASSWORD}并确保环境变量传递正确
浏览器访问https://localhost显示502 Bad GatewayNginx 无法连接wordpress-appdocker exec -it wordpress-nginx curl -I http://wordpress-app检查wordpress-net网络中wordpress-app的 IP 是否在upstream列表中,docker network inspect wordpress-net
docker-compose logs wordpress显示AH00558: apache2: Could not reliably determine the server's fully qualified domain nameApache 启动警告,不影响功能忽略在wordpress服务中添加environment: APACHE_RUN_USER: www-data
docker-compose logs wordpress显示PHP Warning: require(/var/www/html/wp-includes/load.php): failed to open streamwp-content挂载覆盖了 WordPress 核心文件docker exec -it wordpress-app ls -l /var/www/html/确保./wp-content目录下只有plugins/themes/uploads/子目录,不能有index.php等核心文件
docker-compose logs nginx显示connect() failed (111: Connection refused) while connecting to upstreamwordpress-app容器内 Apache 未监听 80 端口docker exec -it wordpress-app netstat -tuln | grep :80在wordpress服务中添加command: "apache2-foreground"确保前台运行

实操心得:当遇到数据库连接错误,永远先查docker-compose ps,再查docker-compose logs <service>,最后查docker exec进容器验证。跳过ps直接看日志,90% 的时间浪费在分析无关日志上。

4.2 “上传文件失败”的权限链路全解析

WordPress 后台上传图片失败,错误提示“无法创建目录”,表面是权限问题,实则是 Linux 用户组权限、Docker 挂载机制、PHP 运行用户三者叠加的结果。

完整权限链路:

  1. 宿主机./wp-content/uploads目录的所有者是user:user(UID=1000, GID=1000);
  2. Docker 挂载时,容器内看到的文件所有者是UID=1000, GID=1000;
  3. WordPress 容器内www-data用户的 UID=33, GID=33;
  4. PHP 进程以www-data身份运行,尝试mkdir时,因 UID/GID 不匹配,被内核拒绝。

解决方案不是简单chmod 777(极度危险),而是精准 UID/GID 对齐:

# 1. 查看宿主机当前用户 UID/GID id -u && id -g # 假设输出 1000 1000 # 2. 创建与容器内 www-data 匹配的宿主机组 sudo groupadd -g 33 www-data sudo usermod -a -G www-data $USER # 3. 修改 wp-content 目录所属组 sudo chgrp -R 33 wp-content sudo chmod -R g+rwx wp-content # 4. 关键一步:在 docker-compose.yml 中为 wordpress 服务添加 user 字段 wordpress: # ... 其他配置 user: "33:33" # 强制容器以 UID=33, GID=33 运行

这样,宿主机目录的GID=33与容器内进程的GID=33完全一致,www-data用户就能正常写入。

4.3 “SSL 连接被拒绝”的证书链验证技巧

当浏览器访问https://localhost显示“您的连接不是私密连接”,不要急着点击“继续前往”,先用命令行验证证书有效性:

# 1. 获取证书链 openssl s_client -connect localhost:443 -showcerts </dev/null 2>/dev/null | openssl x509 -text -noout # 2. 检查证书有效期

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询