1.3 Compose 文件结构与常用指令


1.3 Compose 文件结构与常用指令

本节摘要:docker-compose.yml 是 Compose 的核心,由 services、networks、volumes、configs、secrets 五个顶层块组成,其中 services 是唯一必填的。本节给出一个带逐行注释的最小完整示例,然后逐个拆解 build、ports、volumes、environment、depends_on、restart 等常用键的语义与坑,最后讲清顶层 networks、volumes、configs、secrets 块的写法,让读者拿到一份陌生模板时能流畅读懂。

你能学到什么

  • 能说出五个顶层块各自管什么,以及哪个是必填的
  • 能逐行解读一份完整的最小 Compose 示例
  • 能区分 build 的两种写法、ports 的多种映射形式、volumes 的三种挂载类型
  • 能写出带 condition 条件的 depends_on 并配合 healthcheck 使用
  • 能理解 external 网络、configs、secrets 的适用场景

一、顶层块:文件的五个抽屉

Compose 文件的顶层结构可以想象成一个五斗柜:services 抽屉放服务定义,networks 抽屉放网络,volumes 抽屉放卷,configs 抽屉放配置文件,secrets 抽屉放密钥。五斗柜里 services 是必填的,其余四个抽屉可以空着——不需要就不声明,Compose 会自动补默认值。写文件时从 services 块开始,其余顶层块按需补齐,这也是新规范推荐的书写顺序。

五个顶层块的职责一句话各说各的:services 定义"跑什么",networks 定义"怎么连",volumes 定义"存哪去",configs 定义"读哪个配置",secrets 定义"密码放哪"。命名上有意做了区分:configs 是明文配置,secrets 是敏感信息,两者在挂载方式上几乎一样,但语义上分开写,安全审计时一眼能看出哪些是敏感项。

二、带注释的最小完整示例

先看一张完整的配方卡。这是一个典型的 web 加 API 加数据库三件套,全部注释写在关键行后面:

services: web: image: nginx:1.25 # 拉取指定标签的官方镜像,不用 latest ports: - "80:80" # 宿主机 80 映射容器 80,外网入口 volumes: - ./html:/usr/share/nginx/html # 绑定挂载,改本机文件即生效 depends_on: - api # 先启动 api,再启动 web networks: - frontend api: build: context: ./api # 构建上下文目录 dockerfile: Dockerfile # 指定 Dockerfile 文件名 args: NODE_ENV: production # 构建参数,传给 Dockerfile environment: - DATABASE_URL=postgres://user:password@db:5432/mydb depends_on: db: condition: service_healthy # 等 db 健康检查通过再启动 networks: - backend db: image: postgres:13 volumes: - db_data:/var/lib/postgresql/data # 命名卷,数据不随容器消失 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 2s retries: 3 networks: - backend networks: frontend: driver: bridge backend: driver: bridge volumes: db_data:

这份文件里三层服务各司其职:web 是静态入口,靠绑定挂载直接服务本机 html 目录;api 需要从源码构建,还要求数据库健康后才启动;db 数据进命名卷,并配了健康检查让依赖方可以等待。注意 web 只在 frontend 网络,api 和 db 只在 backend 网络,web 与 api 之间没有直接网络交集——实际项目里如果需要,可以让 web 同时加入 backend,或者让 api 同时暴露到 frontend。这份示例覆盖了本书后续模板八成以上的常见结构,后面的章节会在此基础上按场景增减。

三、build:从 Dockerfile 造镜像

build 有两种写法。简单形式给一个路径,Compose 到该目录找名为 Dockerfile 的文件构建:

services: app: build: ./app

对象形式可以指定更多细节:

services: web: build: context: ./web dockerfile: Dockerfile.dev args: NODE_ENV: development

context 是构建上下文目录,Dockerfile 里所有 COPY 的路径都相对它;dockerfile 换掉默认文件名,常见于区分 Dockerfile.dev 与 Dockerfile.prod;args 传递构建参数,在 Dockerfile 里用 ARG 接收。image 与 build 同时出现时,Compose 构建后会给镜像打上 image 指定的名字,方便复用。

构建相关的两个习惯值得养成:一是 context 别指向包含大量无关文件的大目录,构建上下文会被整个打包发给 Docker 引擎,目录越大构建越慢,配合 .dockerignore 文件把 node_modules、.git 这类内容排除掉,构建速度能快一个量级;二是本地构建的镜像默认没有标签,docker compose ps 之外的镜像列表里会多出一堆无名镜像,建议 build 时同时指定 image 名,让镜像可辨识、可复用。

