一个稍微正经点的应用,很少只有一个容器。典型的 Web 系统至少要有应用服务、数据库、缓存三件套,可能还有反向代理、消息队列。如果全靠 docker run 一条条敲,你会陷入几个麻烦:容器之间的网络要手工建、依赖顺序要手工控、环境变量和端口散落在命令历史里、换台机器部署时根本记不住当初敲了什么。Docker Compose 就是为了解决这个"多容器编排"的痛点而生的。它用一个 YAML 文件描述整套应用,一条命令启动全部、一条命令拆掉全部。这篇文章讲清 Compose 的 YAML 结构、常用命令、变量与依赖管理,并给出一套可以直接跑的"Web + MySQL + Redis"完整示例,最后对比它和 docker run 的差异。
一、Compose 是什么,解决什么问题
Compose 的核心思想是"声明式"。你不再描述"先执行 A、再执行 B"的过程,而是在一个 compose.yaml 文件里声明"我要这么一套服务,它们长这样、这样连、这样存数据"。Compose 读了这个文件,替你算出该创建哪些容器、网络、卷,然后一次性拉起来。
它主要解决四件事:把一组容器当成一个整体来管理(启动、停止、重建);自动创建一个专用网络,让服务之间用服务名互相访问,不用记 IP;用文件固化端口、环境变量、卷挂载、依赖关系,使配置可版本化、可复现;通过 up/down 一条命令完成整套环境的起停,特别适合本地开发和中小型部署。
需要说明版本差异:老版本用的是独立的 docker-compose 二进制(v1,Python 写的),新版本是 docker compose 子命令(v2,Go 写的,随 Docker 一起装)。现在统一推荐用 docker compose。本文示例用 v2 语法。
二、compose.yaml 的顶层结构
一个 Compose 文件通常包含四个顶层字段:
| 字段 | 作用 | 是否必需 |
|---|---|---|
| services | 定义每个容器(镜像、端口、环境变量、卷、依赖等) | 必需 |
| networks | 定义自定义网络 | 可选,不写会自动建默认网络 |
| volumes | 定义命名卷 | 可选,用了命名卷才需要 |
| configs / secrets | 注入配置文件与敏感信息 | 可选,进阶用法 |
下面的骨架展示了各字段的位置关系:
services:
web:
image: nginx:1.25-alpine
ports:
- "8080:80"
networks:
- app-net
volumes:
- web-data:/usr/share/nginx/html
volumes:
web-data:
networks:
app-net:
driver: bridge
注意 YAML 的缩进:用空格,不用 Tab。绝大多数 Compose 报错都是缩进或键名拼错导致的,写完可以用 docker compose config 校验并展开。
三、常用命令速查
Compose 的命令不多,掌握这几条基本够用:
# 启动全部服务(后台) -d 表示 detached
docker compose up -d
# 前台启动并跟踪日志(调试常用,Ctrl+C 停止)
docker compose up
# 只启动指定服务及其依赖
docker compose up -d web
# 构建(有 build: 字段的服务)并启动
docker compose up -d --build
# 查看服务状态
docker compose ps
# 查看日志(-f 跟随,--tail 只看最后 N 行)
docker compose logs -f --tail=100 web
# 进入运行中的容器执行命令
docker compose exec web sh
docker compose exec db mysql -uroot -p
# 停止并删除容器、网络(默认保留命名卷)
docker compose down
# 连卷一起删(危险:会删除数据卷里的数据)
docker compose down -v
# 重启单个服务
docker compose restart web
两个容易混淆的点:docker compose down 默认不会删命名卷,这是为了防止误删数据,要连数据一起清掉必须显式加 -v;docker compose exec 是进正在运行的容器,docker compose run 是另起一个新容器执行一次性命令,别搞混。
四、变量与 .env 文件
硬编码在 YAML 里的密码、端口、版本号很快就会失控,Compose 支持用变量。有两种来源:一是同目录下的 .env 文件(自动加载),二是 shell 环境变量。
# .env 文件(与 compose.yaml 同目录,不要提交到代码仓库)
MYSQL_ROOT_PASSWORD=change_this_root_pwd
MYSQL_DATABASE=appdb
MYSQL_USER=appuser
MYSQL_PASSWORD=change_this_app_pwd
WEB_PORT=8080
IMAGE_TAG=1.25-alpine
在 YAML 里用 ${变量名} 引用,还可以给默认值 ${VAR:-默认值}:
services:
web:
image: nginx:${IMAGE_TAG:-alpine}
ports:
- "${WEB_PORT:-8080}:80"
这里有个安全提醒:.env 里往往包含密码,必须加入 .gitignore,绝不要把它提交到代码仓库。生产环境建议改用 secrets 或由部署系统注入环境变量。
五、depends_on 与 healthcheck
depends_on 控制启动顺序,但要特别注意它只保证"容器被启动了",不保证"服务已经能用了"。数据库容器启动到能接受连接有几秒到几十秒的差距,如果应用在数据库没就绪时就连接,很可能直接崩溃。
services:
app:
build: .
depends_on:
db:
condition: service_healthy # 等 db 健康后再启动
cache:
condition: service_started
要让 condition: service_healthy 生效,被依赖的服务必须定义 healthcheck:
services:
db:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
注意 healthcheck 里要写 $$MYSQL_ROOT_PASSWORD(两个美元符号),这样 Compose 会把 $$ 转义成一个 $ 交给容器内的 shell 展开,而不是在 Compose 解析阶段就被替换掉。start_period 给容器一段"养成期",这期间健康检查失败不计入 retries,对数据库这类启动慢的服务很有用。
六、完整示例:Web + MySQL + Redis 三件套
下面这份 compose.yaml 是一个可以直接跑的最小生产级示例,包含应用(以官方 nginx 示例替代)、MySQL、Redis,演示了端口、卷、网络、环境变量、健康检查、初始化脚本等关键点。
services:
web:
image: nginx:${IMAGE_TAG:-1.25-alpine}
container_name: demo-web
restart: unless-stopped
ports:
- "${WEB_PORT:-8080}:80"
networks:
- app-net
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./html:/usr/share/nginx/html:ro
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: mysql:8.0
container_name: demo-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: Asia/Shanghai
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
ports:
- "3306:3306" # 生产环境建议不对外暴露,仅内网访问
volumes:
- db-data:/var/lib/mysql
- ./initdb:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 5
start_period: 40s
networks:
- app-net
cache:
image: redis:7-alpine
container_name: demo-cache
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis-data:/data
networks:
- app-net
volumes:
db-data:
redis-data:
networks:
app-net:
driver: bridge
几个关键点说明。第一,restart: unless-stopped 让容器在宿主机重启或异常退出后自动拉起,生产环境基本必备。第二,MySQL 的 db-data 命名卷把数据持久化到容器之外,删容器不丢库。第三,./initdb 挂到 /docker-entrypoint-initdb.d,MySQL 官方镜像只在数据目录为空时执行其中脚本,用来做首次初始化。第四,command 覆盖默认启动参数,此处把字符集设成 utf8mb4,避免中文乱码。第五,服务之间通过服务名互访——应用里数据库地址写 db、缓存地址写 cache,Compose 的内置 DNS 会解析到对应容器。
启动与验证:
# 目录结构:compose.yaml / .env / nginx/conf.d/ / html/ / initdb/
docker compose up -d
docker compose ps
docker compose logs -f --tail=50
# 从 web 容器里测试到 db 的连通性(服务名即主机名)
docker compose exec web sh -c "ping -c1 db || getent hosts db"
docker compose down # 停止,保留数据卷
七、Compose 与 docker run 的对比
理解 Compose 的价值,最好拿它和 docker run 比。
| 维度 | docker run | Docker Compose |
|---|---|---|
| 配置形式 | 命令行参数,命令历史里 | 声明式 YAML 文件,可版本化 |
| 多容器管理 | 逐个手工起,顺序靠自己 | 一条命令起停整套 |
| 服务发现 | 需手工建网络并记录 IP | 自动建网络,服务名即域名 |
| 依赖顺序 | 手工 sleep 或用脚本控制 | depends_on + healthcheck |
| 可复现性 | 差,依赖操作记忆 | 强,文件即部署说明 |
| 环境差异 | 开发生产易不一致 | 同文件跨环境部署 |
| 适用规模 | 单容器、临时调试 | 多容器、中小型部署、开发环境 |
一个等价对照能说明问题。用 docker run 部署 MySQL 大概是:
docker network create app-net
docker run -d --name db --network app-net --restart unless-stopped \
-e MYSQL_ROOT_PASSWORD=xxx -e MYSQL_DATABASE=appdb \
-v db-data:/var/lib/mysql -v ./initdb:/docker-entrypoint-initdb.d:ro \
mysql:8.0
同样的事情,Compose 就是上面的 YAML 里一段 db: 定义。容器越多,docker run 的维护成本越呈指数上升,而 Compose 基本是线性增加几行。所以结论很直接:单容器调试用 docker run,多容器(两个及以上)就上 Compose。
需要提醒的是,Compose 是"单机编排"工具,它管理的是同一台宿主机上的容器。跨多台机器的集群编排是 Kubernetes 的领域。中小企业多数场景用一台(或几台各跑一套)Compose 就够了,不必为了用它而引入一整套 K8s。
常见问题
Q:改了 compose.yaml 后怎么让改动生效?
改配置后执行 docker compose up -d,Compose 会以最小改动重建受影响的容器。如果是改了 build: 相关的 Dockerfile,加 --build。
Q:docker compose down 会不会删数据?
默认不会,命名卷会保留。只有加 -v 才会连卷一起删。生产上养成"默认 down、确认后再加 -v"的习惯。
Q:服务之间为什么能直接用名字访问?
Compose 会创建一个默认网络并把所有服务接进去,同时提供内置 DNS,把服务名解析到对应容器 IP。所以连接串里写 db、cache 即可,无需知道具体 IP。
Q:depends_on 能保证数据库先就绪吗?
不能,它只管启动顺序。要保证"就绪",被依赖服务必须有 healthcheck,并配合 condition: service_healthy。否则应用仍需自己做连接重试。
Q:compose.yaml 和 docker-compose.yml 有什么区别?
就是文件名不同。Compose v2 推荐用 compose.yaml(或 compose.yml),旧文件 docker-compose.yml 仍被识别。建议统一用新名。
Q:Compose 能不能用于生产环境?
可以,很多中小型业务就是在一台配置较好的云主机上用 Compose 跑整套服务,配合 restart: unless-stopped 和日志限额即可稳定运行。它的局限是单机,没有跨机调度和高可用;当单机性能或可用性成为瓶颈时,再考虑上 Kubernetes。
Q:容器里的数据怎么备份?
Compose 管理的命名卷同样需要备份。常见做法是用一个临时容器挂载该卷并打包成 tar,具体命令在数据卷那一篇会详细展开。原则是:卷里的数据要定期导出到容器之外、机器之外的存储。
总结
Compose 的本质是把"一套多容器应用"声明在一个 YAML 里,实现整体起停、自动组网、配置固化。核心字段是 services,按需配 networks 和 volumes。
常用命令集中在 up -d、down、ps、logs、exec、build;牢记 down 默认不删卷,down -v 会删数据,执行前务必确认。
变量用 .env 管理,注意把 .env 排除出版本控制;依赖管理用 depends_on 配合 healthcheck,靠健康检查而非单纯的启动顺序来保证就绪。
真实部署里,restart: unless-stopped、数据卷持久化、初始化脚本挂载、字符集参数这几项几乎是标配,示例中的三件套可以按需增删服务直接复用。
选型上,单容器调试用 docker run,两容器以上就用 Compose;跨多机集群才轮到 Kubernetes,中小企业不必过度设计。
企业QQ咨询




