OFFSET偏移
← 文章目录OFFSET / FIELD NOTESNO. 113

Compose:定义多服务应用

用服务、依赖、profiles、watch 和 secrets 管理单机应用。

所在专题Docker:从容器运行到应用编排 ↗
约 13 分钟阅读开始阅读

用一个 YAML 文件定义多个容器,一个命令启动整个应用栈。本篇采用 Compose v2 风格的 docker compose CLI;具体 profiles/watch/develop 功能要求对应版本,第三方兼容实现应逐项验证。


Compose 是什么?#

# 没有 Compose:手动逐条启动
docker network create appnet
docker run -d --name db --network appnet -e POSTGRES_PASSWORD_FILE=/run/secrets/db_password --mount type=bind,src=/path/to/protected-password-file,dst=/run/secrets/db_password,readonly -v pgdata:/var/lib/postgresql/data postgres:16-alpine
docker run -d --name api --network appnet -e DB_HOST=db -p 3000:3000 myapi
docker run -d --name web -p 80:80 --network appnet nginx

# 有 Compose:一个文件 + 一个命令
docker compose up -d

compose.yaml 结构#

# compose.yaml(默认文件名)
# 注意:v2 不再需要 version: 字段!

services:                    # 所有服务定义
  db:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD:?provide a local database password}   # 插值示意;生产考虑 secret 文件
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 10
      start_period: 30s

  api:
    build: ./api              # 从 ./api/Dockerfile 构建(而不是拉现成镜像)
    restart: always
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@db:5432/app
    depends_on:
      db:
        condition: service_healthy   # 等 db 健康检查通过后再启 api
    develop:                  # watch mode(compose v2.22+)
      watch:
        - action: sync
          path: ./api/src
          target: /app/src

  web:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - api

volumes:
  pgdata:                    # 声明命名卷

常用命令#

# 启动(构建 + 创建 + 启动所有服务)
docker compose up -d
#               └┘ 后台模式

# 启动并重新构建(代码改了需要重新 build)
docker compose up -d --build

# 只启动指定服务 + 依赖
docker compose up -d api

# 查看所有服务状态
docker compose ps

# 查看日志
docker compose logs -f        # 所有服务
docker compose logs -f api    # 只看 api

# 在运行中的服务里执行命令
docker compose exec api bash

# 停止所有服务
docker compose stop

# 停止并删除容器、网络(保留卷)
docker compose down

# 还删除声明的非 external 命名卷及匿名卷;先备份,external 卷不由 down 删除
docker compose down -v

# 重启单个服务
docker compose restart api

# 拉取所有服务的最新镜像
docker compose pull

Profiles:按场景启停服务#

同一份 compose 文件,按需启用不同服务:

services:
  api:
    image: myapp:latest     # 没有 profiles → 总是启动

  db:
    image: postgres:16-alpine
    profiles: [full, production]  # 只在指定 profile 下启动

  redis:
    image: redis:alpine
    profiles: [production]

  pgadmin:
    image: dpage/pgadmin4
    profiles: [tools]           # 开发时才用的管理工具

  mailhog:
    image: mailhog/mailhog
    profiles: [dev]             # 开发时捕获邮件
# 默认只启动无 profile 的服务(api)
docker compose up -d

# 启动 full profile 下所有服务(api + db)
docker compose --profile full up -d

# 启动多个 profile
docker compose --profile dev --profile tools up -d

Watch Mode:开发时自动同步#

Compose v2.22+ 可使用 watch;sync+restart 需 2.23.0+,其他动作按对应文档核对:

services:
  api:
    build: .
    develop:
      watch:
        - action: sync          # 文件变了 → 直接同步到容器(快)
          path: ./src
          target: /app/src

        - action: rebuild       # package.json 变了 → 重新构建镜像(慢)
          path: ./package.json  # rebuild 使用构建上下文,不需 target

        - action: sync+restart  # 配置文件变了 → 同步 + 重启容器
          path: ./config
          target: /app/config
