Docker Compose 配置文件归纳总结-千问

Docker Compose 配置文件(通常为 compose.yamldocker-compose.yml)是声明式定义多容器应用的核心。以下是按功能模块 归纳的常用语法速查手册,基于当前主流的 Compose Specification 标准。


1. 顶层结构概览

yaml 复制代码
name: my-project          # 项目名称(可选,默认取目录名)
services: {}              # 【核心】服务定义
networks: {}              # 自定义网络
volumes: {}               # 命名卷 / 外部卷
configs: {}               # 配置管理(Swarm/Compose v2.23+)
secrets: {}               # 敏感数据管理
x-common: &anchor         # 自定义扩展字段 + YAML锚点(复用配置)

⚠️ 现代 Compose 已不再强制要求 version 字段,建议省略以避免混淆。


name
yaml 复制代码
name: ecommerce-prod
services:
  web:
    image: nginx
  • 解释 :显式指定项目名称。所有容器、网络、卷都会以 ecommerce-prod- 为前缀(如 ecommerce-prod-web-1)。
  • 为什么用:避免在同一目录下运行多个 compose 文件时资源命名冲突;CI/CD 中动态注入项目名实现环境隔离。
  • 注意 :若不设置,默认取 compose.yaml 所在目录名,重命名目录会导致旧资源 orphaned。
x-common (YAML 锚点)
yaml 复制代码
x-base-service: &base
  restart: unless-stopped
  logging:
    driver: json-file
    options: { max-size: "10m", max-file: "3" }
  deploy:
    resources:
      limits: { memory: 512M }

services:
  api:
    <<: *base          # 展开锚点
    image: node:20
  worker:
    <<: *base
    image: python:3.12
    deploy:
      resources:
        limits: { memory: 1G }   # 覆盖锚点中的 memory
  • 解释&base 定义锚点,*base 引用,<<: 合并映射。workerdeploy深层覆盖锚点值。
  • 为什么用:消除重复配置,修改一处全局生效。比多文件覆盖更轻量,适合单文件内的横向复用。
  • 注意 :YAML 锚点是浅合并 ,嵌套对象需手动重新声明整个子块(如上例 deploy),不能只写 limits.memory

2. Services 核心语法(最常用)

2.1 镜像与构建
语法 说明
image: nginx:latest 指定镜像
build: ./app 从 Dockerfile 构建
build.context / dockerfile / args / target 构建参数细化
platform: linux/amd64 指定平台架构(Apple Silicon 常用)
pull_policy: always/if_not_present/never 拉取策略
yaml 复制代码
services:
  app:
    build:
      context: ./backend
      dockerfile: Dockerfile.prod     # 指定非默认 Dockerfile
      target: production              # 多阶段构建的目标阶段
      args:
        NODE_VERSION: "20"            # 传入 ARG
        BUILD_DATE: "${BUILD_DATE}"   # 引用环境变量
    platform: linux/amd64             # Apple Silicon 上强制 x86
    pull_policy: if_not_present       # 本地有就不拉,节省带宽
  • 解释target 配合多阶段构建,只打包最终产物;args 在构建时可用 ${ARG_NAME} 引用。
  • 为什么用 :开发/生产共用一个 Dockerfile,通过 target 区分;CI 中缓存镜像层,pull_policy 避免重复拉取。
  • 注意args 中的变量必须在 Dockerfile 中有对应 ARG 声明,否则静默忽略。修改 args 会触发重新构建。
2.2 端口映射
yaml 复制代码
ports:
  - "8080:80"             # host:container 短格式写法
  - "127.0.0.1:3306:3306" # 仅本地访问
  - target: 80            #容器端口 长格式写法
    published: 8080       #宿主机端口
    protocol: tcp         # 长语法,更精确
    mode: host            #Swarm 集群模式下设为 ingress
2.3 环境变量
yaml 复制代码
environment:
  - NODE_ENV=production
  - DB_HOST=${DB_HOST:-localhost}   # 支持默认值
  - DB_PASS=${DB_PASS:?ERROR: DB_PASS is required}   # 缺失则报错退出
