拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Docker容器化部署维基萌博客:从镜像选型到数据备份的完整实践指南

1. 为什么我最终选了 Docker 这条路先说结论维基萌博客系统这套东西我前后折腾过三种部署方式——宝塔面板直接跑、手动编译装环境、最后才换到 Docker 容器化部署。如果你现在问我推荐哪种我毫不犹豫推荐 Docker而且最好是 Docker Desktop 或者 Linux 服务器上的 Docker Engine 都行关键是掌握一套思路而不是死记某条命令。维基萌博客系统本质上是一个以内容管理为核心的博客平台底层依赖 PHP 运行环境、数据库MySQL 或者兼容 MySQL 协议的关系型数据库、Web 服务器Nginx 为主再加上一堆 PHP 扩展和缓存组件。这种组合装起来不难难的是迁移、备份、版本升级和多环境复现。传统方式下你今天在一台服务器上配好了明天换一台机器还得重新装一遍 PHP 扩展、重新改 Nginx 配置、重新初始化数据库中途任何一个版本对不上就是各种奇奇怪怪的报错。Docker 解决的就是这个问题。它把整个运行环境打包成镜像镜像里面 PHP 版本、扩展、配置、Nginx 规则全给你定死走到哪儿都是一样的行为。我实际用下来最大的感受是前期学习成本换来的是后期极大的省心尤其是博客系统这种需要长期维护的项目你不可能每次都靠记忆力去复现环境。这篇文章我会按照我自己的实操路径来写从镜像选择、目录规划、编排文件编写、数据库初始化到上线后的备份和故障排查一步步拆开讲。里面提到的细节都是我踩过坑之后总结出来的希望你看完能直接照着操作不用再走一遍弯路。2. 部署前的思路整理先想清楚再动手2.1 这套博客系统的组成拆解维基萌博客系统的部署本质上可以拆成四个部分Web 服务Nginx负责接收 HTTP 请求、处理静态资源、转发 PHP 请求给 PHP-FPM。应用运行时PHP-FPM负责执行 PHP 代码。这个博客系统对 PHP 版本有要求我当时用的是 PHP 8.1实测稳定。数据存储MySQL 8.0用于存放文章、用户、评论、配置等数据。缓存与辅助Redis用于缓存热点数据、Session减轻数据库压力。这四个部分各有各的配置要求如果用传统方式装你得分别下载、安装、配置、启动任何一环出了问题都不好排查。用 Docker 之后四个容器各干各的活通过内部网络互相通信结构非常清晰。2.2 网络模式的选择逻辑Docker 容器之间的通信最省事的方式是创建一个自定义 bridge 网络。我见过很多人直接让容器用 host 网络模式或者靠端口映射把服务暴露到宿主机再互相访问这样不是不行但安全性和隔离性都差一些。我创建了一个名为wm_blog_net的网络所有容器都接入这个网络它们之间通过服务名直接通信。比如 PHP-FPM 容器访问数据库用的地址是mysql:3306而不是127.0.0.1:3306。这个设计有两点好处容器 IP 会变但服务名稳定重启之后不用改配置。内部流量不经过宿主机网卡少一层转发性能更好也更安全。提示如果你用的是 Docker DesktopWindows 或 macOS网络模式同样适用不需要额外配置。真正需要注意的是文件挂载路径Windows 下挂载目录的权限和路径分隔符跟 Linux 有差异后面我会专门说这个问题。2.3 目录规划让数据和安全解耦容器本身是无状态的重启之后容器内的文件系统改动会丢失除非你提交成新镜像。所以部署前必须做好目录挂载规划把所有需要持久化的数据放到宿主机目录。这是我当时的目录结构/home/wm_blog/ ├── docker-compose.yml ├── .env ├── nginx/ │ └── conf.d/ │ └── default.conf ├── php/ │ └── php.ini ├── mysql/ │ ├── data/ # 数据库数据文件 │ └── init/ # 初始化 SQL 脚本 ├── blog/ # 博客系统源码 └── logs/ ├── nginx/ └── php/核心原则是数据文件、配置文件、日志文件全部挂载到宿主机。这样容器随便删、随便重建数据一点不丢。我后来升级镜像版本的时候就是直接改镜像版本号然后docker compose up -d全程零数据损失。3. 镜像选型与编排文件编写3.1 镜像版本怎么选镜像版本这块我踩过一个大坑。第一次部署的时候我图省事直接用了mysql:latest结果拉下来的是 MySQL 8.4跟博客系统初始化脚本里的某些语法不兼容折腾了两个小时才定位到是数据库版本的问题。后来我把所有镜像版本都固定下来标注成明确的版本号再没出过这种诡异问题。我最终选定的版本组合服务镜像版本说明Nginxnginx:1.27-alpineAlpine 版体积小约 25MB跑起来很稳PHPphp:8.1-fpm官方 FPM 镜像需要自行安装扩展MySQLmysql:8.08.0 系列兼容性好初始化脚本能跑通Redisredis:7-alpine轻量且稳定主要做缓存和 Session这里多说一句PHP 官方镜像不带扩展你需要自己装。博客系统一般需要pdo_mysql、mysqli、redis、gd、zip这几个扩展。我当时写了一个 Dockerfile 来构建 PHP 镜像你也可以用docker-php-ext-install命令在容器里手动装但每次重建容器都得重来一遍建议还是做成镜像。3.2 Dockerfile 构建 PHP 运行时这是我用的 PHP Dockerfile直接贴出来注释我写在后面FROM php:8.1-fpm # 安装系统依赖和 PHP 扩展 RUN apt-get update apt-get install -y \ libzip-dev \ libpng-dev \ libjpeg-dev \ libfreetype6-dev \ docker-php-ext-configure gd --with-freetype --with-jpeg \ docker-php-ext-install -j$(nproc) pdo_mysql mysqli gd zip \ pecl install redis \ docker-php-ext-enable redis \ apt-get clean # 复制自定义配置 COPY php.ini /usr/local/etc/php/conf.d/custom.ini WORKDIR /var/www/html几个关键点docker-php-ext-install是官方镜像自带的扩展安装脚本装完自动生成.so文件并写入配置省去手动编译的麻烦。gd扩展需要先配置--with-freetype --with-jpeg否则后续处理图片验证码之类的功能会报 Call to undefined function imagecreatefromjpeg。pecl install redis需要容器有网络构建镜像的时候要保证能访问外网。docker-php-ext-enable redis相当于自动在docker-php-ext-redis.ini里添加extensionredis.so一行比手动编辑配置文件稳当。3.3 docker-compose.yml 完整编排下面是完整的编排文件这是整个部署的核心建议直接复制然后按需调整version: 3.8 services: nginx: image: nginx:1.27-alpine container_name: wm_nginx ports: - 8080:80 volumes: - ./blog:/var/www/html:ro - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./logs/nginx:/var/log/nginx depends_on: - php networks: - wm_blog_net php: build: ./php image: wm_blog_php:8.1 container_name: wm_php volumes: - ./blog:/var/www/html - ./logs/php:/var/log/php depends_on: - mysql - redis networks: - wm_blog_net mysql: image: mysql:8.0 container_name: wm_mysql ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} TZ: Asia/Shanghai volumes: - ./mysql/data:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d:ro command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci networks: - wm_blog_net redis: image: redis:7-alpine container_name: wm_redis volumes: - ./redis/data:/data command: [redis-server, --appendonly, yes] networks: - wm_blog_net networks: wm_blog_net: driver: bridge.env 文件内容MYSQL_ROOT_PASSWORDyour_strong_root_password MYSQL_DATABASEwm_blog MYSQL_USERwm_user MYSQL_PASSWORDyour_strong_user_password注意.env文件不要提交到 Git 仓库里面是数据库密码属于敏感信息。我见过有网友把.env传上 GitHub结果服务器被扫库数据被删了勒索比特币。数据库端口3306尽量别暴露公网如果只是本机或内网使用把ports里的3306:3306注释掉容器之间通过网络正常通信不需要映射到宿主机。4. Nginx 配置与 PHP 联调细节4.1 Nginx 配置文件这样写才稳Nginx 配置是整个部署过程中最容易出问题的地方。维基萌博客系统的伪静态规则依赖 PathInfo 模式如果你直接把普通的 LNMP 配置套上去大概率会出现 404 或者访问首页正常、访问文章页就白屏的情况。我的 Nginx 配置文件/home/wm_blog/nginx/conf.d/default.confserver { listen 80; server_name your-domain.com; root /var/www/html/public; index index.php index.html; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_param PATH_INFO $fastcgi_path_info; } location ~ /\.ht { deny all; } }几个细节说明root指向/var/www/html/public这个取决于博客系统源码的入口目录。有的系统入口在根目录有的是public子目录你必须先确认清楚再写 root否则会出现 No input file specified 这类经典报错。fastcgi_pass php:9000这里的php是 docker-compose 里的服务名Nginx 容器会通过自定义网络解析到这个服务名从而找到 PHP-FPM 容器的 9000 端口。fastcgi_param PATH_INFO $fastcgi_path_info这行很重要。PathInfo 模式下URL 里的路径信息需要显式传给 PHP否则部分框架路由无法正常工作。4.2 一个容易被忽略的权限问题我在第一次部署时遇到一个诡异的问题博客能安装但是上传图片全部失败日志里报的是权限不足。查了半天才定位到Nginx 容器和 PHP 容器虽然挂载了同一个源码目录但两个容器内的用户不同。Nginx 容器里跑的是nginx用户PHP 容器里跑的是www-data用户宿主机目录权限如果只给了某个 UID另一边的进程就没法写入。解决办法很简单给源码目录设置成 755 或 775并确保 PHP 容器内的www-data用户对上传目录有写权限。如果你用的是 Linux 服务器可以直接操作chown -R www-data:www-data /home/wm_blog/blog chmod -R 755 /home/wm_blog/blog chmod -R 775 /home/wm_blog/blog/storage /home/wm_blog/blog/uploadsWindows 上用 Docker Desktop 的话文件权限映射有自己的规则一般不用特意处理但如果上传时遇到 Permission denied优先检查目录读写权限再检查是不是防病毒软件在中间拦截。5. 数据库初始化与数据迁移5.1 利用官方初始化机制导入数据MySQL 官方镜像有一个非常方便的特性容器首次启动时会依次执行/docker-entrypoint-initdb.d/目录下的.sql脚本。这意味着你只需要把建库建表的 SQL、初始数据丢到这个目录容器第一次启动就会自动导入不需要手动进容器执行。我的操作方式是这样的在原环境比如你之前用宝塔跑着的博客导出数据库mysqldump -u root -p wm_blog backup.sql。把backup.sql放到/home/wm_blog/mysql/init/目录下。执行docker compose up -d启动 MySQL 容器首次启动时自动导入。这里的坑是如果你已经启动过 MySQL 容器/docker-entrypoint-initdb.d/下的脚本不会再执行。因为 MySQL 的数据目录/var/lib/mysql已经有初始化标记了。这种情况下你需要手动导入或者先删掉./mysql/data目录里的数据重新初始化但删除前务必确认数据已备份。警告rm -rf ./mysql/data是危险操作相当于删库。我建议先把整个data目录改名备份比如mv data data_bak确认新库没问题再用再删。做运维动作一定要留后路这是老生常谈但真正做到的人不多。5.2 数据库连接配置怎么写博客系统安装完成后数据库连接配置一般在源码的配置目录里通常是.env或者config/database.php。你需要把数据库地址改成mysql而不是127.0.0.1因为博客应用跑在 PHP 容器里访问的数据库是另一个容器IP 会变但服务名mysql是固定的。举个例子如果博客系统支持.env配置DB_HOSTmysql DB_PORT3306 DB_DATABASEwm_blog DB_USERNAMEwm_user DB_PASSWORDyour_strong_user_password改完之后记得重启 PHP 容器让配置生效docker compose restart php如果配置不生效多半是 PHP 有配置缓存比如 Laravel 系的config:cache需要在源码目录清理缓存。6. 容器启动顺序与依赖处理6.1 用 depends_on 控制启动顺序docker-compose 里depends_on只是控制容器启动的先后顺序不代表上游服务已经就绪。什么意思就是说mysql容器启动了但 MySQL 服务可能还在初始化中首次启动要初始化数据目录、加载插件这个过程可能持续几十秒这时候 PHP 应用去连数据库可能就连不上。解决这个问题的常用办法有两个在应用启动脚本里加一个等待逻辑循环检测数据库端口是否可连通了再启动 PHP-FPM。在 PHP 容器启动时手动写一个包装脚本内置等待逻辑。我的做法是写了一个entrypoint.sh#!/bin/bash echo Waiting for MySQL to be ready... until mysqladmin ping -h mysql -u wm_user -p${MYSQL_PASSWORD} --silent; do sleep 2 done echo MySQL is ready, starting PHP-FPM... exec php-fpm然后在 Dockerfile 里把默认的 entrypoint 替换成这个脚本COPY entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod x /usr/local/bin/entrypoint.sh ENTRYPOINT [/usr/local/bin/entrypoint.sh]mysqladmin是 MySQL 客户端工具php:8.1-fpm镜像里不一定自带需要先apt-get install -y default-mysql-client。如果你不想装客户端也可以用php -r try { new PDO(mysql:hostmysql;port3306, user, pass); echo OK; } catch (Exception $e) { exit(1); }做检测效果一样。6.2 Redis 连接同样要做降级处理缓存组件有一点好就算 Redis 挂了应用一般不会直接死掉顶多是性能下降或者 Session 失效。所以 Redis 的依赖处理可以宽松一点没必要写进 entrypoint 的等待逻辑。我在实际部署里只把 MySQL 等待写死Redis 就让它自我恢复容器重启后自动重新连接即可。7. 上线后必做的三件事备份、监控、更新7.1 数据备份的傻瓜方案很多人部署完博客就撒手不管了直到数据丢失才追悔莫及。Docker 部署有一个天然优势数据都在宿主机目录里备份起来非常直接。我用的备份方案是写一个 cron 脚本每天凌晨打包数据库和源码目录。数据库备份脚本/home/wm_blog/backup.sh#!/bin/bash BACKUP_DIR/home/wm_blog/backups DATE$(date %Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR docker exec wm_mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD} wm_blog $BACKUP_DIR/db_$DATE.sql tar -czf $BACKUP_DIR/files_$DATE.tar.gz /home/wm_blog/blog # 保留最近 7 天备份 find $BACKUP_DIR -name *.sql -mtime 7 -delete find $BACKUP_DIR -name *.tar.gz -mtime 7 -delete然后用 crontab 每天凌晨 2 点执行crontab -e 0 2 * * * /bin/bash /home/wm_blog/backup.sh这个方案我已经用了大半年从来没出过问题。重要的是备份完要测试一次恢复流程别等到真要用的时候发现备份文件是坏的。我一般每个月会手动验证一次把备份 SQL 导入一个临时数据库确认表结构和数据行数没问题。7.2 日志查看与问题定位容器化部署后日志查看方式变成docker logs。我之前排查过一个数据库连接卡死的问题docker logs wm_php --tail 100 docker logs wm_mysql --tail 100 docker logs wm_nginx --tail 100三个容器的日志对比着看基本能定位问题在哪一层。Nginx 日志主要看访问状态码和 PHP 转发是否正常PHP 日志看有没有 PHP 报错和警告MySQL 日志看连接数是否爆掉、有没有慢查询警告。如果日志太多可以先docker logs wm_nginx --since 10m只看最近 10 分钟避免刷屏式输出影响分析。7.3 镜像更新升级的正确姿势博客系统官方发了新版本怎么用 Docker 升级我的经验是先备份数据库 源码这一步不可跳过。把新版源码文件解压到/home/wm_blog/blog保留原配置文件、上传目录。重启 PHP 容器docker compose restart php。刷新博客页面确认功能正常。如果出问题把旧源码备份直接覆盖回来再重启一次即可回滚。比传统部署方式方便的地方在于Nginx 配置、PHP 扩展、数据库版本这些运行环境全部固化在镜像里升级源码时不用担心环境不匹配。真正需要升级基础镜像的时候比如 PHP 版本升级改一下镜像版本号然后docker compose up -d重建容器即可数据都在宿主机目录不会丢失。8. 常见问题速查与避坑指南8.1 网络不通问题容器之间网络不通是最常见的坑。表现形式PHP 容器连不上 MySQL报Connection refused或者Host is unreachable但明明 MySQL 容器在运行。排查思路确认两个容器是否在同一网络docker network inspect wm_blog_net查看接入的容器列表。确认 MySQL 容器内的实际端口docker exec wm_mysql netstat -tlnp | grep 3306。用 PHP 容器 ping MySQLdocker exec wm_php ping mysql。检查 MySQL 是否监听所有地址默认bind-address*是没问题的但如果你自定义了配置改成127.0.0.1容器外就访问不到。如果用的是 Windows Docker Desktop偶尔会遇到 failed to connect to the docker api at npipe 这类报错通常是 Docker Desktop 服务没正常启动重启 Docker Desktop 一般能解决。还有一种是 Windows 下把容器端口映射到宿主机后防火墙拦截导致访问不了记得在 Windows 防火墙里放行对应端口。8.2 容器启动失败排查docker compose up -d之后发现某个容器没起来第一步永远是看日志docker compose logs nginx docker compose logs mysql几个高频原因端口被占用比如宿主机 8080 已经被其他程序占用Nginx 容器起不来。换个端口映射或者停掉冲突程序。挂载目录权限不对宿主机目录不存在或者没有权限容器启动直接报mkdir: cannot create directory。配置文件语法错误Nginx 配置写错容器反复重启restart 策略导致。用docker exec wm_nginx nginx -t检查语法。8.3 数据库乱码问题博客文章出现中文乱码大部分原因是数据库字符集设置不对。MySQL 8.0 默认字符集是utf8mb4但如果你用的是旧版本的数据库导入可能会有latin1的残留。解决方式在 docker-compose 里我已经加了启动参数command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci导入数据之前确保.sql文件头部有SET NAMES utf8mb4; SET CHARACTER SET utf8mb4;导入后检查表字符集SHOW CREATE TABLE your_table_name;如果已经存在的表字符集不对可以用一条命令批量转换ALTER TABLE your_table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;8.4 Docker 镜像拉取慢的问题国内网络环境下直接从 Docker Hub 拉镜像经常慢到怀疑人生甚至直接超时。我处理过最快的办法是配置镜像加速器。Docker Desktop 在设置界面有 Docker Engine 配置项可以编辑 JSON 加registry-mirrors。Linux 服务器上则编辑/etc/docker/daemon.json{ registry-mirrors: [https://docker.m.daocloud.io] }改完重启 Docker 服务systemctl restart docker注意镜像加速器不是永远的。有时候加速器本身也不稳定我实际使用中发现多配几个备选地址比只配一个靠谱。如果某天拉取速度突然变慢可以先docker search看看镜像是否正常再换一个加速地址。8.5 容器环境下 PHP 扩展缺失博客系统安装完成后进入首页提示缺少某某函数或者某个扩展没启用这种报错在 Docker 环境里特别常见因为你用的 PHP 镜像是最精简的很多扩展默认没装。我遇到过的典型报错Call to undefined function mysqli_connect()缺mysqli扩展。Class Redis not found缺redis扩展。Call to undefined function imagecreatefrompng()缺gd扩展。解决办法就是回到 Dockerfile把缺的扩展补上然后重新构建镜像docker compose build php docker compose up -d扩展装完后可用下面命令验证docker exec wm_php php -m | grep redis docker exec wm_php php -m | grep gd docker exec wm_php php -m | grep pdo_mysql9. 我踩过的一些坑写在最后部署这套博客系统到现在我最大的体会是Docker 不是万能药但它把环境差异带来的问题压缩到了极致。以前在 A 服务器上跑得好好的换到 B 服务器就各种报错这种问题在 Docker 场景下基本绝迹。另外想提醒一点任何部署方案网上都有大量教程但别人的配置永远是参考不是答案。你能踩的坑往往就藏在环境差异和版本差异里。比如我在 Ubuntu 上安装 Docker 时遇到过一次服务启动失败排查下来是 Docker 源配置的问题而同样的步骤在 CentOS 上完全是另一套操作逻辑。所以遇到问题的时候先冷静定位再系统排查别急着把整个编排文件推倒重来。如果你也想用 Docker 部署维基萌博客系统我的建议是先把官方文档看一遍再用这篇文章的编排文件搭一套测试环境跑通之后再往生产环境迁移。数据迁移那块一定要提前演练等你需要用的时候再摸索那种压力我经历过不好受。最后再分享一个小技巧每次修改完 docker-compose.yml 或者配置文件后执行docker compose config可以校验编排文件格式是否正确这个命令能提前发现一大半语法错误省得反复重启容器试错。熟用docker compose config和docker logs你的 Docker 部署之路会顺很多。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门