Docker Compose 实战指南

Docker Compose 将多容器应用的配置集中到一份 YAML 文件中,通过 docker compose up/down 一键创建或销毁整个运行环境,确保团队成员在相同配置下获得一致的开发、测试体验。

1. V1 与 V2 版本差异

特性docker-compose (V1)docker compose (V2)
状态已停止维护 (2023.06)当前标准
语言PythonGo
集成独立二进制Docker CLI 插件
性能一般启动更快,并发效率更高

V2 用 Go 重写,与 Docker 引擎深度集成,直接复用 CLI 上下文和认证机制。V1 已于 2023 年 6 月停止维护,新项目一律使用 V2。

在选择工具之前,先厘清 Compose 的定位边界。

2. 与 Kubernetes 对比

特性docker runDocker ComposeKubernetes
定位单容器运行单机多容器编排集群容器编排
管理方式命令式声明式 YAML声明式 YAML
伸缩性手动单机伸缩自动水平伸缩
适用场景快速测试开发、测试、小型部署大规模生产环境

Compose 的边界是单机多容器编排。需要跨节点调度、自动伸缩时,才需要迁移至 Kubernetes。

3. 核心配置结构

docker-compose.yml 由三个顶层块组成:services 定义各容器,networks 配置服务间网络,volumes 管理持久化存储。

3.1 配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# version 字段在 V2+ 规范中可选,通常省略
services:
webapp:
build: .
ports:
- "8080:80"
volumes:
- .:/app
environment:
- REDIS_HOST=cache
depends_on:
- cache

cache:
image: "redis:alpine"

执行 docker compose up 后:

  1. Compose 解析配置文件
  2. 根据 depends_on 先启动 cache 服务
  3. 构建 webapp 镜像并启动容器
  4. 创建默认网络,webapp 通过主机名 cache 访问 Redis

⚠️ 卷挂载路径格式为 主机路径:容器路径(左主右容),写反会导致数据挂载异常。

3.2 环境变量管理

敏感信息存入 .env 文件,Compose 启动时自动加载:

1
2
# .env
POSTGRES_PASSWORD=mysecretpassword
1
2
3
4
5
services:
db:
image: postgres
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}

3.3 启动顺序与健康检查

depends_on 只保证容器启动顺序,不保证服务就绪。需配合 healthcheck 使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
services:
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5

webapp:
depends_on:
db:
condition: service_healthy

depends_on + healthcheck 的完整启动判定逻辑:

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart TD
    A["docker compose up"] --> B["启动 db 容器"]
    B --> C{"healthcheck 通过?"}
    C -->|"是"| D["启动 webapp 容器"]
    C -->|"否, 重试中"| E["等待 interval 后重试"]
    E --> C
    C -->|"超过 retries"| F["标记 unhealthy, webapp 不启动"]
    D --> G["所有服务就绪"]

    classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef success fill:#10B981,stroke:#059669,color:#fff
    classDef warning fill:#F59E0B,stroke:#D97706,color:#fff
    classDef danger fill:#EF4444,stroke:#DC2626,color:#fff

    class A primary
    class G success
    class E warning
    class F danger

3.4 Profiles 按需启动

为特定场景的服务分配 profile,仅在显式指定时启动:

1
2
3
4
5
6
services:
web:
image: nginx
mailhog:
image: mailhog/mailhog
profiles: ["dev"]
1
docker compose --profile dev up

配置结构理解后,网络与存储是另外两个关键环节。

4. 网络配置

4.1 Expose 与 Ports

配置作用范围用途
ports宿主机 + 内部网络对外暴露服务
expose仅内部网络服务间通信

数据库等内部服务只用 expose,不配置 ports

1
2
3
4
5
services:
database:
image: postgres
expose:
- "5432"
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart TB
    subgraph Host ["宿主机"]
        P["ports: 8080"]
    end

    subgraph Network ["Compose 内部网络"]
        W["webapp"]
        D["database (expose: 5432)"]
    end

    External["外部访问"] --> P
    P --> W
    W --> D
    External -.->|"禁止"| D

    classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef danger fill:#EF4444,stroke:#DC2626,color:#fff
    class W primary
    class D danger

4.2 服务发现

Compose 网络内直接用服务名作为主机名:

  • redis://cache:6379(正确)
  • redis://localhost:6379(错误,容器内 localhost 指向自身)

⚠️ 跨容器通信使用服务名(如 cachedb),localhost 只指向容器自身。

网络确定了服务间通信方式,存储则决定数据的生命周期。

5. 存储挂载

Compose 支持三种挂载方式,选型不当会导致数据丢失或权限问题:

类型数据位置持久化适用场景
Bind mount宿主机指定路径开发时代码同步
Named volumeDocker 管理区域数据库、持久化数据
tmpfs内存否(重启清空)敏感临时数据、单元测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
services:
webapp:
volumes:
- .:/app # bind mount:代码实时同步
- node_modules:/app/node_modules # named volume:隔离容器内依赖

db:
volumes:
- pgdata:/var/lib/postgresql/data # named volume:数据库持久化

test:
tmpfs:
- /tmp # tmpfs:临时文件不落盘

volumes:
pgdata: # Docker 自动管理,数据存于 /var/lib/docker/volumes/
node_modules:

选型建议:

  • 开发源码用 bind mount,其余持久化数据统一用 named volume
  • 数据库文件不要用 bind mount,Linux/macOS 路径权限差异容易导致启动失败
  • 不需持久化的临时目录(如 /tmp/run/secrets)用 tmpfs

6. 开发体验

6.1 BuildKit 缓存加速