env_file:
  - .env                   #当只需要指定文件路径、不需要其他选项(如 required)时,可以直接使用字符串简写形式,path:可省略
  - path: .env.local       #只有当你需要配置 额外选项 时,才必须使用长语法:
    required: false        # 文件不存在时不报错
  • 解释${VAR:?msg} 在变量未定义或为空时终止 compose 并打印 msg;required: false 让可选配置文件不阻塞启动。
    • 注意environment 优先级 高于 env_file.env 文件不支持 shell 展开(如 $(cmd)),只做简单替换。
2.4 存储挂载
yaml 复制代码
volumes:
  - db_data:/var/lib/mysql          # 命名卷
  - ./config:/app/config:ro         # 绑定挂载(只读)
  - type: tmpfs                     # 内存文件系统
    target: /tmp
    tmpfs.size: 100M
  - type: bind
    source: ./uploads               #宿主机路径
    target: /app/uploads            #容器内路径
    bind:
      create_host_path: true                  # 宿主机目录不存在时自动创建
  • 注意 :命名卷由 Docker 管理生命周期,docker compose down -v 才会删除;bind mount 的宿主机路径必须是绝对路径或相对 compose 文件的路径。
2.5 重启与生命周期
yaml 复制代码
restart: unless-stopped   # no / always / on-failure / unless-stopped
stop_grace_period: 30s    # SIGTERM 后等待时间
stop_signal: SIGINT       # 自定义停止信号
init: true                # 注入 tini 作为 PID 1,正确处理信号
  • 解释init: true 在容器内插入一个轻量 init 进程(tini),负责回收僵尸进程和正确转发信号。
  • 为什么用 :Node.js/Python 等应用默认不处理 SIGTERM,stop_grace_period + init 组合确保优雅退出;长时间运行的 worker 必须有 unless-stopped 防崩溃。
  • 注意stop_grace_period 超时后会发 SIGKILL 强杀;init 会增加约 1MB 镜像体积,但几乎无性能开销。

这三个配置项共同构成了 Docker 容器的优雅退出(Graceful Shutdown)机制 。它们决定了当执行 docker compose stopdocker compose down 时,容器内的应用是"安全地保存状态后退出",还是"被暴力杀死导致数据丢失"。

以下是逐项深度解析:


1. stop_grace_period: 30s

是什么

定义 Docker 在发送停止信号后,等待容器自行退出的最长时间 。默认值为 10s

工作流程
复制代码
docker compose stop
       │
       ▼
发送 stop_signal (默认 SIGTERM) ──→ 应用收到信号,开始清理(关连接、刷缓存、排空队列)
       │
       ▼
等待 stop_grace_period (30s)
       │
       ├── 应用在 30s 内正常退出 → ✅ 容器停止
       │
       └── 30s 超时仍未退出   → ❌ 强制发送 SIGKILL,立即杀死
为什么需要调整
  • 默认 10s 太短:Java/Spring Boot 应用关闭通常需要 15-30s(销毁 Bean、关闭连接池);Go/Node.js 排空 HTTP 长连接也可能超过 10s。
  • 设置过长:如果应用已经死锁无法退出,过长的 grace period 会让部署/重启过程卡住。
⚠️ 关键注意
  • 这个值必须 大于 应用实际完成清理所需的时间,否则等同于没配。
  • SIGKILL 无法被捕获,超时强杀意味着所有未完成的写入、未提交的事务都会丢失。

2. stop_signal: SIGINT

是什么

指定 Docker 发送给容器 PID 1 进程的第一个停止信号 。默认是 SIGTERM

常见信号对比
信号 编号 默认行为 适用场景
SIGTERM 15 请求终止,可捕获 大多数应用默认,Nginx、PostgreSQL
SIGINT 2 中断,可捕获 Node.js、Python (Flask/FastAPI)、Ctrl+C 等效
SIGQUIT 3 退出并 dump,可捕获 Go 应用(pprof)、Java(thread dump)
SIGKILL 9 立即杀死,不可捕获 ⛔ 永远不要设为 stop_signal
为什么要改

不同语言/框架监听的信号不同:

  • Node.jsprocess.on('SIGINT', ...) 是惯用写法,很多框架默认只监听 SIGINT 而不处理 SIGTERM。如果用默认的 SIGTERM,应用可能直接忽略,等到 grace period 超时被 SIGKILL 强杀。
  • Go 应用:某些框架用 SIGQUIT 触发 graceful shutdown 并同时输出 goroutine stack trace,方便排查关闭慢的原因。
💡 最佳实践