四、ports:端口映射的三种形式

ports 把容器端口发布到宿主机,最常用的是"宿主机端口:容器端口":

services: web: ports: - "80:80" - "443:443" - "8080:8080"

也可以只写容器端口,让 Docker 自动分配一个随机宿主机端口,配合 docker compose port 查询;还能用范围批量映射:

services: app: ports: - "3000-3005:3000-3005"

这会把宿主机 3000 到 3005 的五个端口一一对应到容器同名端口,适合调试一组端口固定的服务。短格式映射还有一个变体:在宿主机端口前加 IP 前缀,比如 "127.0.0.1:8080:80",只允许本机访问,生产上不想对外暴露管理端口时很实用。

端口映射的典型报错是宿主机端口被占用:up 时报 bind: address already in use,说明 80 端口已被其他进程或其他容器占用。排查顺序是先用 docker compose ps 看是不是自己项目里的容器占着,再用系统命令查端口归属,最后决定改端口还是停掉冲突进程。另外注意映射字符串最好加引号,"80:80" 写成 80:80 在大多数情况下也能解析,但 YAML 对某些形态的解析可能出意外,统一加引号是零成本保险。

五、volumes:三种挂载类型

volumes 键支持三类挂载:绑定挂载、命名卷、tmpfs。绑定挂载直接连宿主机路径,双向实时同步;命名卷由 Docker 管理,独立于容器生命周期;tmpfs 是内存文件系统,速度最快但重启即失。

services: web: volumes: - ./html:/usr/share/nginx/html # 绑定挂载 - mydata:/data # 命名卷 - type: tmpfs target: /tmp # tmpfs 挂载,仅内存 volumes: mydata:

判断用哪种:需要持久且要备份的用命名卷,比如数据库;开发时改代码要即时生效的用绑定挂载;只放临时缓存、丢了也无所谓的用 tmpfs。

六、environment:环境变量的两种写法

environment 可以用列表和映射两种形式,效果等价:

services: app: environment: - NODE_ENV=production - DATABASE_URL=postgres://user:password@db:5432/mydb - API_KEY=${API_KEY}

列表形式每行一个键值对,映射形式则像字典一样缩进写。${API_KEY} 表示从宿主机环境变量取值,宿主机没有这个变量时 Compose 会留空并给出警告。需要批量注入一批变量时,还可以用 env_file 指定一个文件,文件里每行一个键值对,比 environment 更适合存放大量配置。

七、depends_on:依赖的两种强度

depends_on 控制启动顺序,有两种写法。简单列表只保证"先启动",不保证"已就绪":

services: web: image: nginx:latest depends_on: - app

更强的写法带 condition,让服务等依赖方健康检查通过后才启动:

services: web: image: nginx:latest depends_on: app: condition: service_healthy app: build: ./app healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000"] interval: 5s timeout: 2s retries: 3

数据库"已经启动但还没接受连接"是经典坑:postgres 容器起来了,端口却还没就绪,应用连接失败就退出。解决办法就是给 db 配 healthcheck,让依赖方用 condition: service_healthy 等待。depends_on 还有一个边界要避开:循环依赖。A 依赖 B、B 依赖 A 的写法会让 Compose 无法确定启动顺序,虽然多数实现不直接报错,但行为不可预测。真正的解法是重构服务边界——把相互依赖的逻辑拆开,或者让启动慢的一方自己实现重试连接,而不是用依赖关系硬撑。

八、restart 与其他常用键

restart 定义容器退出后的重启策略,取值四个:no 是默认,不自动重启;on-failure 只在非零退出码时重启;always 无论退出原因都重启,包括 Docker 重启后;unless-stopped 与 always 类似,但手动 stop 后不再拉起。服务器上跑的服务我倾向 always 或 unless-stopped。

其余高频键还有:command 覆盖镜像默认启动命令;entrypoint 覆盖入口点;expose 声明端口但不发布到宿主机;labels 给容器打标签供其他工具识别。这些键和上面讲的 build、ports、volumes、environment、depends_on 一起,覆盖了 90% 模板的阅读需求。

关于 command 与 entrypoint 的关系,值得多说一句:entrypoint 定义容器启动时第一个执行的程序,command 是传给它的参数,两者合起来才是完整的启动命令。镜像一般自带默认 entrypoint,command 的作用是替换默认参数。比如官方 nginx 镜像的 entrypoint 会加载 /etc/nginx/conf.d 下的配置,command 可以追加 -g 参数改运行配置。写配置时如果发现"我改了 command 但启动行为没变",先查镜像的 entrypoint 是不是把命令吃掉了。