docker compose up --watch
# 修改 ./src 代码 → 自动同步到容器
# 修改 package.json → 自动重新构建

环境变量与 Secrets#

.env 文件

下面是不可直接执行的占位格式;真实凭据应由受控文件或秘密管理工具提供,不能把它们写入公共示例。

# .env(放在 compose.yaml 同级目录,加入 .gitignore)
DB_PASSWORD=<从受控凭据来源提供>
API_KEY=<从受控凭据来源提供>
# compose.yaml 中引用
services:
  db:
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}   # 自动从 .env 读取

Compose 文件 secrets 与 Swarm secrets

# 以下 external secret 由 Swarm 管理;普通 Compose 可改用 file: 来源
secrets:
  db_password:
    external: true       # 预先用 docker secret create 创建

services:
  db:
    secrets:
      - db_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password

覆盖文件(Override)#

多环境管理:基础文件 + 环境覆盖文件。

# compose.yaml(基础配置)
services:
  api:
    image: myapp:latest
    ports:
      - "3000:3000"
  db:
    image: postgres:16-alpine
# compose.prod.yaml(生产环境覆盖)
services:
  api:
    restart: always
    deploy:
      resources:
        limits:
          memory: 512M
    environment:
      NODE_ENV: production

  db:
    restart: always
    volumes:
      - prod-pgdata:/var/lib/postgresql/data

volumes:
  prod-pgdata:
# 开发环境(只用基础文件)
docker compose up -d

# 生产环境(基础 + 覆盖)
docker compose -f compose.yaml -f compose.prod.yaml up -d

生产环境使用建议#

详细生产实践见 第六篇 生产部署实践。关键原则:

原则 做法
不要用 latest 标签 固定版本或 digest
选择合适的 restart 策略 处理退出;单机 unhealthy 状态不自动触发 restart
设置健康检查 配合 depends_on condition: service_healthy
限制资源 deploy.resources.limits
用覆盖文件管理环境 -f compose.yaml -f compose.prod.yaml
审查后使用 --remove-orphans 会删除本项目未再声明的服务容器,先确认数据和范围

普通 Compose 也支持 secrets

secrets:
  db_password:
    file: ./protected/db_password.txt
services:
  db:
    image: postgres:16-alpine
    secrets: [db_password]
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password

普通 Compose 从受控源文件挂载 secret,不自动获得 Swarm 的 Raft 加密和分发机制;保护源文件与部署权限。.env 用于插值,不会自动把所有变量注入容器,也不是加密凭据存储,docker compose config 输出可能包含插值后的秘密,避免公开。

启动与依赖边界

短格式 depends_on 只排序启动,不保证应用已经就绪;健康条件等待检查成功,但不持续保证依赖之后永远健康。连接重试和故障处理仍由应用承担。无 profile 的服务默认启用,显式指定带 profile 的服务还会启动该服务及必要依赖,并不自动启用同 profile 的所有其他服务。固定 host ports 的服务不能简单扩成多个副本。

profiles、override 和 secret 各节是独立配置片段,需合并完整服务的镜像、数据库凭据、健康工具和端口设置后才可运行;此处不是经过运行验证的完整项目。

参考与延伸

  1. Compose secrets ↗
  2. Compose watch ↗
  3. Compose 启动顺序 ↗
  4. Compose down ↗
  5. Compose develop 版本要求 ↗

根据技术笔记重新整理,示例使用通用名称。

沿专题继续阅读

Docker:从容器运行到应用编排

  1. 01Docker 基础:镜像、容器与守护进程
  2. 02Docker 命令行:镜像、容器、卷与网络
  3. 03Dockerfile 与镜像构建
  4. 04Compose:定义多服务应用
  5. 05Docker 网络与存储
  6. 06Docker 生产实践:资源、安全与恢复
  7. 07NixOS 上声明 Docker 与容器
  8. 08Swarm:从服务副本到集群编排
  9. 09Docker 与 Podman:架构、兼容与共存
CONTINUE READING / 下一篇Docker 网络与存储

选择网络模式和持久化方式,明确暴露端口与数据生命周期。