查阅你所用框架的文档,确认它监听哪个信号来做优雅关闭,然后将 stop_signal 设为对应值。信号不匹配是"明明配了 grace period 但应用还是被强杀"的最常见原因。


3. init: true

是什么

在容器内注入一个轻量级 init 进程 (通常是 tini,约 10KB),作为真正的 PID 1,你的应用变成 PID 2+。

解决什么问题

在没有 init 的情况下,你的应用直接作为 PID 1 运行,会面临两个经典问题:

问题 无 init (应用=P1) 有 init (tini=P1, 应用=P2+)
僵尸进程 PID 1 不负责回收子进程,已退出的子进程变 zombie,内存泄漏 tini 自动 wait() 回收所有孤儿/僵尸进程
信号转发 Linux 内核不给 PID 1 发送默认信号处理;应用若不显式注册 SIGTERM handler,信号会被丢弃 tini 正确接收信号并 forward 给应用子进程
实际例子
yaml 复制代码
# ❌ 没有 init:Node.js 作为 PID 1
# docker compose stop → 发 SIGTERM → Node 没注册 handler → 信号被忽略 → 等 30s → SIGKILL 强杀

# ✅ 有 init:tini 作为 PID 1
# docker compose stop → 发 SIGTERM → tini 收到 → 转发给 Node(PID 2) → Node 正常退出
services:
  app:
    image: node:20
    init: true                # 注入 tini
    stop_signal: SIGINT       # tini 转发 SIGINT 给 Node
    stop_grace_period: 30s    # 给 Node 足够时间清理
⚠️ 注意事项
  • init: true 会增加约 10KB 镜像体积和极微小的启动开销,生产环境完全可以接受。
  • 如果你的 Dockerfile 中已经手动安装了 tini/dumb-init 并用 ENTRYPOINT ["/sbin/tini", "--"] 启动,则不需要 再设 init: true,否则会嵌套两层 init。
  • Alpine 镜像自带 /sbin/tini;Debian/Ubuntu 基础镜像不含,但 Docker 会在运行时自动注入,无需修改 Dockerfile。

🔗 三者协同的完整生命周期

复制代码
docker compose stop api
        │
        ▼
  tini (PID 1) 收到 SIGINT (stop_signal)
        │
        ▼
  tini 将 SIGINT 转发给应用 (PID 2)
        │
        ▼
  应用执行优雅关闭逻辑(关DB连接、排空请求、flush日志)
        │
        ▼
  应用在 30s (stop_grace_period) 内退出
        │
        ▼
  tini 回收应用进程 → 容器干净停止 ✅

一句话总结init 确保信号能送达且僵尸被回收,stop_signal 确保发的是应用能识别的信号,stop_grace_period 确保应用有足够时间完成清理。三者缺一不可,否则优雅退出就是纸上谈兵。

2.6 健康检查
yaml 复制代码
services:
  api:
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s           # 启动后 30s 内失败不计入统计
    depends_on:
      db:
        condition: service_healthy
  • 解释start_period 是冷启动宽限期,期间健康检查仍执行但不影响容器状态判定;CMD-SHELL 允许管道和逻辑运算符。
  • 为什么用 :Java/Go 应用启动慢,没有 start_period 会被误判 unhealthy 反复重启;depends_on.condition 确保数据库就绪后再启动 API。
  • 注意test 推荐用数组形式避免 shell 转义问题;健康检查命令应轻量,避免高频 curl 消耗资源;docker inspect --format='{``{.State.Health.Status}}' <container> 可实时查看状态。

在 Docker Compose Specification 中,depends_oncondition 一共有 3 个合法值:

condition 值 含义 典型场景
service_started 等待依赖容器启动成功(即进入 running 状态) 无健康检查的普通服务;仅需确保进程已拉起
service_healthy 等待依赖容器的健康检查通过(healthcheck status = healthy) 数据库、缓存、API 网关等需要确认可用后才连接的服务
service_completed_successfully 等待依赖容器正常退出且退出码为 0 数据库迁移、初始化脚本、一次性 setup 任务

