你是否经历过这样的场景:本地开发一个 Web 项目,需要手动启动 MySQL、Redis、Nginx,每开一个终端窗口运行一条命令,端口冲突了还要逐一排查?上线部署时又得在服务器上重来一遍,稍有遗漏就导致服务起不来。这种”手动挡”式的服务管理方式,在微服务架构日益普及的今天已经完全跟不上节奏了。Docker Compose 正是为解决这个痛点而生——用一个 YAML 文件定义所有服务的依赖关系、网络配置和启动参数,一条命令就能把整套环境拉起来。本文将通过一个完整的实战案例,带你从零掌握 Docker Compose 多服务编排,最终部署到生产 [VPS](https://cn.hostease.com/vps/) 上。
一、Docker Compose 基础概念与 YAML 语法
1.1 什么是 Docker Compose
Docker Compose 是 Docker 官方提供的多容器编排工具。它通过一个 docker-compose.yml(或 compose.yaml)文件,用声明式的方式描述一组相关联的服务,然后通过 docker compose up 一键创建并启动所有容器。相比逐个运行 docker run,Compose 有三个核心优势:
- 声明式配置:所有服务定义在一个文件里,版本可控、团队可共享
- 自动网络管理:同一 Compose 项目中的服务自动加入同一个网络,服务名即为 DNS 主机名
- 依赖与生命周期管理:支持
depends_on控制启动顺序,docker compose down一键清理
1.2 YAML 文件结构速览
一个标准的 docker-compose.yml 包含以下几个顶级配置块:
# compose.yaml 基本结构
services: # 定义各个服务容器
web:
image: nginx:alpine
ports:
- "80:80"
volumes: # 声明命名卷,用于持久化数据
db_data:
networks: # 自定义网络(可选)
app_net:
services 是最核心的部分。每个服务下常用的配置项包括:
image— 指定镜像,或用build从 Dockerfile 构建ports— 端口映射,格式宿主机:容器volumes— 挂载目录或命名卷environment/env_file— 环境变量depends_on— 声明依赖关系restart— 重启策略,生产环境建议设为always或unless-stopped
如果你还没有安装 Docker 和 Compose,可以参考 我们的 VPS 技术教程 中的安装指南,在你的服务器上快速完成环境准备。
二、实战编排 — Web 应用 + MySQL + Redis + Nginx 反代
2.1 项目目录结构
我们以一个典型的 PHP/Laravel 项目为例,编排以下四个服务:
- app — PHP-FPM 应用容器
- db — MySQL 8.0 数据库
- redis — Redis 7 缓存
- nginx — Nginx 反向代理,对外暴露 80/443 端口
项目目录结构如下:
my-project/
├── docker-compose.yml
├── .env
├── .env.production
├── nginx/
│ └── default.conf
├── src/ # 项目源码
│ └── ...
└── Dockerfile
2.2 编写完整的 docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: myapp-php
restart: unless-stopped
volumes:
- ./src:/var/www/html
environment:
- DB_HOST=db
- DB_PORT=3306
- DB_DATABASE=${DB_NAME}
- DB_USERNAME=${DB_USER}
- DB_PASSWORD=${DB_PASS}
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- app_net
db:
image: mysql:8.0
container_name: myapp-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASS}
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASS}
volumes:
- db_data:/var/lib/mysql
ports:
- "127.0.0.1:3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app_net
redis:
image: redis:7-alpine
container_name: myapp-redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASS}
volumes:
- redis_data:/data
ports:
- "127.0.0.1:6379:6379"
networks:
- app_net
nginx:
image: nginx:alpine
container_name: myapp-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./src:/var/www/html
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./certbot/conf:/etc/letsencrypt:ro
- ./certbot/www:/var/www/certbot:ro
depends_on:
- app
networks:
- app_net
volumes:
db_data:
redis_data:
networks:
app_net:
driver: bridge
2.3 Nginx 反向代理配置
创建 nginx/default.conf,将 PHP 请求转发到 app 容器的 9000 端口:
server {
listen 80;
server_name yourdomain.com;
root /var/www/html/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
2.4 启动与验证
在项目根目录执行:
# 首次构建并启动(后台运行)
docker compose up -d --build
# 查看所有容器状态
docker compose ps
# 查看日志(实时跟踪)
docker compose logs -f app
# 进入 app 容器执行迁移等操作
docker compose exec app php artisan migrate
启动后,访问服务器的 80 端口即可看到应用页面。MySQL 数据存储在 db_data 命名卷中,即使容器重建也不会丢失。
三、开发环境 vs 生产环境的配置差异
3.1 用 .env 文件隔离环境变量
开发和生产的数据库密码、域名、调试开关等配置完全不同。Compose 原生支持 .env 文件,我们可以通过 env_file 指令区分不同环境:
# .env(开发环境,提交到 .gitignore)
DB_NAME=myapp_dev
DB_USER=dev
DB_PASS=dev123
MYSQL_ROOT_PASS=root123
REDIS_PASS=
APP_DEBUG=true
# .env.production(生产环境,通过 CI/CD 或手动创建)
DB_NAME=myapp_prod
DB_USER=prod_user
DB_PASS=S3cur3P@ssw0rd!
MYSQL_ROOT_PASS=R00tS3cur3!
REDIS_PASS=R3d1sP@ss!
APP_DEBUG=false
生产部署时指定环境文件:
# 使用 .env.production 覆盖默认 .env
docker compose --env-file .env.production up -d
3.2 用 override 文件实现环境差异化
更优雅的做法是使用 Compose 的多文件合并机制。基础配置写在 compose.yaml,开发特有的配置放在 compose.override.yaml(Compose 自动加载),生产配置放在 compose.prod.yaml:
# compose.override.yaml — 开发环境专用
services:
app:
build:
target: development # Dockerfile 多阶段构建的开发阶段
volumes:
- ./src:/var/www/html # 挂载源码,支持热更新
environment:
- APP_DEBUG=true
- XDEBUG_MODE=develop
db:
ports:
- "127.0.0.1:3306:3306" # 开发时暴露端口方便 Navicat 连接
# compose.prod.yaml — 生产环境专用
services:
app:
build:
target: production # 生产阶段,不含调试工具
volumes:
- app_storage:/var/www/html/storage # 不挂载本地代码
environment:
- APP_DEBUG=false
restart: always
db:
ports: [] # 生产环境不暴露数据库端口
restart: always
redis:
restart: always
生产部署时显式指定文件:
docker compose -f compose.yaml -f compose.prod.yaml up -d
3.3 volumes 与开发热更新
开发环境中,将本地源码目录挂载到容器内,代码修改后立即生效,无需重新构建镜像:
volumes:
- ./src:/var/www/html # 绑定挂载,本地改代码容器里立刻看到
生产环境中则应使用命名卷或直接将代码 COPY 到镜像内,避免绑定挂载带来的性能损耗和安全风险。
四、部署到 VPS 的完整流程
4.1 服务器环境准备
选择一台 Linux VPS(推荐 Ubuntu 22.04 或 Debian 12),像 Hostease 的 VPS 方案 提供了灵活的配置和快速部署能力,非常适合运行 Docker 容器化应用。登录服务器后执行:
# 更新系统包
sudo apt update && sudo apt upgrade -y
# 安装 Docker(官方脚本一键安装)
curl -fsSL https://get.docker.com | sudo sh
# 将当前用户加入 docker 组(免 sudo)
sudo usermod -aG docker $USER
newgrp docker
# 验证安装
docker --version
docker compose version
4.2 部署代码到服务器
# 克隆项目代码
git clone https://github.com/yourname/my-project.git
cd my-project
# 创建生产环境变量文件
cp .env.production.example .env.production
# 编辑 .env.production,填入真实的数据库密码和域名
nano .env.production
4.3 配置 SSL 证书(HTTPS)
使用 Certbot 自动申请 Let’s Encrypt 免费证书:
# 创建证书目录
mkdir -p certbot/conf certbot/www
# 先用 HTTP 模式启动 Nginx(临时配置)
docker compose -f compose.yaml -f compose.prod.yaml up -d nginx
# 申请证书
docker run --rm -v "$(pwd)/certbot/conf:/etc/letsencrypt" \
-v "$(pwd)/certbot/www:/var/www/certbot" \
certbot/certbot certonly --webroot \
-w /var/www/certbot -d yourdomain.com --email you@email.com --agree-tos
4.4 生产启动与健康检查
# 构建镜像并后台启动所有服务
docker compose -f compose.yaml -f compose.prod.yaml up -d --build
# 检查所有容器状态,确保都是 running
docker compose ps
# 检查应用日志确认无报错
docker compose logs --tail=50 app
# 测试数据库连接
docker compose exec db mysql -u root -p -e "SHOW DATABASES;"
4.5 设置自动续期与定时备份
# Certbot 自动续期(加入 crontab)
echo "0 3 * * 1 docker run --rm -v $(pwd)/certbot/conf:/etc/letsencrypt \
-v $(pwd)/certbot/www:/var/www/certbot \
certbot/certbot renew && docker compose restart nginx" | crontab -
# MySQL 每日自动备份
echo "0 2 * * * docker compose exec -T db mysqldump -u root \
-p\$MYSQL_ROOT_PASS --all-databases | gzip > \
/backup/mysql_\$(date +\%Y\%m\%d).sql.gz" | crontab -
4.6 常用运维命令速查
docker compose ps— 查看服务状态docker compose logs -f [服务名]— 实时查看日志docker compose restart [服务名]— 重启单个服务docker compose down— 停止并移除容器docker compose pull && docker compose up -d— 拉取新镜像并更新docker system prune -f— 清理无用镜像和缓存,释放磁盘空间
如果你是第一次接触容器化部署,建议先阅读 VPS 使用教程 了解基础环境配置。
更多服务器运维技巧,可以参考 服务器运维指南 中的 Docker 系列文章。
常见踩坑与解决方案
在实际使用 Docker Compose 进行多服务编排时,即使是经验丰富的开发者也容易遇到一些棘手问题。以下是几个高频踩坑场景及对应的解决方案。
容器启动顺序与服务依赖
很多新手会误以为 depends_on 能保证数据库完全就绪后再启动应用。实际上,depends_on 只保证容器启动,不保证服务内部初始化完成。解决方案是使用 healthcheck 配合 condition: service_healthy,让 Compose 真正等待服务健康检查通过。
环境变量管理与安全
开发环境和生产环境的数据库密码、API 密钥往往不同。如果把敏感信息直接写在 docker-compose.yml 中提交到 Git 仓库,一旦泄露后果严重。推荐使用 .env 文件配合 env_file 指令,将敏感配置与代码分离,并在 .gitignore 中排除 .env 文件。
磁盘空间与日志管理
长时间运行的 Docker 环境会产生大量容器日志和无用镜像,逐渐耗尽磁盘空间。建议在 Docker 守护进程配置中启用日志轮转策略,限制单个容器的日志文件大小。同时定期执行清理命令移除悬空镜像和停止的容器。
总结与行动建议
Docker Compose 让多服务应用的开发和部署变得可重复、可维护。通过本文的实战配置,你已经掌握了从 YAML 编写、环境隔离、到生产部署的完整链路。总结几个关键原则:
- 开发用绑定挂载,生产用镜像内置——兼顾开发效率与运行安全
- 敏感信息用 .env 文件管理——永远不要把密码写进 compose 文件并提交到 Git
- 生产环境配置 restart: always——确保服务器重启后容器自动恢复
- 善用 healthcheck——让
depends_on真正等待服务就绪,而非仅仅容器启动
现在就打开你的终端,用 docker compose up -d 启动你的第一个多服务编排项目吧。如果你正在寻找一台稳定可靠的 VPS 来托管 Docker 应用,Hostease 提供多种配置的 VPS 方案,支持一键部署 Docker 环境,助你快速上线。
建议将本文收藏为部署清单,每次上线新项目时对照检查,可以避免 90% 的常见配置问题。