概述
服务拆成微服务之后,部署的痛点从"怎么写"变成了"怎么管":一个能跑起来的应用往往要同时拉起 MySQL、Nacos、网关和好几个业务服务,靠手敲 docker run 一条条起,参数容易漏、启动顺序难保证、环境重建一次就要重来一遍。Docker Compose 就是解决这一层问题的工具------用一份 yml 描述整个应用,一条命令拉起全部容器。
纲要
- Compose 解决什么问题 :单条
docker run只能管一个容器,集群部署需要"声明式"描述 - Compose 文件长什么样 :
version/services两层结构,字段与docker run参数的映射关系 - 字段速查 :
imagebuildportsenvironmentvolumesdepends_onnetworks - 两种安装方式 :二进制文件放到
/usr/local/bin与 Docker 插件(docker compose空格版) - 版本差异 :v1 横线版
docker-compose与 v2 空格版docker compose的区别与共存 - 安装验证 :
docker-compose --version/docker compose version的预期输出 - 常用子命令速查 :
up -dpslogs -frestartstopdowndown -vbuildconfig - 实战坑 :yml 缩进、
version字段告警、执行目录与-f、service 名与容器名不是一回事
Compose 解决什么问题
前面几节我们已经能用 Docker 部署中间件(Redis、MySQL),也能用 Dockerfile 把自研的微服务打成镜像再跑起来。但这些动作都有一个共同点:手动,一个个来。
bash
docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123 -v /tmp/mysql/data:/var/lib/mysql mysql:5.7.25
docker run -d --name nacos -p 8848:8848 -e MODE=standalone nacos/nacos-server
docker run -d --name userservice -p 8081:8081 userservice:1.0
docker run -d --name orderservice -p 8082:8082 orderservice:1.0
docker run -d --name gateway -p 10010:10010 gateway:1.0
小公司几十个服务,大公司几百上千个。逐个敲命令的问题不在于累,而在于不可复现:
- 某次漏了一个
-e,容器起来了但连不上数据库,排查半天 - Nacos 还没就绪,业务服务已经注册失败退出
- 换台机器重建环境,得把上面这串命令从头再抄一遍,抄错一个端口就出问题
- 想销毁重来,得记住每个容器的名字挨个
docker rm
Compose 的思路是把"怎么起"从命令式改写成声明式:用一份文本文件描述"集群里有哪些容器、各自怎么运行",然后交给 Compose 一次性翻译成对应的容器操作。
#mermaid-svg-wHFk1m2GbHxNBfKv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wHFk1m2GbHxNBfKv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wHFk1m2GbHxNBfKv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wHFk1m2GbHxNBfKv .error-icon{fill:#552222;}#mermaid-svg-wHFk1m2GbHxNBfKv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wHFk1m2GbHxNBfKv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wHFk1m2GbHxNBfKv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wHFk1m2GbHxNBfKv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wHFk1m2GbHxNBfKv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wHFk1m2GbHxNBfKv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wHFk1m2GbHxNBfKv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wHFk1m2GbHxNBfKv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wHFk1m2GbHxNBfKv .marker.cross{stroke:#333333;}#mermaid-svg-wHFk1m2GbHxNBfKv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wHFk1m2GbHxNBfKv p{margin:0;}#mermaid-svg-wHFk1m2GbHxNBfKv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wHFk1m2GbHxNBfKv .cluster-label text{fill:#333;}#mermaid-svg-wHFk1m2GbHxNBfKv .cluster-label span{color:#333;}#mermaid-svg-wHFk1m2GbHxNBfKv .cluster-label span p{background-color:transparent;}#mermaid-svg-wHFk1m2GbHxNBfKv .label text,#mermaid-svg-wHFk1m2GbHxNBfKv span{fill:#333;color:#333;}#mermaid-svg-wHFk1m2GbHxNBfKv .node rect,#mermaid-svg-wHFk1m2GbHxNBfKv .node circle,#mermaid-svg-wHFk1m2GbHxNBfKv .node ellipse,#mermaid-svg-wHFk1m2GbHxNBfKv .node polygon,#mermaid-svg-wHFk1m2GbHxNBfKv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wHFk1m2GbHxNBfKv .rough-node .label text,#mermaid-svg-wHFk1m2GbHxNBfKv .node .label text,#mermaid-svg-wHFk1m2GbHxNBfKv .image-shape .label,#mermaid-svg-wHFk1m2GbHxNBfKv .icon-shape .label{text-anchor:middle;}#mermaid-svg-wHFk1m2GbHxNBfKv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wHFk1m2GbHxNBfKv .rough-node .label,#mermaid-svg-wHFk1m2GbHxNBfKv .node .label,#mermaid-svg-wHFk1m2GbHxNBfKv .image-shape .label,#mermaid-svg-wHFk1m2GbHxNBfKv .icon-shape .label{text-align:center;}#mermaid-svg-wHFk1m2GbHxNBfKv .node.clickable{cursor:pointer;}#mermaid-svg-wHFk1m2GbHxNBfKv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wHFk1m2GbHxNBfKv .arrowheadPath{fill:#333333;}#mermaid-svg-wHFk1m2GbHxNBfKv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wHFk1m2GbHxNBfKv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wHFk1m2GbHxNBfKv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wHFk1m2GbHxNBfKv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wHFk1m2GbHxNBfKv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wHFk1m2GbHxNBfKv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wHFk1m2GbHxNBfKv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wHFk1m2GbHxNBfKv .cluster text{fill:#333;}#mermaid-svg-wHFk1m2GbHxNBfKv .cluster span{color:#333;}#mermaid-svg-wHFk1m2GbHxNBfKv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wHFk1m2GbHxNBfKv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wHFk1m2GbHxNBfKv rect.text{fill:none;stroke-width:0;}#mermaid-svg-wHFk1m2GbHxNBfKv .icon-shape,#mermaid-svg-wHFk1m2GbHxNBfKv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wHFk1m2GbHxNBfKv .icon-shape p,#mermaid-svg-wHFk1m2GbHxNBfKv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wHFk1m2GbHxNBfKv .icon-shape .label rect,#mermaid-svg-wHFk1m2GbHxNBfKv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wHFk1m2GbHxNBfKv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wHFk1m2GbHxNBfKv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wHFk1m2GbHxNBfKv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} docker-compose.yml
声明式描述
Docker Compose
容器: mysql
容器: nacos
容器: userservice
容器: orderservice
容器: gateway
创建专用网络
服务间用服务名互访
一句话概括:Compose 文件 ≈ 一组 docker run 命令的集合,只是把命令行参数换成了 yml 里的指令,并且额外接管了网络创建和启动顺序。
与 docker run 的能力对照
理解 Compose 最快的方式是拿一条现成的 docker run 和 yml 里的对应写法做对比。左边是命令行,右边是 Compose 字段:
| 能力 | docker run 参数 |
Compose 字段 | 说明 |
|---|---|---|---|
| 容器标识 | --name mysql |
service 名(mysql:) |
Compose 里用服务名代替容器名 |
| 镜像 | 命令末尾 mysql:5.7.25 |
image: mysql:5.7.25 |
直接拉取现成镜像 |
| 临时构建 | docker build . + docker run |
build: ./user-service |
两件事合并成一条字段 |
| 端口映射 | -p 8848:8848 |
ports: - "8848:8848" |
内部服务可不暴露端口 |
| 环境变量 | -e MYSQL_ROOT_PASSWORD=123 |
environment: 下键值对 |
语法不同,语义相同 |
| 数据卷挂载 | -v /tmp/mysql/data:/var/lib/mysql |
volumes: 列表 |
支持 $PWD 相对路径 |
| 后台运行 | -d |
默认就是后台 | Compose 无 -d 字段 |
| 网络 | --network xxx |
networks: |
不写则自动建默认网络 |
| 依赖顺序 | 无(靠人工等) | depends_on: |
控制启动先后 |
| 一键重建 | 无 | up -d / down |
整组容器一起生命周期管理 |
注意表的最后三行------这是 Compose 相比"把命令抄进脚本"真正多出来的东西。docker run 对"服务之间的依赖关系"和"整组容器的生命周期"完全无感,而这些恰恰是集群部署最容易翻车的地方。
Compose 文件结构
顶层只有两大部分:version 声明语法版本,services 下面每个子节点代表一个容器。
version
历史上 Compose 文件格式分 1.x、2.x、3.x 三大版本,不同版本支持的字段有差异。教学里常见的 version: "3.8" 就是 3.x 的最新小版本。
yaml
version: "3.8"
services:
mysql:
image: mysql:5.7.25
这里有个坑要提前说清:Compose V2 已经废弃 version 字段 。用新版 docker compose 执行带 version 的文件时,会打印一行告警:
text
WARN[0000] /path/docker-compose.yml: the attribute `version` is obsolete,
it will be ignored, please remove it to avoid potential confusion
这行只是提醒,不影响执行,现代写法是把 version 整行删掉。反过来,如果你的目标是兼容旧的 docker-compose v1,那 version 最好保留,而且服务名、字段名大小写都要按老版本规则来。
services
services 下的每个键是一个服务,对应一个容器。拿课程资料里的真实文件举例:
yaml
version: "3.2"
services:
nacos:
image: nacos/nacos-server
environment:
MODE: standalone
ports:
- "8848:8848"
mysql:
image: mysql:5.7.25
environment:
MYSQL_ROOT_PASSWORD: 123
volumes:
- "$PWD/mysql/data:/var/lib/mysql"
- "$PWD/mysql/conf:/etc/mysql/conf.d/"
userservice:
build: ./user-service
orderservice:
build: ./order-service
gateway:
build: ./gateway
ports:
- "10010:10010"
这份文件里有两种典型写法,值得分开看:
- 基于现成镜像 :
nacos和mysql都给了image,Compose 直接拉镜像起容器。mysql 挂了两个目录出来------数据目录和配置目录,容器删了数据还在。 - 临时构建再运行 :
userservice、orderservice、gateway只给了build,没有image。意思是先到指定目录下执行docker build生成镜像,再拿这个镜像创建容器。build一个字段相当于原来的docker build . && docker run ...两步。
还有一点容易疑惑:mysql 服务没有配 ports。这是有意的------微服务集群里 MySQL 只给集群内部的服务用,属于内网访问,没必要把 3306 暴露到宿主机上,少暴露一个端口就少一份风险。
字段速查
| 字段 | 作用 | 等价命令 | 注意 |
|---|---|---|---|
image |
指定镜像 | 命令末尾的镜像名 | 建议带 tag,如 mysql:5.7.25 |
build |
从目录构建镜像 | docker build |
值是目录路径,./ 代表当前目录 |
ports |
端口映射,宿主机:容器 | -p |
字符串形式,必须加引号 |
environment |
注入环境变量 | -e |
键值对;值含特殊字符要引号 |
volumes |
数据卷 / 目录挂载 | -v |
支持 $PWD 变量 |
depends_on |
声明启动依赖 | 无 | 只保证启动顺序,不保证服务就绪 |
networks |
指定网络 | --network |
同名网络内的服务可用服务名互访 |
container_name |
强制指定容器名 | --name |
不写则自动生成,且不允许扩容到多副本 |
command |
覆盖容器启动命令 | 命令末尾的 CMD | 用于临时改启动参数 |
restart |
重启策略 | --restart |
常见值 always、on-failure |
depends_on 这一行的注意点值得单独强调:它只控制容器创建和启动的先后顺序 ,不会等 MySQL 真正 ready。MySQL 进程起来了但还没初始化完接收连接,业务服务照样会连不上退出。真要等就绪,得配合健康检查 healthcheck 加 condition: service_healthy,或者干脆在应用层加重试。
安装 Docker Compose
Compose 是一个独立于 Docker 引擎的可执行工具,有两种获取方式。
方式一:二进制文件(v1 横线版)
从 GitHub Release 拉对应架构的二进制,放到 /usr/local/bin:
bash
# 下载并直接写入 /usr/local/bin/docker-compose
curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
uname -s 和 uname -m 会分别替换成系统名(如 Linux)和架构(如 x86_64),所以下载到的文件名形如 docker-compose-Linux-x86_64,落地后重命名为 docker-compose。
GitHub 直连速度通常很慢甚至超时,此时可以直接用课前资料里提供的 docker-compose 二进制文件,手动上传到 /usr/local/bin/ 目录,效果一样。
无论用哪种,都要补上执行权限:
bash
# 赋予可执行权限
chmod +x /usr/local/bin/docker-compose
chmod +x 就是给文件加执行位,加完之后 ls -l 能看到文件名变色,表示可以直接执行。
方式二:Docker 插件(v2 空格版)
Docker 官方后来把 Compose 收编为 CLI 插件,随 Docker 一起安装或通过包管理器装:
bash
# Debian / Ubuntu 系
apt-get update && apt-get install -y docker-compose-plugin
# RHEL / CentOS 系
yum install -y docker-compose-plugin
装完之后命令形式变了:不是 docker-compose(横线),而是 docker compose(空格,作为 docker 的子命令)。
v1 与 v2 的区别与共存
这是最容易踩的坑,两者的调用方式、可执行文件位置、行为都不完全一样。
| 维度 | Compose v1(横线版) | Compose v2(空格版) |
|---|---|---|
| 命令形式 | docker-compose up -d |
docker compose up -d |
| 可执行文件 | 独立二进制 docker-compose |
Docker CLI 插件,通常在 /usr/libexec/docker/cli-plugins/ |
| 安装方式 | 手动下载二进制 | 随 Docker / 包管理器安装 |
version 字段 |
必需 | 已废弃,写了会告警 |
| 容器命名 | 项目名_服务名_序号 |
基本一致,部分默认行为调整 |
| 现状 | 1.x 已停止维护 | 官方推荐 |
两个版本可以共存 ,二进制路径不冲突,docker-compose 和 docker compose 能同时执行。风险在于"你以为在跑 v2,实际敲的是 v1"------比如脚本里写了 docker-compose,但宿主机只装了插件,就会直接报 command not found。排查手段是先确认两个命令各自的版本和路径:
bash
which docker-compose
which docker
docker compose version
命令自动补全(可选项)
给 bash 加上 Compose 的补全脚本,敲 docker-compose 后按 Tab 能提示子命令和参数:
bash
curl -L https://raw.githubusercontent.com/docker/compose/1.29.1/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
国内网络访问 raw.githubusercontent.com 常解析失败,表现为命令卡住或报"无法解析主机"。临时方案是往 hosts 里补一条记录:
bash
echo "199.232.68.133 raw.githubusercontent.com" >> /etc/hosts
补全只是锦上添花,装不上不影响 Compose 本身使用。
安装验证
装完必须先验证,别等到部署时才发现在用哪个版本。
bash
# v1 横线版
docker-compose --version
预期输出:
text
docker-compose version 1.23.1, build b02f1306
bash
# v2 空格版
docker compose version
预期输出:
text
Docker Compose version v2.24.5
两条命令都报 command not found,说明没装成功或者路径不对,先 ls -l /usr/local/bin/docker-compose 确认文件在不在、有没有 x 权限。
常用子命令速查
up 和 down 是核心,其余都是围绕运行状态的观察和微调。
| 命令 | 作用 | 说明 |
|---|---|---|
docker compose up -d |
创建并启动全部服务 | -d 后台运行;首次会自动 build/拉镜像 |
docker compose up -d --build |
强制重新构建镜像再启动 | 改了 Dockerfile 或代码后必用 |
docker compose ps |
查看本组容器状态 | 只看当前项目,比 docker ps 清爽 |
docker compose logs -f 服务名 |
跟踪日志 | 不加服务名则输出全部 |
docker compose restart 服务名 |
重启指定服务 | 只重启,不重建容器 |
docker compose stop |
停止容器,保留容器和数据卷 | 可以再 start 回来 |
docker compose down |
停止并删除容器、网络 | 数据卷默认保留 |
docker compose down -v |
连同数据卷一起删除 | 危险,数据会丢 |
docker compose build |
只构建镜像不起容器 | 用于调试构建过程 |
docker compose config |
校验并打印合并后的配置 | 排查 yml 语法和变量替换 |
down -v 单独拎出来说:down 只删容器和网络,挂载出来的数据卷(比如 MySQL 的 data 目录)还在,下次 up 数据能恢复;down -v 会把匿名卷和命名卷一起删掉。-v 在某些版本里还会顺手带走 volumes 里 $PWD 映射的目录内容,生产环境敲之前一定确认是不是真的要清库。
实战坑
缩进必须是空格,不能是 Tab。 YAML 的层级完全靠缩进表达,混入 Tab 会直接报 found character '\t' that cannot start any token。IDE 里先把"Tab 转空格"打开,统一两格或四格都行,关键是整个文件一致。
version 写错会产生告警但不报错。 比如写 version: "3.x" 或者漏了引号被解析成数字,V2 会忽略它继续执行,V1 则可能直接报 Unsupported config option。用 V2 干脆删掉这一行最省心。
必须在 Compose 文件所在目录执行,或用 -f 指定。 docker compose up 默认读当前目录的 docker-compose.yml(也兼容 compose.yaml)。在别处执行会报找不到文件,正确姿势:
bash
# 显式指定文件路径
docker compose -f /opt/cloud-demo/docker-compose.yml up -d
service 名和容器名不是一回事。 service 名是 yml 里的键,比如 mysql;Compose 实际创建的容器名默认是 项目名_服务名_序号,形如 cloud-demo_mysql_1。好处是同名网络内其他服务可以直接用 mysql 这个服务名做主机名访问,不用关心真实容器名;代价是如果你手动写了 container_name: mysql,就锁死了单副本,没法 --scale mysql=2 扩容。
$PWD 是执行时的当前目录。 volumes 里写 $PWD/mysql/data 时,挂载的是你执行 docker compose 那一刻所在目录下的路径。用 -f 在别处执行时,$PWD 不会自动跟着变成文件所在目录,容易挂载到空目录上去。稳妥写法是用绝对路径,或者借助 Compose 提供的变量插值。
Docker 引擎 docker-compose.yml docker compose 开发者 Docker 引擎 docker-compose.yml docker compose 开发者 #mermaid-svg-COnImCKQzSneZ33p{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-COnImCKQzSneZ33p .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-COnImCKQzSneZ33p .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-COnImCKQzSneZ33p .error-icon{fill:#552222;}#mermaid-svg-COnImCKQzSneZ33p .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-COnImCKQzSneZ33p .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-COnImCKQzSneZ33p .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-COnImCKQzSneZ33p .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-COnImCKQzSneZ33p .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-COnImCKQzSneZ33p .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-COnImCKQzSneZ33p .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-COnImCKQzSneZ33p .marker{fill:#333333;stroke:#333333;}#mermaid-svg-COnImCKQzSneZ33p .marker.cross{stroke:#333333;}#mermaid-svg-COnImCKQzSneZ33p svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-COnImCKQzSneZ33p p{margin:0;}#mermaid-svg-COnImCKQzSneZ33p .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-COnImCKQzSneZ33p text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-COnImCKQzSneZ33p .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-COnImCKQzSneZ33p .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-COnImCKQzSneZ33p .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-COnImCKQzSneZ33p .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-COnImCKQzSneZ33p #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-COnImCKQzSneZ33p .sequenceNumber{fill:white;}#mermaid-svg-COnImCKQzSneZ33p #sequencenumber{fill:#333;}#mermaid-svg-COnImCKQzSneZ33p #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-COnImCKQzSneZ33p .messageText{fill:#333;stroke:none;}#mermaid-svg-COnImCKQzSneZ33p .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-COnImCKQzSneZ33p .labelText,#mermaid-svg-COnImCKQzSneZ33p .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-COnImCKQzSneZ33p .loopText,#mermaid-svg-COnImCKQzSneZ33p .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-COnImCKQzSneZ33p .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-COnImCKQzSneZ33p .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-COnImCKQzSneZ33p .noteText,#mermaid-svg-COnImCKQzSneZ33p .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-COnImCKQzSneZ33p .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-COnImCKQzSneZ33p .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-COnImCKQzSneZ33p .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-COnImCKQzSneZ33p .actorPopupMenu{position:absolute;}#mermaid-svg-COnImCKQzSneZ33p .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-COnImCKQzSneZ33p .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-COnImCKQzSneZ33p .actor-man circle,#mermaid-svg-COnImCKQzSneZ33p line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-COnImCKQzSneZ33p :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop 每个 service docker compose up -d 解析 yml(校验缩进/字段) 服务定义列表 创建/复用项目网络 build 或 pull 镜像 创建并启动容器 输出容器状态与名称 docker compose ps 列出本组容器
项目结构
Compose 项目通常和要被部署的应用放在一起,build 字段里的相对路径就从这里开始算:
tree
cloud-demo/
├── docker-compose.yml # 编排入口,一条命令拉起全部服务
├── mysql/
│ ├── data/ # 挂载到容器的 /var/lib/mysql
│ └── conf/ # 挂载到容器的 /etc/mysql/conf.d/
├── user-service/
│ ├── Dockerfile # build: ./user-service 据此构建
│ └── app.jar
├── order-service/
│ ├── Dockerfile
│ └── app.jar
└── gateway/
├── Dockerfile
└── app.jar
这个目录结构本身说明了一件事:Compose 把"每个服务的构建物料 + 整个集群的编排描述"收在一个目录下,搬机器时整目录拷走,up -d 就能还原环境,不再依赖散落在各处的命令历史。
官方文档
总结
- Compose 的本质是声明式替代命令式 :一份 yml 描述整个应用,
docker run的每个参数都能在文件里找到对应字段。 - 它比"把命令写进 shell 脚本"多出来的价值,在于整组容器的生命周期管理 和服务间网络与依赖的声明 ,这两点单条
run天生做不到。 - 文件结构只有
version+services两层,version在 V2 已废弃;每个 service 要么给image直接拉,要么给build现构建。 - 安装记住两条路:二进制放
/usr/local/bin加chmod +x是 v1 横线版,包管理器装的插件是 v2 空格版,两者能共存但要清楚自己敲的是哪个。 - 最容易出事的三个地方:yml 用 Tab 缩进、在错误目录执行导致找不到文件、以及手滑敲了
down -v删库。