⚠️ 关键注意事项

  1. service_healthy 的前提 :被依赖的服务必须定义了 healthcheck ,否则 Compose 会直接报错:

    复制代码
    service "db" has no healthcheck configured
  2. 没有 service_stopped / service_failed:不存在"等某个服务停止"或"等某个服务失败"的条件。如果需要这类逻辑,应使用外部编排工具或在应用层处理。

  3. 默认值 :如果省略 condition,等价于 service_started

    yaml 复制代码
    # 以下两种写法完全等价
    depends_on:
      redis:
        condition: service_started
    
    depends_on:
      - redis   # 简写形式,隐式 = service_started
  4. service_completed_successfully 的行为细节

    • 该容器退出码为 0 → 条件满足,后续服务启动
    • 该容器退出码非 0 → 条件永不满足docker compose up 会一直等待直到超时或手动中断
    • 该容器仍在运行 → 继续等待
  5. 仅控制启动顺序,不控制停止/重启depends_on 只在 up 时生效。当依赖服务崩溃重启时,不会 自动重启依赖它的服务------这需要通过 restart: unless-stopped + 应用层的重连/重试机制来保障。

💡 记忆口诀started (起来了)、healthy (活好了)、completed(干完了)------三个词覆盖所有启动依赖场景。

针对下面这个行的解释:

yaml 复制代码
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]

这行配置是 Docker 健康检查的核心探测命令 。它的作用是:每隔一段时间,在容器内部执行一次 HTTP 请求,根据返回结果判断服务是否存活。

下面逐层拆解:

1. 语法结构

yaml 复制代码
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]
#      ↑               ↑
#   执行模式          实际执行的 shell 命令字符串

test 支持两种形式:

形式 写法 Shell 解析 适用场景
CMD-SHELL ["CMD-SHELL", "command string"] ✅ 经过 /bin/sh -c 解析 需要管道 `
CMD ["CMD", "curl", "-sf", "http://..."] ❌ 直接 exec,不经过 shell 简单命令,无特殊字符,更安全

这里选择 CMD-SHELL 是因为用到了 || 逻辑运算符。

2. 命令逐参数解析

bash 复制代码
curl -sf http://localhost:8080/health || exit 1
部分 含义
curl 容器内发起 HTTP 请求(前提是镜像中安装了 curl
-s Silent:静默模式,不输出进度条和错误信息,避免日志污染
-f Fail fast:HTTP 4xx/5xx 时直接返回非零退出码(默认 curl 在 404 时仍返回 0)
http://localhost:8080/health 探测目标:容器自身的 localhost,不是宿主机
`

3. 判定逻辑

Docker 只看退出码

复制代码
curl 成功 (HTTP 2xx) → 退出码 0 → ✅ healthy
curl 失败 (任何原因) → 退出码 ≠ 0 → ❌ unhealthy

|| exit 1 的作用:确保 curl 失败时退出码一定是 1,而不是 curl 自身可能返回的各种奇怪错误码(如 7=连接拒绝、28=超时、22=HTTP错误等)。统一为 1 便于排查。

4. ⚠️ 四个常见坑

  1. 镜像里没有 curl

    Alpine 镜像默认不带 curl,健康检查会直接报 executable file not found

    dockerfile 复制代码
    # 解决方案1:安装 curl
    RUN apk add --no-cache curl
    
    # 解决方案2:用 wget 替代(Alpine 自带)
    test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health || exit 1"]
    
    # 解决方案3:用专用工具(推荐生产环境)
    # COPY --from=ghcr.io/klauspost/healthcheck /healthcheck /usr/local/bin/
  2. localhost 是容器内部

    localhost:8080 指的是容器自己 的 8080 端口,不是宿主机。如果服务监听在 0.0.0.0:8080,容器内 localhost:8080 可以访问;但如果服务只监听了外部 IP 或 Unix Socket,则需要调整 URL。

  3. -f 不能捕获所有异常

    -f 只对 HTTP 响应码生效。如果 DNS 解析失败、TCP 连接被拒绝等,curl 本身就会返回非零码,-f 不参与。所以 -sf 组合已经覆盖了绝大多数失败场景。

  4. 命令要轻量

    健康检查每 interval 执行一次。不要用 curl 去请求一个重接口(如全表查询),应专门暴露一个轻量的 /health 端点,只做内存级检查。

5. 更健壮的替代方案

对于生产环境,建议用专门的探针工具替代 curl:

yaml 复制代码
# 使用 dockerize(无需安装 curl,二进制极小)
test: ["CMD", "dockerize", "-wait", "http://localhost:8080/health", "-timeout", "5s"]

