4. Docker Compose:从单容器到多服务编排

说实话,Docker 用顺手之后,你很快就会发现一个问题——单容器跑着挺爽,但真实项目哪有这么简单?一个 Web 应用背后,通常连着数据库、缓存、消息队列……手动一个个启动容器,再配置网络、管理依赖,那感觉就像在玩杂耍。

这时候,Docker Compose 就派上用场了。我个人习惯把它叫做「一键启动全家桶」。你只需要写一个 YAML 文件,把服务、网络、卷都定义好,一条命令就能拉起整个应用栈。

4.1 Compose 文件编写:YAML 里的「服务说明书」

Compose 文件的核心,说白了就是告诉 Docker:「我要跑哪些服务,它们之间怎么通信,数据存哪里。」

先看一个最基础的例子:

version: '3.8'

services:
  web:
    image: nginx:latest
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

嗯,这里要注意几个关键点:

  • version:指定 Compose 文件格式版本。3.8 是目前比较稳定的版本,兼容性好。
  • services:定义所有容器服务。每个服务就是一个容器。
  • volumes:声明持久化存储卷。数据库这类有状态服务,数据必须持久化。
我的小习惯: 我一般会在项目根目录放一个 .env 文件,把密码、端口这些敏感信息抽离出来。Compose 文件里用 ${VAR_NAME} 引用,既安全又灵活。

4.2 多容器应用编排:让服务「认识彼此」

多容器编排最核心的问题是什么?是服务发现。容器 A 怎么找到容器 B?

在 Compose 里,这个问题被优雅地解决了——服务名就是主机名。你想想看,web 服务要连接数据库,直接写 db:3306 就行了,不用管 IP 地址。

来看一个完整的 Web + DB 应用示例:

version: '3.8'

services:
  web:
    build: ./web
    ports:
      - "5000:5000"
    depends_on:
      - db
    environment:
      DB_HOST: db
      DB_USER: app_user
      DB_PASSWORD: ${DB_PASSWORD}

  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pg_data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  pg_data:

这里有个细节——depends_on。它确保 db 服务先启动,web 再启动。但注意,它只保证启动顺序,不保证数据库已经就绪。我在项目中遇到过好几次,web 启动时数据库还没初始化完成,导致连接失败。

避坑指南: 我曾经因为 depends_on 的「假启动」问题,排查了整整一下午。解决方案是在应用代码里加重试机制,或者用 wait-for-it.sh 脚本等待端口就绪。

4.3 Compose 常用命令:每天都会敲的那些

说实话,Compose 的命令不多,但每个都很实用。我整理了一份常用命令表:

命令 作用 我的使用场景
docker-compose up -d 后台启动所有服务 日常开发,一键拉起环境
docker-compose down 停止并移除所有容器 清理环境,重新开始
docker-compose logs -f 实时查看日志 调试时盯着看,特别有用
docker-compose ps 查看服务状态 快速确认哪些服务在跑
docker-compose exec 进入运行中的容器 比如 exec web bash 进容器排查
docker-compose build 重新构建镜像 代码改了,需要重新打包

你可能会问:upstart 有什么区别?up 会创建并启动容器,start 只启动已经存在的容器。我一般只用 up,省心。

4.4 Compose 实战:Web + DB 应用完整部署

光说不练假把式。咱们来一个完整的实战——部署一个 Flask Web 应用 + PostgreSQL 数据库。

先看项目结构:

myapp/
├── docker-compose.yml
├── web/
│   ├── Dockerfile
│   ├── app.py
│   └── requirements.txt
└── db/
    └── init.sql

第一步:写 Dockerfile

FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]

第二步:写 Compose 文件

version: '3.8'

services:
  web:
    build: ./web
    ports:
      - "8000:5000"
    environment:
      DB_HOST: db
      DB_NAME: myapp
      DB_USER: myuser
      DB_PASSWORD: mypass
    depends_on:
      - db

  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypass
    volumes:
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

第三步:启动

docker-compose up -d

就这么简单。等几秒钟,访问 http://localhost:8000,你的应用就跑起来了。

核心要点:
  • 数据库初始化脚本放在 /docker-entrypoint-initdb.d/ 目录下,容器首次启动时会自动执行
  • Web 服务通过环境变量获取数据库连接信息,不要硬编码
  • 数据卷 pgdata 保证数据库重启后数据不丢失

我记得有一次,团队新来的同事把数据库密码直接写死在代码里,结果上线前才发现要改十几个文件。从那以后,我强制要求所有项目必须用环境变量或 .env 文件管理配置。

好了,这一章的内容就到这里。Docker Compose 的核心思想就是「声明式管理」——你描述想要的状态,Compose 负责实现。下一章我们会聊聊更复杂的编排场景,比如多环境配置和扩展服务。

额外提醒: 生产环境不建议直接用 Compose,它更适合开发和测试。生产环境请考虑 Docker Swarm 或 Kubernetes。但 Compose 的编排思想,在 Kubernetes 里同样适用。