Docker Compose 实战:从零搭建一个带 HTTPS 的 WordPress 网站

本文将以专家的视角,一步步指导你如何使用 Docker Compose 搭建一个包含 Nginx、MySQL、PHP-FPM 和 Let's Encrypt 自动续期 SSL 证书的完整 WordPress 生产环境。我们将深入探讨服务编排、数据持久化、网络配置和安全实践,提供一套可复用、易于维护的部署方案。

阅读时长: 6 分钟
共 2523字
作者: eimoon.com

手动部署 WordPress 那一套 LAMP/LEMP 的流程,想必大家都不陌生,但说实话,挺繁琐的。环境配置、软件依赖、权限管理,任何一个环节出错都可能让你折腾半天。

今天我们换个玩法,用 Docker Compose 把 WordPress、Nginx、MySQL 和 SSL 证书一条龙搞定。这套方案不仅部署速度快,而且环境隔离做得好,方便迁移和维护,非常适合现代化的工作流。

整个流程下来,你会得到一个包含以下组件的、生产就绪的 WordPress 网站:

  • Nginx 作为 Web 服务器
  • MySQL 8.0 数据库
  • WordPress (PHP-FPM) 应用本身
  • Certbot 用于自动获取和续期 Let’s Encrypt 的免费 SSL 证书

准备工作

在开始之前,你需要准备好以下几样东西:

  1. 一台装有 Docker 和 Docker Compose 的服务器(建议 Ubuntu)。
  2. 一个注册好的域名,并且已经将 DNS A 记录指向你服务器的公网 IP。
  3. 具备 sudo 权限的非 root 用户。

第一步:初始化项目结构

首先,我们需要为项目创建一个目录,并规划好文件结构。

# 创建并进入项目目录
mkdir wordpress_site && cd wordpress_site

# 创建用于存放 Nginx 配置的子目录
mkdir nginx-conf

接下来,创建一个 .env 文件,用于存放敏感的环境变量。

nano .env

在这个文件里,我们定义数据库的密码等信息。请务必使用你自己的强密码替换掉占位符。

# ~/wordpress_site/.env

MYSQL_ROOT_PASSWORD=your_strong_root_password
MYSQL_USER=wp_user
MYSQL_PASSWORD=your_strong_user_password

这个文件专门用来存放敏感信息,比如数据库密码,这样就不会意外地把它提交到 Git 仓库里,非常安全的地道。为了确保这一点,最好再创建一个 .gitignore.dockerignore 文件,把 .env 加进去。

第二步:定义服务编排 docker-compose.yml

这是整个部署的核心。docker-compose.yml 文件定义了我们需要的所有服务(容器)、它们之间的关系、网络以及数据卷。

在项目根目录 ~/wordpress_site/下创建 docker-compose.yml 文件:

nano docker-compose.yml

然后,把下面的配置完整地粘贴进去。我会再后面分块解释每个部分的作用。

version: '3'

services:
  db:
    image: mysql:8.0
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - dbdata:/var/lib/mysql
    command: '--default-authentication-plugin=mysql_native_password'
    networks:
      - app-network

  wordpress:
    depends_on:
      - db
    image: wordpress:latest-fpm-alpine # 使用 fpm-alpine 镜像
    container_name: wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress:/var/www/html
    networks:
      - app-network

  webserver:
    depends_on:
      - wordpress
    image: nginx:latest-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email your_email@example.com --agree-tos --no-eff-email --staging -d your_domain.com -d www.your_domain.com

volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge

配置解析:

  • services: 定义了四个服务:db, wordpress, webserver, certbot
  • db 服务 (MySQL):
    • image: mysql:8.0: 锁定 MySQL 8.0 版本,避免未来因镜像更新导致兼容性问题。
    • env_file: .env: 从 .env 文件加载环境变量。
    • volumes: - dbdata:/var/lib/mysql: 将数据库文件持久化到名为 dbdata 的 Docker 数据卷中,这样即使容器被删除,数据也不会丢失。
    • command: '--default-authentication-plugin=mysql_native_password': 这是一个关键点。MySQL 8.0 默认的认证插件 PHP 不支持,所以我们强制它使用旧的兼容插件。
    • networks: - app-network: 将其连接到我们自定义的网络中。
  • wordpress 服务:
    • depends_on: - db: 因为 WordPress 应用依赖数据库,这个 depends_on 确保了 db 容器会比 wordpress 容器先启来
    • image: wordpress:latest-fpm-alpine: 我们用的是 fpm-alpine 版本的镜像。FPM (FastCGI Process Manager) 是 Nginx 连接 PHP 所需的处理器,alpine 版本则体积更小。
    • environment: 这里设置了 WordPress 连接数据库所需的各种凭证,注意它引用了 .env 文件中的变量。
    • volumes: - wordpress:/var/www/html: 将 WordPress 的所有文件(核心、主题、插件、上传内容)都存放在 wordpress 数据卷中。
  • webserver 服务 (Nginx):
    • ports: - "80:80" - "443:443": 将容器的 80 和 443 端口映射到主机的对应端口,这样外部流量才能访问。
    • volumes: 这里挂载了三个卷:
      • wordpress:/var/www/html: 共享 WordPress 的文件,以便 Nginx 可以提供服务。
      • ./nginx-conf:/etc/nginx/conf.d: 将我们本地的 Nginx 配置文件目录挂载到容器中。
      • certbot-etc:/etc/letsencrypt: 共享 SSL 证书文件。
  • certbot 服务:
    • 这个服务专门用来和 Let’s Encrypt 打交道。
    • volumes: 它也挂载了证书目录和 WordPress 文件目录(用于 webroot 验证)。
    • command: 这是核心。它告诉 Certbot:
      • certonly: 只获取证书,不安装。
      • --webroot: 使用 webroot 插件进行验证,即在网站根目录下放置一个临时文件。
      • --staging: 非常重要! 初始阶段我们使用 Let’s Encrypt 的测试环境,避免因为配置错误而触发请求频率限制。
      • -d your_domain.com: 替换成你自己的域名。
  • volumesnetworks:
    • 在文件底部,我们声明了所有用到的命名数据卷 (dbdata, wordpress, certbot-etc) 和自定义桥接网络 app-network。这个网络让所有服务能通过容器名互相通信,同时保持了与外部网络的隔离。