# 或使用 grpc-health-probe(gRPC 服务)
test: ["CMD", "/bin/grpc_health_probe", "-addr=:50051"]

一句话总结curl -sf ... || exit 1 是"在容器内用最小开销验证 HTTP 服务是否真正可用"的标准写法,-s 防日志噪音,-f 让 HTTP 错误变为非零退出码,|| exit 1 统一失败信号。使用前务必确认镜像中有 curl。

2.7 资源限制
yaml 复制代码
services:
  worker:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
          pids: 100               # 限制最大进程数,防 fork bomb
        reservations:
          cpus: '0.5'
          memory: 512M
  • 解释limits 是硬上限(超出 OOM kill / CPU throttle);reservations 是软保障(调度器优先分配);pids 防止恶意或 bug 导致的进程爆炸。
  • 为什么用 :单机多服务共存时防止某个服务吃光资源;pids 是安全加固的重要手段。
  • 注意 :Compose V2 中 deploy.resourcesdocker compose up 时生效(无需 Swarm);memory 不支持小数,单位 B/K/M/G;CPU 支持小数如 '0.25'
2.8 依赖与启动顺序
yaml 复制代码
depends_on:
  db:
    condition: service_healthy     # ✅ 推荐:等健康检查通过
  redis:
    condition: service_started     # 仅等容器启动
  worker:
    condition: service_completed_successfully  # 等一次性任务成功完成
yaml 复制代码
services:
  migrate:
    image: my-app:migrate
    command: ["python", "manage.py", "migrate"]
    restart: "no"                 # 一次性任务,完成即停
  api:
    depends_on:
      db:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully   # 等迁移成功完成
      redis:
        condition: service_started                  # 仅需启动,无需健康
  • 解释 :三种 condition 对应不同语义:service_healthy 等健康检查通过;service_completed_successfully 等退出码 0;service_started 仅等容器 running。
  • 为什么用 :数据库迁移是一次性任务,API 必须等它成功才能启动;Redis 无健康检查端点时用 service_started 兜底。
  • 注意depends_on 不保证 服务永远可用,只保证启动时序;应用自身仍需实现重试逻辑。service_completed_successfully 要求目标服务 restart: "no" 或自然退出。

3. Networks 网络配置

yaml 复制代码
networks:
  frontend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.0.0/16
  backend:
    external: true                 # 使用已存在的网络
    name: shared-network

服务内引用:

yaml 复制代码
services:
  web:
    networks:
      frontend:
        aliases: [web-api]        # 网络别名
      backend:
        ipv4_address: 172.28.0.10 # 固定IP
yaml 复制代码
networks:
  frontend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.0.0/16
          gateway: 172.28.0.1
  monitoring:
    external: true
    name: grafana-net            # 引用已存在的网络

services:
  nginx:
    networks:
      frontend:
        aliases: [web, api-gateway]   # 同一网络内多个 DNS 名称
        ipv4_address: 172.28.0.10     # 固定 IP(需在 subnet 范围内)
      monitoring:                      # 跨网络通信
  • 解释aliases 提供额外 DNS 解析名;固定 IP 需配合 ipam.subnet 使用;external 引用宿主机上已创建的网络。
  • 为什么用:微服务间通过别名解耦真实服务名;Legacy 应用硬编码了特定 IP 时需固定地址;Prometheus/Grafana 等监控栈通常独立部署,通过 external network 接入。
  • 注意 :固定 IP 在 docker compose up --scale 多实例时会冲突,仅适用于单实例服务;不同网络间的容器默认隔离,需同时加入两个网络才能互通。

针对你引用的 networks 配置片段,以下是三个关键概念的详细解析:

1. driver 的可选值

driver 指定 Docker 使用哪种网络驱动来创建该网络。常用值如下:

driver 值 说明 典型场景
bridge 默认值。创建独立的 Linux bridge,容器间通过虚拟网卡通信,与宿主机网络隔离 单机多容器应用(绝大多数 Compose 项目)
host 容器直接共享宿主机网络栈,无 NAT、无端口映射,性能最高但失去网络隔离 高性能网络应用、监控 Agent
overlay 跨多主机的加密 VXLAN 网络,依赖 Swarm 或手动配置 Docker Swarm 集群服务发现
macvlan 为每个容器分配独立 MAC 地址,表现为物理网络上的真实设备 需要直接接入局域网、遗留系统对接
ipvlan 类似 macvlan 但共享宿主机 MAC,仅分配独立 IP 对 MAC 数量有交换机限制的环境
none 无任何网络连接,仅有 loopback 安全沙箱、纯离线计算任务

