草根站长通常只有一两台服务器,却要跑网站、数据库、缓存、定时任务好几个服务。以前的做法是全部装在一个系统里,依赖混在一起,升级一个组件可能把另一个搞挂;换服务器时又要重新装一遍环境,费时费力。Docker Compose 解决的就是这个问题:把每个服务装进独立的容器,用一份 YAML 文件描述整个应用栈,一条命令就能启动、停止、迁移。本文用一个个人网站的完整案例,带你从零掌握 Docker Compose。
一、Docker Compose 是什么
Docker 本身管理单个容器,而一个网站通常需要多个容器配合:Nginx 负责静态文件和反向代理,PHP-FPM 跑业务代码,MySQL 存数据,Redis 做缓存。Compose 是 Docker 官方的多容器编排工具,你只需要写一个 docker-compose.yml,把各个服务的镜像、端口、数据卷、依赖关系描述清楚,然后 docker compose up -d 就能把整套环境拉起来。相比一条条 docker run,Compose 的好处是配置即代码:换服务器时把目录拷过去,一条命令恢复整个环境,彻底告别手工装环境的噩梦。而且 Compose 文件本身就是环境文档,别人拿到文件就能复现你的整套部署,团队协作、找人帮忙排错都方便很多。
二、安装与基本概念
新版 Docker 已经把 Compose 集成成了插件,安装 Docker 后直接验证:
docker compose version
能输出版本号就说明可用。如果提示 command not found,说明 Docker 版本较老,需要单独安装 docker-compose 插件,或者直接升级 Docker。Compose 的核心是 docker-compose.yml 文件,最顶层有三个常用键:version(新版已可省略)、services(定义各个容器)、volumes 和 networks(定义数据卷和网络)。每个 service 下常用的配置项有 image(镜像)、container_name(容器名)、ports(端口映射)、volumes(数据卷挂载)、environment(环境变量)、restart(重启策略)、depends_on(依赖关系)。容器之间默认通过服务名互相访问,Compose 会自动为它们创建独立的桥接网络,隔离外部网络,既安全又省去手动配 IP 的麻烦。
三、实战:部署一个 Nginx + PHP 网站
假设你有一个 PHP 网站,传统 LNMP 环境要装 Nginx、PHP-FPM、改配置、调权限,至少折腾半小时。用 Compose 只需要两个文件。先建目录结构:
mkdir -p ~/myweb/{nginx,www}
nginx 目录放站点的配置文件 default.conf:
server {
listen 80;
root /var/www/html;
index index.php index.html;
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_pass 写的是 php:9000,这里的 php 是服务名,Compose 会自动把它解析成容器的内网 IP,容器之间不需要知道对方的 IP,这就是 Compose 网络带来的便利。www 目录放网站代码。然后写 docker-compose.yml:
services:
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
volumes:
- ./www:/var/www/html
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
restart: always
php:
image: php:8.2-fpm-alpine
volumes:
- ./www:/var/www/html
restart: always
启动:
cd ~/myweb
docker compose up -d
看到两个容器状态为 Up 就成功了,浏览器访问服务器 IP 即可打开网站。以后改代码只需要更新 ./www 目录,Nginx 和 PHP 容器通过挂载卷实时读取,无需重启。如果 PHP 站点要开 HTTPS,把 80 端口换成 443 映射,再把证书文件挂载进 Nginx 容器即可,Nginx 官方镜像本身支持 ssl 模块。这份配置连同代码一起放到 Git 里,就是一套完整的可复现环境。
四、实战:网站加数据库和缓存
动态网站离不开数据库。在 services 下追加 MySQL 和 Redis:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: "ChangeMe_StrongPassword"
MYSQL_DATABASE: myweb
MYSQL_USER: myweb_user
MYSQL_PASSWORD: "AnotherStrongPass"
volumes:
- mysql_data:/var/lib/mysql
restart: always
redis:
image: redis:7-alpine
command: redis-server --requirepass "RedisPass123"
volumes:
- redis_data:/data
restart: always
volumes:
mysql_data:
redis_data:
这里最关键的是数据卷:mysql_data 和 redis_data 声明在顶层 volumes 里,数据存在 Docker 管理的数据卷中,即使容器删了重建,数据也不会丢。千万不要把 MySQL 数据目录直接放在容器里,容器一删数据就全没了,这是新手最容易犯的致命错误。PHP 容器里要连数据库,需要在代码里把数据库地址写成 mysql、Redis 地址写成 redis,也就是服务名。如果 PHP 镜像缺 pdo_mysql 扩展,可以基于官方镜像写一个 Dockerfile 自己装:
FROM php:8.2-fpm-alpine
RUN docker-php-ext-install pdo_mysql mysqli
然后在 compose 文件里把 php 服务的 image 换成自己构建的镜像,或者用 build 指令指定 Dockerfile 路径,Compose 会自动构建。数据库密码这类敏感信息,建议用环境变量文件管理:把变量写进 .env 文件,compose 文件里用 ${MYSQL_ROOT_PASSWORD} 引用,.env 加入 .gitignore 不要提交到仓库。
五、常用命令与日常维护
Compose 的日常命令不多,记住这几个就够:
docker compose up -d # 启动全部服务(-d 后台运行)
docker compose ps # 查看各容器状态
docker compose logs -f nginx # 跟踪查看某个服务的日志
docker compose exec php sh # 进入容器执行命令
docker compose restart php # 重启某个服务
docker compose down # 停止并删除容器(数据卷默认保留)
docker compose pull # 拉取最新镜像
docker compose config # 校验配置文件并展开变量
看日志是排错的第一手段,nginx 报 502 就先 docker compose logs php 看 PHP 有没有起来;数据库连不上就先看 mysql 日志。进入容器排查时,exec 比 attach 更安全,用完 exit 退出即可。升级镜像时,先 pull 新镜像,再 up -d,Compose 会用新镜像重建变更的容器,其他容器不受影响。改完 compose 文件想确认语法对不对,先跑 docker compose config 校验,能避免因为缩进错误导致整个服务起不来。
还有一个容易忽视的问题:容器日志默认会无限增长,写满磁盘是 VPS 上常见的故障。Nginx、PHP 的访问日志如果不限制,几个月就能吃掉几个 GB。在 compose 文件里给每个服务加上日志轮转配置即可:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
这样单个日志文件超过 10MB 就自动滚动,最多保留 3 份,磁盘永远不会被日志撑爆。生产环境建议所有服务都加上这段配置,配合定时任务定期清理,磁盘告警能少一大半。
六、资源限制与重启策略
小内存 VPS 上最怕容器把内存吃光导致整个服务器卡死。Compose 支持给每个服务设置资源上限:
mysql:
image: mysql:8.0
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
memory 限制内存上限,cpus 限制 CPU 使用,MySQL 这类吃内存的服务建议至少给 512M,PHP-FPM 根据并发量给 256M 到 512M。restart 策略也很重要:restart: always 表示容器异常退出后自动重启,服务器开机后 Docker 也会自动拉起容器,基本实现了服务自愈,比裸机时代半夜爬起来重启服务强太多了。注意 MySQL 容器首次启动初始化较慢,PHP 服务如果启动得太早可能连不上数据库,可以在 depends_on 里加 condition: service_healthy,配合 healthcheck 健康检查:
mysql:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
这样 Compose 会等 MySQL 健康检查通过后才启动依赖它的服务,彻底解决启动顺序问题。如果你的 compose 版本支持,还可以用 depends_on 的 condition 语法,让 Nginx 等 PHP 就绪、PHP 等 MySQL 就绪,形成完整的依赖链。
七、备份、迁移与常见坑
数据在数据卷里,备份就要针对数据卷操作。MySQL 的备份还是用官方工具最稳妥:
docker compose exec mysql mysqldump -uroot -p myweb > backup_$(date +%F).sql
还原时把备份文件导进去即可:
docker compose exec -T mysql mysql -uroot -p myweb < backup_2026-08-27.sql
整个环境迁移更简单:把项目目录(含 docker-compose.yml 和挂载的代码目录)打包拷到新服务器,装好 Docker,docker compose up -d,再把数据库备份导入,网站就搬家完成了。想更省事,可以把备份任务写进 crontab,每天凌晨自动执行 mysqldump 并保留最近 7 天的备份,再配合云存储同步一份异地副本。
常见坑有三个:一是端口冲突,本机 80 端口被其他程序占用时,up 会报端口被占用,把 ports 映射改成 8080:80 即可,或者先停掉占用端口的程序;二是挂载目录权限,容器内进程以特定用户运行,代码目录权限不对会报 Permission denied,注意 chown 到容器用户的 uid(PHP-FPM 默认是 www-data,uid 82);三是容器时区默认是 UTC,日志时间差 8 小时,可以在 environment 里加 TZ=Asia/Shanghai,同时 MySQL 连接串里也要带上时区参数。遇到问题先看日志,docker compose logs 基本能定位九成的问题,别一上来就重启容器,那样反而把现场破坏了。
八、写在最后
Docker Compose 把繁琐的环境搭建变成了文件管理,特别适合一个人维护多个站点的草根站长。先把上面这个 Nginx + PHP + MySQL + Redis 的栈跑通,再逐步加入自己的服务,你会发现自己再也不怕换服务器,也不怕把环境搞坏了——大不了 docker compose down 重来。建议把 compose 文件纳入 Git 管理,每次改动留个记录,这是性价比最高的运维投资。等站点规模再大一些,还可以进一步学习 Docker Swarm 或 K8s,但那是后话,先把 Compose 用熟,已经能覆盖绝大多数个人站点的需求了。