第三步:配置 Nginx 并获取 SSL 证书

现在,我们需要先创建一个简单的 Nginx 配置,仅用于通过 Let’s Encrypt 的 HTTP-01 质询。

nginx-conf 目录下创建 nginx.conf 文件:

nano nginx-conf/nginx.conf

填入以下内容,记得替换域名:

# ~/wordpress_site/nginx-conf/nginx.conf

server {
    listen 80;
    server_name your_domain.com www.your_domain.com;

    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/html;
    }
}

这个配置非常简单,它只监听 80 端口,并正确处理 Certbot 验证所需的请求。

启动容器并获取测试证书

万事俱备,我们来启动除了 certbot 之外的所有服务:

docker-compose up -d --build db wordpress webserver

然后,单独运行 certbot 服务来获取测试证书:

docker-compose run --rm certbot

如果一切顺利,你应该能看到成功获取证书的提示。你可以通过查看 certbot-etc 数据卷的内容来确认:

sudo ls -l /var/lib/docker/volumes/wordpress_site_certbot-etc/_data/live

看到你的域名文件夹就代表成功了。

获取生产证书

测试成功后,我们来获取正式的生产证书。修改 docker-compose.yml 文件,把 certbot 服务里的 command 中的 --staging 标志去掉。

# ... (部分配置)
  certbot:
    # ...
    command: certonly --webroot --webroot-path=/var/www/html --email your_email@example.com --agree-tos --no-eff-email -d your_domain.com -d www.your_domain.com

然后再次运行 certbot 服务:

docker-compose run --rm certbot

现在,你就拥有了正式的 SSL 证书。

第四步:启用 HTTPS 并强化安全

有了证书,我们就可以更新 Nginx 配置,全面启用 HTTPS。

用下面的内容覆盖 nginx-conf/nginx.conf 文件:

# ~/wordpress_site/nginx-conf/nginx.conf

server {
    listen 80;
    server_name your_domain.com www.your_domain.com;

    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/html;
    }

    location / {
        rewrite ^ https://$host$request_uri? permanent;
    }
}

server {
    listen 443 ssl http2;
    server_name your_domain.com www.your_domain.com;

    root /var/www/html;
    index index.php;

    ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;

    # 推荐的安全配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers off;
    
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;
    
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    location ~ /\.ht {
        deny all;
    }
}

这个配置做了几件事:

  1. 保留了 80 端口的 server 块,但将所有普通 HTTP 请求永久重定向到 HTTPS。
  2. 新增了一个监听 443 端口的 server 块,启用了 SSL 和 HTTP/2。
  3. 指定了 SSL 证书和私钥的路径。
  4. 配置了 PHP-FPM 的处理逻辑,将 .php 请求转发给 wordpress 服务的 9000 端口。

保存配置后,重新加载 Nginx 服务使之生效:

docker-compose restart webserver

第五步:完成 WordPress 网页安装

现在,在你的浏览器中访问 https://your_domain.com。你应该能看到 WordPress 的安装界面了。按照提示选择语言、设置站点标题、管理员用户名和密码,即可完成安装。

第六步:设置证书自动续期

Let’s Encrypt 证书有效期为 90 天,我们需要让它自动续期。最简单的方法是创建一个 cron 任务。

先创建一个续期脚本 ssl_renew.sh

nano ssl_renew.sh

写入以下内容:

#!/bin/bash

# 切换到你的项目目录
cd /home/your_user/wordpress_site/

# 运行 certbot renew 命令
docker-compose run --rm certbot renew

# 重启 webserver 以加载新证书
docker-compose restart webserver

给它执行权限:

chmod +x ssl_renew.sh

然后编辑 root 用户的 crontab:

sudo crontab -e

在文件末尾添加一行,让脚本在每个月的 1 号凌晨 3:30 运行:

30 3 1 * * /home/your_user/wordpress_site/ssl_renew.sh > /var/log/cron.log 2>&1

这个脚本会定期运行,检查你的证书是否快要过期,如果是,就自动续期,然后从新加载 Nginx 配置。这样就一劳永逸了。

总结

至此,你已经成功地用 Docker Compose 搭建了一个功能完善、安全可靠且易于维护的 WordPress 网站。这套配置不仅能跑 WordPress,稍作修改就能用于部署其他任何基于 PHP 和 MySQL 的应用。

Docker Compose 的强大之处就在于这种声明式的、可复用的部署方式,希望这次实践能给你在未来的项目中带来一些启发。

关于

关注我获取更多资讯

公众号
📢 公众号
个人号
💬 个人号
使用 Hugo 构建
主题 StackJimmy 设计