⚠️ 注意 :在 Compose 中如果不写 driver,默认就是 bridgehostnone 模式下,ipamsubnet 等配置无效且会被忽略。


2. ipam 是什么?

IPAM = IP Address Management(IP 地址管理)

它定义了 Docker 如何为该网络分配 IP 地址段。核心子字段:

yaml 复制代码
ipam:
  driver: default          # IPAM 驱动,几乎总是 default
  config:
    - subnet: 172.28.0.0/16      # 整个网络的 CIDR 网段
      gateway: 172.28.0.1        # 网关地址(可选,默认取网段第一个可用IP)
      ip_range: 172.28.5.0/24    # 实际分配给容器的IP池(可选,必须是subnet的子集)
为什么要手动指定 IPAM?
  • 避免网段冲突 :Docker 默认自动分配 172.x.0.0/16192.168.x.0/20,如果你跑了多个 Compose 项目或与宿主机 VPN/内网冲突,就需要显式指定不重叠的网段。
  • 固定容器 IP :配合服务的 ipv4_address 使用时,必须先定义明确的 subnet。
  • 合规要求:某些企业环境要求容器网络必须落在特定审批过的网段内。
💡 大多数时候不需要配

如果你的项目只有一个 compose 文件、不与外部网络交互,完全可以省略整个 ipam,让 Docker 自动管理即可。


3. name 是什么意思?

name 用于显式指定网络在 Docker 引擎中的真实名称

默认行为(不设 name)

Docker 会自动生成名称:{项目名}_{网络key}

yaml 复制代码
# 项目名为 my-project,网络 key 为 backend
# → 实际创建的网络名叫: my-project_backend
networks:
  backend:
    driver: bridge
设置 name 后
yaml 复制代码
networks:
  backend:
    name: shared-network    # 实际创建的网络就叫 shared-network
两种核心用途
用途 说明
跨项目共享网络 项目 A 创建 name: shared-network,项目 B 用 external: true + name: shared-network 引用同一个网络,实现不同 Compose 项目间的容器互通
稳定可预测的名称 避免项目目录改名导致网络名变化,方便脚本、CI/CD、外部工具引用
⚠️ nameexternal 的关系
yaml 复制代码
# 场景1:自己创建网络,并指定名称
backend:
  name: shared-network       # docker network create shared-network

# 场景2:引用别人已创建的网络
backend:
  external: true
  name: shared-network       # 告诉 Compose:"别创建,去找叫这个名字的现有网络"

关键区别external: truename查找条件 ;非 external 时 name创建时的命名。两者含义不同但语法相同。


📌 速记总结

字段 一句话记忆
driver "用什么方式连" → bridge/host/overlay/macvlan/ipvlan/none
ipam "IP 从哪来" → 定义网段、网关、分配池,防冲突用
name "叫什么名字" → 自定义真实网络名,跨项目共享必备

4. Volumes 卷管理

yaml 复制代码
volumes:
  db_data:                        # 命名卷(自动创建)
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/postgres      # 绑定到宿主机特定路径
  external_vol:
    external: true
    name: pre-existing-volume     # 引用外部卷
  • 解释driver_optstype: none + o: bind + device 组合实现了"命名卷语法 + bind mount 物理路径"的效果,兼具可移植性和路径可控性。
  • 为什么用:SSD/HDD 分离部署时,将数据库卷指向高性能磁盘;集群环境中预先创建卷并统一管理,compose 只引用不创建。
  • 注意device 路径必须事先存在 ,Docker 不会自动创建;此写法仅 local driver 支持;docker volume inspect pg_data 可查看实际挂载点。

5. 高级技巧

YAML 锚点复用配置
yaml 复制代码
x-logging: &default-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

services:
  web:
    logging: *default-logging
  worker:
    logging: *default-logging
多文件覆盖
bash 复制代码
docker compose -f compose.yaml -f compose.prod.yaml up -d

后者同名 key 会覆盖前者,适合环境差异化配置。