九、YAML 语法三坑:文件写对才能跑对

Compose 文件是 YAML 格式,语法错误会在启动时报 YAML 解析失败,报错信息往往指向行号但不够直白。三个高频坑先排掉:第一,缩进必须一致,YAML 不允许混用 Tab 与空格,编辑器里把 Tab 展开成空格最稳妥;第二,值里的冒号后必须跟空格,services:web 这种写法直接解析失败;第三,特殊字符要引号,端口映射、带冒号的 URL、带星号的命令都要用引号包起来,否则 YAML 解析器会按自己的规则理解。

验证语法不用等启动:docker compose config 命令会解析并展开整个文件,语法问题在这里一步暴露,而且报错信息比 up 时友好得多。我的习惯是每次改完文件先跑一遍 config 再 up,十秒的检查能省下十分钟的排错。

Compose 文件层级结构图

Compose 文件层级结构图

顶层 networks 与 volumes 块

networks 块声明项目自定义网络。默认驱动是 bridge,也可以标记外部网络:

networks: mynetwork: driver: bridge external_network: external: true

external: true 表示这张网在项目之外已经存在(比如由另一个项目或手工创建),Compose 只负责把服务接进去,不负责创建它。用外部网络可以在两个独立项目之间打通通信,前提是网络已经存在,否则启动报错。

volumes 块声明命名卷,常用配置是 driver 和 external:

volumes: db_data: driver: local named_volume:

driver 默认是 local,在宿主机目录存放卷数据;多机场景可以换 nfs 或 glusterfs 驱动。external: true 同样表示卷已由外部创建,Compose 只引用。

configs 与 secrets:明文配置与敏感信息

configs 与 secrets 顶层块都从文件读取内容,再挂载进容器:

configs: my_config: file: ./config.txt secrets: db_password: file: ./db_password.txt services: app: image: myapp configs: - source: my_config target: /app/config.txt secrets: - source: db_password target: /run/secrets/db_password

服务里用 source 指定来源,target 指定挂载目标路径。secrets 挂载后内容只出现在容器内存里,不进镜像层,比把密码写进环境变量更安全。注意 configs 和 secrets 由 Swarm 模式演进而来,本地 docker compose 对 file 形式的支持是完整的,日常单机部署也可以放心用。

服务依赖关系图

常用键速查表

作用 常见写法示例
image 指定镜像 postgres:13 别用 latest,内容不可控
build 从 Dockerfile 构建 build: ./app context 路径相对 Compose 文件位置
ports 映射端口到宿主机 "8080:80" 只暴露需要的端口
expose 声明端口不映射 expose: ["5432"] 同网络服务才能访问
volumes 挂载卷或目录 db_data:/var/lib/postgresql/data 路径写错会静默建目录
environment 设置环境变量 NODE_ENV=production 值里带特殊字符要引号
depends_on 控制启动顺序 condition: service_healthy 简单列表不保证就绪
restart 重启策略 always 调试期建议 no,防循环重启
command 覆盖启动命令 command: python app.py 会替换镜像默认命令

⚠️ volumes 路径是最容易静默出错的地方:冒号左边是宿主机路径或卷名,右边是容器路径。写错宿主机路径时 Compose 不会报错,而是默默创建空目录挂进去,容器里看到的就是空目录,排错半天找不到原因。

💡 把 depends_on 从列表升级成条件:凡是"启动后马上要连数据库"的服务,都值得给数据库配 healthcheck 并用 condition: service_healthy,一次配置,长期免踩"连接被拒"的坑。

核心回顾

  • 五个顶层块:services 必填,networks、volumes、configs、secrets 按需声明
  • build 两种写法:简单路径指向 Dockerfile 所在目录,对象形式可换 dockerfile 名和传 args
  • ports 三种映射:固定端口、自动分配、范围批量映射,前缀 IP 可限制访问来源
  • volumes 三类挂载:绑定挂载双向同步、命名卷独立持久、tmpfs 仅内存
  • depends_on 两档强度:列表只排顺序,condition 等健康检查通过才启动
  • configs 与 secrets:分别承载明文配置与敏感信息,secrets 不进镜像层
  • restart 四档策略:no、on-failure、always、unless-stopped,服务器服务优先 always

文件语法学完,接下来让文件真正跑起来——1.4 节讲 up、down、start、stop、restart 五个命令的行为差异,以及一套典型的开发日操作序列。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U