为什么个人站长也需要 Docker
很多个人站长对 Docker 的第一印象是"学习成本高、用不上"。但当你经历过一次服务器迁移之后,就会明白容器化的价值:以前换服务器,要重新装 Nginx、装 PHP、装 MySQL、改配置、迁移数据,折腾一整天还容易出错;用 Docker 之后,一个 docker-compose.yml 文件加上一个数据目录,十分钟就能在新服务器上把整套环境跑起来,而且版本完全一致,不存在"在我机器上好好的"这种问题。这篇文章面向零基础的站长,手把手带你用 Docker 部署一个典型的 PHP 网站(Nginx + PHP-FPM + MySQL),并加上自动备份方案。
一、安装 Docker 与 Docker Compose
以 Ubuntu 22.04 为例,安装 Docker 官方源里的版本最省心:
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin装好后执行 sudo docker run hello-world 验证。为了让当前用户免 sudo 执行 docker 命令,把用户加入 docker 组:sudo usermod -aG docker $USER,然后重新登录终端生效。注意,把用户加入 docker 组等同于授予 root 权限,生产环境要谨慎,个人服务器上问题不大。
二、编写 docker-compose.yml
我们用一个 docker-compose.yml 把三个服务编排起来:Nginx 负责接收 HTTP 请求并转发给 PHP-FPM,PHP-FPM 负责解析 PHP 代码,MySQL 负责存储数据。目录结构建议如下:
~/www/
├── docker-compose.yml
├── nginx/
│ └── conf.d/
│ └── site.conf
├── php/ # 挂载 PHP 代码
└── mysql/ # MySQL 数据目录(会自动创建)docker-compose.yml 的内容:
version: "3.8"
services:
nginx:
image: nginx:1.25-alpine
container_name: web_nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./php:/var/www/html:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- php
restart: unless-stopped
php:
image: php:8.2-fpm-alpine
container_name: web_php
volumes:
- ./php:/var/www/html
restart: unless-stopped
mysql:
image: mysql:8.0
container_name: web_mysql
environment:
MYSQL_ROOT_PASSWORD: "改成你自己的强密码"
MYSQL_DATABASE: myblog
MYSQL_USER: myblog
MYSQL_PASSWORD: "改成你自己的强密码"
volumes:
- ./mysql:/var/lib/mysql
restart: unless-stopped几个关键点:第一,restart: unless-stopped 保证服务器重启或容器崩溃后自动拉起,个人服务器没有监控告警,这个配置能省很多事;第二,MySQL 的数据一定要挂载到宿主机目录(./mysql),否则容器一删数据全没;第三,alpine 版本的镜像体积小,Nginx 镜像才几十 MB,比 centos 版小好几倍。
三、配置 Nginx 站点
nginx/conf.d/site.conf 的内容和传统部署基本一样,只是 fastcgi_pass 要指向 php 容器名和端口:
server {
listen 80;
server_name www.example.com example.com;
root /var/www/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000; # 关键:容器名 php
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\. {
deny all;
}
}在 compose 网络里,服务名(nginx、php、mysql)就是容器的主机名,容器之间可以互相解析,所以 fastcgi_pass php:9000 能直接连通 PHP 容器,不需要写 IP 地址。这也是容器化部署比传统部署优雅的地方:服务之间的通信关系由编排文件显式定义,一目了然。
四、启动与常用命令
一切就绪后,在 ~/www 目录下执行:
docker compose up -d # 后台启动所有服务
docker compose ps # 查看服务状态
docker compose logs -f nginx # 跟踪查看 Nginx 日志
docker compose restart php # 重启某个服务
docker compose down # 停止并删除容器(数据卷保留)第一次启动会从镜像仓库拉取镜像,根据网速需要几分钟。启动后用 docker compose ps 确认三个服务都是 Up 状态,浏览器访问服务器 IP 或域名,能看到 PHP 探针或站点首页就说明部署成功了。这里提醒一句:如果想用现成的 CMS,直接把程序文件放到 ./php 目录再访问,比手工编译 LAMP 环境快得多,这也是很多站长选择 Docker 的直接原因。
五、自动备份:站长最不能省的一步
容器化部署有个好处:备份变得非常简单。数据库用 mysqldump 导出,网站文件直接打包,然后传到异地(比如另一个对象存储或另一台服务器)。写一个备份脚本 backup.sh:
#!/bin/bash
BACKUP_DIR=/root/backups
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
# 备份 MySQL 数据库
docker exec web_mysql mysqldump -uroot -p'数据库密码' myblog | gzip > $BACKUP_DIR/db_$DATE.sql.gz
# 备份网站文件
tar czf $BACKUP_DIR/www_$DATE.tar.gz -C ~/www php
# 删除 7 天前的旧备份,防止磁盘被撑爆
find $BACKUP_DIR -name "*.gz" -mtime +7 -delete然后用 crontab -e 添加定时任务,每天凌晨 3 点执行:
0 3 * * * /root/backup.sh >> /var/log/backup.log 2>&1这里有个实战经验:备份脚本一定要先手动执行一遍确认没问题,再挂到 cron 里。我见过不少站长写了备份脚本却从没验证过,等到服务器出问题时才发现备份文件是坏的,那才是最绝望的时刻。建议每月手动恢复一次备份,确认数据完整可用。
六、容器维护:磁盘清理与镜像管理
容器用久了会发现磁盘空间悄悄变小,这是 Docker 的一个老问题:每次构建镜像、更新镜像都会留下旧版本,悬空镜像(dangling image)和停止的容器越积越多。定期清理非常必要:
docker system df # 查看磁盘占用情况
docker system prune -f # 清理悬空镜像、停止的容器、无用网络
docker system prune -a --volumes # 深度清理,注意会删除所有未使用的镜像和数据卷注意最后一条命令要谨慎使用,--volumes 会连同没被容器使用的数据卷一起删掉,如果备份数据放在 Docker 数据卷里,一定要确认没有误删。更稳妥的做法是把这条清理命令挂到 cron 里,每周执行一次 docker system prune -f 就足够了。另外建议给日志加上轮转限制:在 /etc/docker/daemon.json 里配置 log-driver 的 max-size 和 max-file,防止某个容器日志无限增长把磁盘写满:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}七、两个容易忽略的细节:时区与文件权限
容器默认使用 UTC 时区,如果程序里打印日志、生成文件名用到了时间,你会发现时间比北京时间慢了 8 个小时。虽然不影响功能,但排查问题时非常别扭。解决方法是给容器挂载时区文件,或者在 compose 里设置环境变量:
services:
php:
environment:
- TZ=Asia/Shanghai
volumes:
- /etc/localtime:/etc/localtime:ro再一个是文件权限问题。PHP 容器默认以 www-data 用户运行,而你在宿主机上用 root 拷贝进去的代码文件属主是 root,权限不对会导致程序无法写入缓存目录或者上传目录,表现就是"网页能打开但一上传图片就报错"。遇到这种问题,进入容器看看运行用户,再在宿主机上把对应目录的属主改掉:
docker exec -it web_php id # 查看容器内用户
sudo chown -R 1000:1000 ./php/upload # 按容器内 uid 调整宿主机目录属主很多人在这上面折腾一下午,最后发现只是权限问题。建议部署初期就把上传目录、缓存目录的权限测试一遍,避免上线后被用户发现上传失败。
八、日常更新:如何安全升级容器版本
镜像不是装完就一劳永逸的,基础镜像和软件版本会不断发布安全更新。升级的思路是"先拉新镜像、再重建容器、最后回滚兜底":
docker compose pull # 拉取最新的镜像
docker compose up -d # 用新镜像重建容器
docker compose ps # 确认服务恢复正常
docker compose images # 查看当前使用的镜像版本升级前务必先备份数据库和网站文件,这是所有运维操作的第一原则。升级后发现不兼容,可以用 docker compose down 停掉服务,然后修改 docker-compose.yml 把镜像版本改回旧版,再 up -d 启动,就完成了回滚。个人站长的原则是:不要追新,稳定优先,安全补丁跟上就行,大版本升级选在流量低的时间段做。
九、常见故障排查
容器化部署最常见的三个问题:第一,容器启动后立刻退出,用 docker logs 容器名 查看日志,十有八九是配置错误或端口冲突;第二,页面返回 502 Bad Gateway,通常是 PHP-FPM 容器没起来,或者 fastcgi_pass 写错了主机名,用 docker compose ps 确认 php 容器是 Up 状态;第三,数据库连接失败,检查 MySQL 容器环境变量里的数据库名、用户名、密码是否和程序配置一致,注意 MySQL 8 默认的认证插件是 caching_sha2_password,个别老程序不支持,可以在环境变量里加 MYSQL_ROOT_HOST 或者改用 mysql_native_password。掌握了这几个排查思路,大部分问题都能自己解决。
十、总结
Docker 对个人站长来说不是负担,而是解放。它让环境部署从"手工作坊"变成了"一键复制",让迁移和备份变得清晰可靠,也让服务器维护从"凭感觉"变成了"看配置文件"。建议所有还在手工编译环境的站长都试试容器化,从本文的 docker-compose 模板开始,跑通一个 PHP 站点,再逐步加上 Redis、Cron 等容器,你会发现自己省下来的时间远超学习 Docker 投入的时间。下一篇打算写 Docker 下 HTTPS 证书的自动续期(certbot + acme.sh)实战,感兴趣的话欢迎留言交流。