bash 复制代码
# base: compose.yaml
# prod overlay: compose.prod.yaml
docker compose -f compose.yaml -f compose.prod.yaml config > merged.yaml
yaml 复制代码
# compose.prod.yaml 只写差异部分
services:
  api:
    image: myapp:v2.3.1            # 覆盖镜像标签
    environment:
      - LOG_LEVEL=warn             # 追加/覆盖环境变量
    deploy:
      replicas: 3                  # 新增字段
  • 解释 :后者文件同名 key 递归合并,列表类型(如 environment)整体替换 而非追加(除非用 - 语法)。
  • 为什么用:base 文件入 Git,prod/staging overlay 按需叠加;CI 中动态生成 overlay 注入版本号。
  • 注意docker compose config 是调试合并结果的必备工具;环境变量列表若想追加而非替换,需在 overlay 中重复列出 base 的所有变量。
变量插值
yaml 复制代码
image: myapp:${TAG:-latest}
labels:
  com.example.version: "${VERSION:?VERSION is required}"  # 缺失时报错

支持的运算符:${VAR-default}${VAR:-default}${VAR:?err}${VAR:+alt}

变量插值完整示例
yaml 复制代码
services:
  app:
    image: registry.example.com/myapp:${TAG:-latest}
    labels:
      version: "${VERSION:?Please set VERSION env var}"
      branch: "${BRANCH:+feature/${BRANCH}}"   # BRANCH 非空时才展开
  • 解释${VAR:-default} 变量未定义或为空时用 default;${VAR:?msg} 未定义或为空时报错;${VAR:+alt} 变量非空时用 alt,否则为空。
  • 为什么用:CI 流水线中 TAG 可能为空,fallback 到 latest;VERSION 是发布必填项,缺失立即失败;分支标签仅在 feature 构建时添加。
  • 注意 :插值发生在 compose 解析阶段,早于 容器创建;.env 文件中的变量也参与插值,但 $$ 可转义为字面量 $

config使用

config 是 Docker Compose 中容易被忽略但非常实用的功能。它的核心价值是:将配置文件作为独立对象管理,而不是把文件路径硬编码进 volume 挂载。

1. config vs volume 的本质区别

对比项 volumes 挂载文件 configs
来源 宿主机文件系统 Compose 管理的命名对象
容器内路径 你指定的任意路径 固定 /configs/<name>(只读)
权限控制 依赖宿主机文件权限 可指定 uid/gid/mode
Swarm/K8s 兼容 ❌ 仅单机有效 ✅ 原生支持集群分发
版本管理 同名更新会创建新版本(Swarm)
适用场景 开发环境、大文件、需读写 生产配置、敏感文件、集群部署

2. 完整示例:Nginx + 自定义应用配置

docker-compose.yml
yaml 复制代码
# ========== 定义 config 对象 ==========
configs:
  nginx_conf:                        # config 名称(引用时用这个名字)
    file: ./configs/nginx.conf       # 宿主机源文件路径
  app_settings:
    file: ./configs/app.yaml
  db_password:
    file: ./secrets/db_password.txt  # ⚠️ 注意:敏感内容建议用 secrets,这里仅作演示

services:
  web:
    image: nginx:alpine
    configs:
      # ✅ 方式1:短语法 → 挂载到 /configs/nginx_conf(只读)
      - nginx_conf

      # ✅ 方式2:长语法 → 自定义容器内路径和权限
      - source: app_settings         # 引用上面定义的 config 名称
        target: /etc/app/settings.yaml  # 容器内的目标路径
        uid: "1000"                  # 文件所有者 UID
        gid: "1000"                  # 文件所属组 GID
        mode: 0440                   # 文件权限(八进制)

  api:
    image: my-api:latest
    configs:
      - source: app_settings
        target: /app/config.yaml
        mode: 0400                   # 仅 owner 可读

3. 容器内实际效果

bash 复制代码
# 短语法挂载的文件
$ docker exec web cat /configs/nginx_conf
# → nginx.conf 的内容(只读)

# 长语法自定义路径的文件
$ docker exec web cat /etc/app/settings.yaml
# → app.yaml 的内容,权限为 0440,属主 uid=1000

$ docker exec web ls -la /etc/app/settings.yaml
# -r--r----- 1 1000 1000 ... settings.yaml