通过 cache_from 复用已有镜像层,显著缩短 CI 和多人协作时的构建时间:

1
2
3
4
5
6
7
8
services:
webapp:
build:
context: .
cache_from:
- type=registry,ref=myrepo/webapp:cache
args:
BUILDKIT_INLINE_CACHE: "1" # 将缓存元数据写入镜像,供 cache_from 使用

首次构建后推送缓存镜像,后续 CI 直接复用:

1
2
docker compose build
docker compose push

Docker 23+ 默认启用 BuildKit,无需额外设置环境变量。

6.2 Compose Watch 热更新

Compose Watch(Docker Engine 25+)监听文件变化,按路径规则自动触发不同操作:

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart LR
    A["文件变更"] --> B{"匹配路径规则?"}
    B -->|"src/**"| C["sync\n仅传输变更文件"]
    B -->|"package.json"| D["rebuild\n重建镜像"]
    B -->|"config/**"| E["sync+restart\n同步后重启容器"]

    classDef trigger fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef decision fill:#F59E0B,stroke:#D97706,color:#1E3A5F
    classDef sync fill:#10B981,stroke:#059669,color:#fff
    classDef rebuild fill:#EF4444,stroke:#DC2626,color:#fff
    classDef restart fill:#0EA5E9,stroke:#0284C7,color:#fff

    class A trigger
    class B decision
    class C sync
    class D rebuild
    class E restart

对应的 YAML 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
services:
webapp:
build: .
develop:
watch:
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: package.json
- action: sync+restart
path: ./config
target: /app/config
1
docker compose up --watch

sync 只传输变更文件,不会将宿主机 node_modules 覆盖容器内依赖目录,比 bind mount 整个项目目录更安全。

开发阶段配置就绪后,进入部署阶段的关键配置。

7. 部署实践

7.1 多环境配置

compose.override.yml 在开发时自动合并覆盖基础配置,生产环境用 -f 显式指定:

1
2
3
compose.yml           # 基础配置(镜像、依赖关系)
compose.override.yml # 开发覆盖,自动加载(挂载源码、开启调试)
compose.prod.yml # 生产专用,手动指定(资源限制、日志驱动)
1
2
3
4
5
6
7
# compose.yml
services:
webapp:
image: myrepo/webapp:${TAG:-latest}
restart: unless-stopped
expose:
- "8080"
1
2
3
4
5
6
7
8
9
10
# compose.override.yml
services:
webapp:
build: .
ports:
- "8080:8080"
volumes:
- .:/app
environment:
- DEBUG=true
1
2
3
4
5
# 开发:自动合并 override
docker compose up

# 生产:跳过 override,仅合并指定文件
docker compose -f compose.yml -f compose.prod.yml up -d

7.2 重启策略

1
2
3
services:
webapp:
restart: unless-stopped # 手动 stop 后不重启,其余情况自动恢复
策略行为
no从不重启(默认)
always总是重启,包括 docker compose stop
on-failure[:N]非零退出码时重启,可限制最大次数
unless-stopped手动 stop 不重启,其余情况自动重启(推荐)

7.3 资源限制

不加限制的容器可能耗尽宿主机资源,影响同机其他服务:

1
2
3
4
5
6
7
8
9
services:
webapp:
deploy:
resources:
limits:
cpus: "0.50" # 最多使用 50% 单核 CPU
memory: 512M # 超出触发 OOM kill
reservations:
memory: 256M # 调度时保证分配的最低内存

7.4 日志管理

默认 json-file 驱动不限制文件大小,长期运行会撑满磁盘:

1
2
3
4
5
6
7
services:
webapp:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3" # 共保留 30MB 日志

生产环境可换用 lokifluentd 驱动集中推送日志,只需修改 driveroptions

7.5 Secrets 管理

环境变量可被 docker inspect 明文读取,敏感凭据改用 secrets:

1
2
3
4
5
6
7
8
9
10
11
12
services:
db:
image: postgres
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password # postgres 官方镜像支持 _FILE 后缀
secrets:
- db_password

secrets:
db_password:
file: ./secrets/db_password.txt # 开发:本地文件
# external: true # 生产:外部管理(Docker Swarm secrets)

容器从 /run/secrets/<name> 读取内容,密钥不进入镜像层,不出现在 docker inspect 输出中。

7.6 配置复用

include 嵌入外部 Compose 文件,支持远程 Git 仓库:

1
2
3
4
5
6
7
8
9
include:
- path: ./common-services/postgres.yml
- path: ./common-services/redis.yml
- project: my-monitoring-stack
path: git@github.com:my-org/infra-compose.git/logging-stack/compose.yml

services:
my-app:
build: .

8. 常用命令

8.1 Run 与 Exec

命令容器生命周期用途
run创建新容器,执行后销毁一次性任务(迁移、安装依赖)
exec进入已运行容器调试、查看日志
1
2
docker compose run --rm webapp npm install
docker compose exec database bash

8.2 Up 与 Run

维度uprun
启动范围所有服务单个服务
端口映射生效默认不生效
容器生命周期持续运行执行后退出

强制端口映射:docker compose run --service-ports web

8.3 水平扩展

1
docker compose up -d --scale worker=3

约束:

  1. 仅限无状态服务,数据库等有状态服务不可扩展
  2. 扩展服务不能定义固定宿主机端口
  3. 无自动伸缩,需手动调整副本数(自动伸缩需 Kubernetes HPA)

Compose 内置 DNS 对多副本自动轮询负载均衡。

⚠️ --scale 的服务若配置了固定宿主机端口,多副本启动时端口冲突。需移除 ports,改用 expose 或动态端口。

9. 参考资料