Docker Compose 多服务编排实战:从开发环境到生产部署

你是否经历过这样的场景:本地开发一个 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 — 重启策略,生产环境建议设为 alwaysunless-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% 的常见配置问题。

发表评论