4. ⚠️ 关键注意事项

  1. 容器内默认路径是 /configs/<name> ,不是 /etc/xxx。如果你的应用期望读取 /etc/nginx/nginx.conf必须用长语法指定 target

  2. 所有 config 文件在容器内都是只读的 ,应用无法修改。如果需要运行时写入,仍需用 volumes

  3. Config 内容变更不会自动热更新 。修改宿主机文件后,必须 docker compose up -d 重建容器才能生效(Swarm 模式下可通过滚动更新实现零停机)。

  4. 敏感数据请用 secrets 而非 configs

    yaml 复制代码
    secrets:
      db_password:
        file: ./secrets/db_password.txt
    services:
      api:
        secrets:
          - db_password   # 挂载到 /run/secrets/db_password(tmpfs,不落盘)

    configssecrets 语法几乎相同,唯一区别是存储位置和安全性。

  5. Dockerfile 中不能用 COPY --from=config :config 只在运行时注入,构建阶段不可见。如需构建时使用配置文件,仍需 COPYARG

5. 什么时候该用 config?

复制代码
需要在容器中提供配置文件?
    │
    ├── 开发环境 / 需要频繁修改 / 文件很大 → volumes
    │
    ├── 生产环境 / 集群部署 / 需要精细权限控制 → ✅ configs
    │
    └── 密码 / Token / 证书私钥等敏感数据 → ✅ secrets

一句话总结configs 让你像声明变量一样声明配置文件------定义一次、多处引用、权限可控、与宿主机路径解耦。它是从"单机开发"迈向"生产级编排"的关键一步。


6. 常见反模式 ⚠️

❌ 避免 ✅ 推荐
version: '3.8' 省略 version 字段
links: 使用 networks + 服务名 DNS
depends_on 无条件等待 配合 healthcheck + condition
硬编码密码到 environment 使用 secrets.env + gitignore
restart: always 用于调试 开发时用 no,生产用 unless-stopped
单个巨大 compose 文件 拆分 + 多文件组合 + x-锚点复用

快速验证命令

bash 复制代码
# 1. 渲染最终配置(含所有变量替换、锚点展开、多文件合并)
docker compose config
# 输出完整的、无变量的 YAML,用于 code review 和 CI 校验

# 2. 静默校验语法
docker compose config --quiet && echo "✅ Valid" || echo "❌ Invalid"
# CI pipeline 中作为 gate check

# 3. 预览变更(不实际操作)
docker compose up --dry-run
# 显示哪些容器会 create/recreate/start,类似 terraform plan

# 4. 检查单个服务的最终配置
docker compose config | yq '.services.api'
# 配合 yq/jq 快速定位某个服务的合并结果

💡 最佳实践 :将 compose.yaml 视为基础设施代码,纳入版本控制;敏感信息通过 .env(不入仓库)或 secrets 注入;始终为生产服务配置 healthcheck + restart: unless-stopped + 资源限制三件套。
💡 终极建议 :将以上所有示例整合到一个 compose.reference.yaml 文件中作为团队模板,新服务直接复制裁剪,比文档更高效。每次升级 Compose 版本后,用 docker compose config --quiet 回归验证兼容性。

相关推荐
INNOVIX稳石机器人1 小时前
从“存得下”到“管得活”:稳石四向穿梭车如何重塑密集仓储新逻辑?
大数据·运维
2401_858286112 小时前
OS81.【Linux】基于环形队列的多生产者-多消费者模型
linux·运维·服务器·环形队列
X1A0RAN2 小时前
Jenkins Pipeline 变量打印指南
运维·servlet·jenkins
吴爃2 小时前
小微企业 SRE 稳定性建设(二):上线前 P0 验收项目
运维·可用性测试·稳定性·故障
XUEYUAN52122 小时前
跨境爬虫与矩阵运维:代理IP风控原理、节点差异与厂商技术调研
运维·爬虫·矩阵
运维大师2 小时前
【K8S 运维实战】37-金融企业多集群落地
运维·金融·kubernetes
笑一下蒜了.3 小时前
DNS 原理与 BIND 权威服务器实操笔记
运维·服务器·笔记
行者-全栈开发3 小时前
【Linux内核】CVE-2026-46242:Bad Epoll Linux 内核 epoll UAF 漏洞修复指南(99%可靠root的云安全噩梦)
linux·运维·服务器·cve-2026-46242·linux内核漏洞·use-after-free·云多租户安全
平安的平安3 小时前
人在外地,怎样访问办公室里的电脑和内部资源?
运维·服务器·电脑