Docker Compose 详解:从单容器到多服务编排的工程化入口

在学习 Docker 的过程中,很多人最开始接触的是 docker run。比如启动一个 Redis,可以写一条命令;启动一个 MySQL,也可以写一条命令;启动一个后端服务,再写一条命令。单独看这些命令都不复杂,但当一个真实项目需要同时依赖后端服务、数据库、缓存、消息队列、Nginx、对象存储、监控服务时,问题就会立刻变得混乱。
你不仅要记住每个容器的镜像名、端口映射、环境变量、挂载路径,还要处理容器之间的网络访问、启动顺序、数据持久化、日志查看、重启策略等问题。更麻烦的是,这套命令很难沉淀下来。今天你能手动启动成功,明天换一台机器、换一个同事、换一个环境,可能又要重新摸索一遍。
Docker Compose 解决的正是这个问题。
它不是用来替代 Docker 的,而是在 Docker 之上提供了一层更适合工程项目的声明式配置。简单说,Docker 负责运行容器,Dockerfile 负责构建镜像,而 Docker Compose 负责把多个容器组织成一套完整应用。
一、Docker Compose 是什么
Docker Compose 是 Docker 官方提供的多容器应用管理工具。它允许开发者通过一个 YAML 文件描述整套应用的运行结构,包括有哪些服务、每个服务使用什么镜像、暴露哪些端口、挂载哪些目录、使用哪些环境变量、依赖哪些其他服务、加入哪些网络等。
这个 YAML 文件通常叫:
bash
compose.yaml
或者:
bash
docker-compose.yml
现代 Docker 更推荐使用 compose.yaml,但旧项目中大量使用 docker-compose.yml,两者都很常见。
使用 Compose 之后,原本一堆分散的 docker run 命令,可以变成一个统一的配置文件。启动时只需要执行:
bash
docker compose up -d
关闭时执行:
bash
docker compose down
这就是 Compose 最大的价值:把"手动命令"变成"可复用的配置"。
二、为什么需要 Docker Compose

如果只是启动一个临时容器,docker run 足够了。例如:
bash
docker run -d -p 6379:6379 redis:7
这条命令能快速启动 Redis。但真实项目通常不是单容器。
以一个 Java 后端项目为例,它可能需要:
- Spring Boot 应用
- MySQL 数据库
- Redis 缓存
- Nginx 反向代理
- MinIO 对象存储
- Prometheus 监控
- Grafana 看板
如果全部手写 docker run,命令会很长,而且难以维护。比如 MySQL 要配置密码、数据库名、数据目录挂载;Redis 可能需要配置文件;Nginx 需要挂载配置和静态目录;后端服务要连接 MySQL 和 Redis,还要配置环境变量。
更关键的是,容器之间不能随便用 127.0.0.1 互相访问。因为每个容器都有自己的网络命名空间,在一个容器内部访问 127.0.0.1,指向的是它自己,而不是宿主机,也不是其他容器。
Compose 会默认为项目创建一个网络,同一个 Compose 项目里的服务可以通过服务名互相访问。比如后端服务要连接 MySQL,不应该写:
text
127.0.0.1:3306
而应该写:
text
mysql:3306
这里的 mysql 就是 Compose 文件中定义的服务名。
这件事非常重要。很多 Docker 初学者部署项目失败,本质上不是应用配置错了,而是没有理解容器网络模型。
三、Docker、Dockerfile、Docker Compose 的关系

这三个概念经常被混在一起,但它们的职责完全不同。
Docker 是容器运行平台。它负责镜像、容器、网络、数据卷等底层能力。
Dockerfile 是镜像构建说明书。它描述如何把代码、运行环境、依赖文件打包成一个镜像。
Docker Compose 是多容器编排说明书。它描述多个容器应该如何一起运行。
举个例子。一个 Spring Boot 项目的 Dockerfile 可能是:
dockerfile
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/app.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
这个文件只负责构建后端应用镜像。
但后端应用不能孤立运行,它还需要 MySQL 和 Redis。于是你可以用 Compose 把它们组织到一起:
yaml
services:
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
redis:
image: redis:7
Dockerfile 解决"这个服务怎么打包",Compose 解决"这些服务怎么一起运行"。
四、Compose 文件的基本结构

一个典型的 Compose 文件主要由三部分组成:
yaml
services:
...
networks:
...
volumes:
...
其中最重要的是 services。
services 用来定义服务。每个服务通常对应一种容器角色,例如 app、mysql、redis、nginx。
networks 用来定义网络。大多数情况下,即使你不写,Compose 也会自动创建默认网络。
volumes 用来定义数据卷。数据库、对象存储、上传文件目录等需要长期保存的数据,一般都要通过 volume 或宿主机目录挂载。
一个最小 Compose 文件可以这样写:
yaml
services:
redis:
image: redis:7
ports:
- "6379:6379"
启动:
bash
docker compose up -d
这时 Compose 会启动一个 Redis 容器,并把宿主机的 6379 端口映射到容器内部的 6379 端口。
五、services:Compose 的核心
services 是 Compose 文件的核心。所有容器服务都定义在这里。
例如:
yaml
services:
app:
image: my-app:latest
ports:
- "8080:8080"
这里定义了一个名为 app 的服务,它使用 my-app:latest 镜像,并把宿主机 8080 端口映射到容器 8080 端口。
常见配置包括:
yaml
services:
app:
image: my-app:latest
container_name: demo-app
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
volumes:
- ./logs:/app/logs
restart: unless-stopped
image 表示使用已有镜像。
container_name 表示指定容器名称。它不是必须的。小项目里可以写,方便查看;复杂项目中不一定建议强依赖它,因为 Compose 本身会根据项目名和服务名自动生成容器名。
ports 表示端口映射。
environment 表示环境变量。
volumes 表示目录或数据卷挂载。
restart 表示重启策略。常用的是 unless-stopped,意思是容器异常退出后自动重启,除非你手动停止它。
六、image 和 build 的区别
在 Compose 中,一个服务可以使用 image,也可以使用 build。
使用 image 表示直接拉取现成镜像:
yaml
services:
redis:
image: redis:7
使用 build 表示根据本地 Dockerfile 构建镜像:
yaml
services:
app:
build: .
也可以写得更完整:
yaml
services:
app:
build:
context: .
dockerfile: Dockerfile
image: demo-app:latest
这里的 context 表示构建上下文,通常是项目根目录;dockerfile 表示 Dockerfile 文件名;image 表示构建出来的镜像名称。
开发自己的后端服务时,通常使用 build。使用 MySQL、Redis、Nginx 这种成熟基础组件时,通常直接使用 image。
七、ports:端口映射
容器内部的端口默认不会直接暴露给宿主机。想从宿主机或外部访问容器服务,需要配置 ports。
例如:
yaml
ports:
- "8080:8080"
左边是宿主机端口,右边是容器端口。
如果写成:
yaml
ports:
- "9000:8080"
表示访问宿主机的 9000 端口,实际进入容器的 8080 端口。
这在多个服务内部端口相同的时候非常有用。比如两个应用容器内部都是 8080,但宿主机上不能同时占用同一个端口,因此可以分别映射成:
yaml
ports:
- "8081:8080"
和:
yaml
ports:
- "8082:8080"
需要注意,容器之间互相访问时,一般不需要走宿主机映射端口,而是直接通过服务名和容器内部端口访问。
例如 app 访问 redis,应该写:
text
redis:6379
而不是:
text
localhost:6379
也不是:
text
宿主机IP:6379
八、environment:环境变量配置
很多镜像通过环境变量进行初始化配置。例如 MySQL:
yaml
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: demo
后端服务也经常通过环境变量读取配置:
yaml
services:
app:
image: demo-app:latest
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/demo
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: 123456
SPRING_REDIS_HOST: redis
这样做的好处是镜像本身可以保持通用,不同环境通过不同环境变量控制行为。
比如开发环境使用:
text
SPRING_PROFILES_ACTIVE=dev
测试环境使用:
text
SPRING_PROFILES_ACTIVE=test
生产环境使用:
text
SPRING_PROFILES_ACTIVE=prod
这比把配置写死在镜像内部更合理。
九、volumes:数据持久化

容器是可以删除和重建的。如果数据只存在容器内部,容器一删,数据就没了。
数据库尤其不能这样。MySQL、PostgreSQL、Redis 持久化、MinIO、Elasticsearch 等服务都需要考虑数据持久化。
Compose 中常见的 volume 写法有两种。
第一种是 named volume:
yaml
services:
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
这里的 mysql_data 是 Docker 管理的数据卷。它会被挂载到容器内的 /var/lib/mysql。
第二种是 bind mount,也就是直接挂载宿主机目录:
yaml
services:
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./html:/usr/share/nginx/html
这种方式适合挂载配置文件、静态资源、日志目录。
二者区别很明显。
named volume 更适合数据库数据,因为它由 Docker 管理,路径不依赖具体宿主机目录结构。
bind mount 更适合开发环境和配置文件,因为它能直接映射当前项目目录,修改本地文件后容器里可以立刻看到。
需要特别注意:
bash
docker compose down
默认不会删除 named volume。
但:
bash
docker compose down -v
会删除 volume。数据库数据可能会直接消失。
所以在生产环境或重要测试环境中,不要随便执行 docker compose down -v。
十、networks:容器间通信

Compose 默认会为项目创建一个网络。同一个项目中的服务会加入这个网络,并且可以通过服务名互相访问。
例如:
yaml
services:
app:
image: demo-app
depends_on:
- mysql
mysql:
image: mysql:8.0
在 app 容器内部,访问 MySQL 应该使用:
text
mysql:3306
这里的 mysql 是服务名,不是容器名。
如果需要更明确地划分网络,也可以手动定义:
yaml
services:
app:
image: demo-app
networks:
- backend
mysql:
image: mysql:8.0
networks:
- backend
networks:
backend:
对于简单项目,默认网络就够了。对于复杂项目,比如前端代理层、后端服务层、数据库层,可以拆分多个网络,减少不必要的服务暴露。
例如:
yaml
services:
nginx:
image: nginx
networks:
- frontend
- backend
app:
image: demo-app
networks:
- backend
mysql:
image: mysql:8.0
networks:
- backend
networks:
frontend:
backend:
这种结构中,Nginx 可以连接外部和后端,后端服务和 MySQL 只在内部网络通信。
十一、depends_on:启动顺序不等于服务可用

很多人会写:
yaml
services:
app:
image: demo-app
depends_on:
- mysql
这表示 app 依赖 mysql,Compose 会先启动 MySQL,再启动 app。
但这里有一个常见误区:depends_on 不等于 MySQL 已经完全初始化完成。
MySQL 容器启动了,不代表数据库已经可以接受连接。尤其在第一次初始化数据库时,MySQL 可能需要几秒甚至几十秒才能真正可用。如果后端应用一启动就连接数据库,可能会报连接失败。
严谨的处理方式有两个。
第一,给数据库加 healthcheck:
yaml
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: demo
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p123456"]
interval: 10s
timeout: 5s
retries: 5
第二,应用侧要有重试机制。
在真实工程里,不能把服务启动成功完全依赖于容器启动顺序。服务之间的网络波动、数据库慢启动、配置中心延迟、注册中心延迟都可能出现。因此应用本身需要具备重试、超时和容错能力。
十二、一个完整的 Spring Boot + MySQL + Redis 示例

下面是一个相对完整的 Java 后端项目 Compose 示例:
yaml
services:
app:
build:
context: .
dockerfile: Dockerfile
image: demo-app:latest
container_name: demo-app
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: 123456
SPRING_REDIS_HOST: redis
SPRING_REDIS_PORT: 6379
depends_on:
- mysql
- redis
restart: unless-stopped
mysql:
image: mysql:8.0
container_name: demo-mysql
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: demo
TZ: Asia/Shanghai
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
redis:
image: redis:7
container_name: demo-redis
ports:
- "6379:6379"
restart: unless-stopped
volumes:
mysql_data:
启动:
bash
docker compose up -d --build
查看容器:
bash
docker compose ps
查看日志:
bash
docker compose logs -f app
进入容器:
bash
docker compose exec app bash
停止:
bash
docker compose down
如果只是重启后端服务:
bash
docker compose restart app
如果修改了代码并重新构建:
bash
docker compose up -d --build app
这套命令已经足够覆盖大部分开发和测试场景。
十三、Compose 常用命令
启动所有服务:
bash
docker compose up
后台启动:
bash
docker compose up -d
后台启动并重新构建镜像:
bash
docker compose up -d --build
停止并删除容器和网络:
bash
docker compose down
停止但不删除容器:
bash
docker compose stop
再次启动已停止的容器:
bash
docker compose start
查看运行状态:
bash
docker compose ps
查看日志:
bash
docker compose logs
实时查看某个服务日志:
bash
docker compose logs -f app
进入某个服务容器:
bash
docker compose exec app bash
拉取镜像:
bash
docker compose pull
构建镜像:
bash
docker compose build
删除容器、网络和数据卷:
bash
docker compose down -v
清理未使用资源:
bash
docker system prune
但清理命令要谨慎,尤其是在服务器上,不要不看内容就执行。
十四、Compose 适合什么场景
Docker Compose 最适合以下场景。
第一,本地开发环境。
开发一个后端项目时,可以用 Compose 一键启动 MySQL、Redis、MQ、MinIO、Nginx。这样每个开发者都能使用一致的基础环境,减少"我本地可以,你本地不行"的问题。
第二,测试环境。
测试人员或 CI 流程可以用 Compose 拉起一套临时环境,跑完测试后销毁。它比手动安装数据库和中间件更干净。
第三,单机部署。
对于个人项目、小型网站、内部工具、轻量 AI 服务,一台服务器配合 Docker Compose 完全够用。比如部署博客、Nginx、后端 API、数据库、Redis,都可以用 Compose 管理。
第四,AI 服务组合。
现在很多 AI 应用并不是一个模型服务就够了。它可能包括 vLLM、Embedding 服务、向量数据库、Redis、任务队列、后端 API、Web UI、Nginx。用 Compose 可以把这些服务统一管理起来。
第五,工程交付。
如果你要把一个项目交给别人运行,给一份 Compose 文件比写一大堆安装文档更可靠。别人只要安装 Docker,然后执行 docker compose up -d,就能启动整套服务。
十五、Compose 不适合什么场景
Compose 虽然好用,但它不是万能的。
它不适合复杂的多节点集群调度。比如几十台机器、数百个服务、副本自动扩缩容、滚动发布、服务发现、资源调度、复杂灰度策略,这些是 Kubernetes 更擅长的领域。
它也不适合承载高度复杂的生产治理体系。比如服务网格、自动伸缩、跨节点故障迁移、声明式资源调度、细粒度权限控制,这些不是 Compose 的核心目标。
Compose 更像是单机环境下的清晰编排工具。它的优势是简单、直接、易读、容易维护。对于中小型项目,它非常高效;对于大型平台,它通常只是开发和测试阶段的辅助工具。
十六、Docker Compose 和 Kubernetes 的区别
很多人会问:学了 Docker Compose,还需要学 Kubernetes 吗?
答案取决于你的场景。
Docker Compose 关注的是"在一台机器上,把多个容器按配置启动起来"。
Kubernetes 关注的是"在一个集群里,把大量容器作为资源进行调度和治理"。
Compose 的核心是简单。一个 YAML 文件,一条命令,适合快速启动应用。
Kubernetes 的核心是集群管理。它有 Deployment、Service、Ingress、ConfigMap、Secret、PVC、HPA 等一整套对象模型,适合生产级大规模部署。
如果你只是部署个人网站、内部工具、AI Demo、小型服务,Compose 足够。
如果你要管理几十个微服务,多个节点,高可用部署,自动扩缩容,滚动升级,那么 Kubernetes 更合适。
不要为了"技术更高级"而过早上 Kubernetes。很多项目不是死于技术不够高级,而是死于复杂度过早膨胀。Compose 的价值在于它能用很低成本解决 80% 的单机编排问题。
十七、实际使用中的常见坑
第一个坑:在容器里写 localhost 连接其他容器。
这是最常见的问题。容器内部的 localhost 指向容器自己,不是宿主机,也不是其他容器。服务之间访问要用服务名。
例如:
text
mysql:3306
redis:6379
第二个坑:忘记持久化数据库数据。
MySQL 如果没有挂载 volume,容器删掉后数据就可能丢失。数据库服务必须明确配置数据卷。
第三个坑:随便执行 docker compose down -v。
-v 会删除数据卷。对数据库来说,这可能是灾难。
第四个坑:把敏感信息直接写进 Compose 文件。
例如数据库密码、AccessKey、SecretKey。如果是公开仓库,不应该把这些内容直接写进去。更好的方式是使用 .env 文件,或者在生产环境中使用更安全的密钥管理方式。
第五个坑:过度依赖 depends_on。
depends_on 只能解决部分启动顺序问题,不是服务健康保证。数据库、注册中心、消息队列都可能启动慢,应用侧必须有重试能力。
第六个坑:端口冲突。
宿主机同一个端口只能被一个服务占用。如果启动失败,要检查是不是端口已经被占用。
可以用:
bash
lsof -i :8080
或:
bash
netstat -tunlp | grep 8080
第七个坑:镜像架构不匹配。
在 Mac ARM 上构建镜像,然后部署到 Linux x86 服务器时,需要注意平台架构。必要时要指定:
bash
docker buildx build --platform linux/amd64
或者在 Compose 中指定:
yaml
platform: linux/amd64
第八个坑:日志无限增长。
容器日志如果不限制,长期运行后可能占满磁盘。生产或长期运行环境需要配置日志轮转,或者接入日志系统。
十八、.env 文件的使用
Compose 支持读取 .env 文件。这样可以把变量从 YAML 文件中抽离出来。
例如 .env:
env
APP_PORT=8080
MYSQL_ROOT_PASSWORD=123456
MYSQL_DATABASE=demo
Compose 文件:
yaml
services:
app:
image: demo-app
ports:
- "${APP_PORT}:8080"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
这样做的好处是同一份 Compose 文件可以适配多个环境。开发环境、测试环境、生产环境只需要使用不同的 .env 文件。
但要注意,.env 不等于绝对安全。如果里面有密码,不应该提交到公开 Git 仓库。通常可以提交 .env.example,但忽略真实 .env。
例如 .gitignore 中写:
gitignore
.env
然后提供:
env
APP_PORT=
MYSQL_ROOT_PASSWORD=
MYSQL_DATABASE=
作为示例文件。
十九、一个 Nginx + 后端 + MySQL 的部署思路
真实部署中,常见结构是:
text
用户请求 -> Nginx -> 后端服务 -> MySQL/Redis
Compose 可以这样组织:
yaml
services:
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app
app:
image: demo-app:latest
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/demo
SPRING_REDIS_HOST: redis
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:7
volumes:
mysql_data:
Nginx 反代配置里,后端地址可以写:
nginx
proxy_pass http://app:8080;
这里的 app 不是域名,也不是宿主机名,而是 Compose 服务名。Nginx 和 app 在同一个 Compose 网络里,所以可以通过服务名通信。
这就是 Compose 网络带来的便利。
二十、Compose 的工程价值
Docker Compose 的价值不只是"少写几条命令"。
它真正改变的是工程交付方式。
没有 Compose 时,环境部署依赖个人经验。文档写得再详细,也可能因为系统版本、命令顺序、路径差异、依赖冲突而失败。
有了 Compose 后,环境本身被配置文件固化下来。服务、端口、变量、网络、数据卷都写在同一个文件里。这个文件可以进入 Git,可以版本管理,可以代码审查,可以回滚。
这就是基础设施即代码的一种轻量体现。
对于个人开发者来说,Compose 可以让你更快搭建项目环境。
对于团队来说,Compose 可以让环境更一致。
对于部署来说,Compose 可以让单机服务更容易维护。
对于学习 Docker 来说,Compose 是从"会启动容器"走向"会组织应用"的关键一步。
二十一、学习 Docker Compose 的正确顺序
学习 Compose 不应该一开始就背全部配置项。正确顺序应该是:
先理解 Docker 容器和镜像。
再理解 Dockerfile,知道镜像是怎么构建出来的。
然后理解端口映射、环境变量、数据卷、容器网络。
最后再学习 Compose。
Compose 本质上是把这些 Docker 能力用 YAML 文件组织起来。如果底层概念不清楚,看到 Compose 文件也只是记语法,遇到问题很难排查。
最小学习路径可以是:
第一,启动一个 Redis。
第二,用 Compose 启动 Redis。
第三,用 Compose 启动 MySQL,并持久化数据。
第四,用 Compose 启动一个后端服务连接 MySQL。
第五,加上 Redis。
第六,加上 Nginx。
第七,加上 .env。
第八,处理日志、健康检查、重启策略。
走完这条路径,基本就能应对大部分实际项目。
二十二、总结
Docker Compose 是 Docker 体系中非常实用的一层工具。它不负责构建单个服务的业务逻辑,也不负责像 Kubernetes 那样管理复杂集群。它的核心价值是把多个容器服务组织成一套可运行、可复用、可维护的应用环境。
对于开发者来说,Compose 是从单容器使用走向工程化部署的关键工具。
它让 MySQL、Redis、Nginx、后端服务、AI 推理服务、监控组件不再是一堆零散命令,而是一份清晰的 YAML 配置。
理解 Compose,要抓住几个核心点:
服务写在 services 里。
镜像用 image,本地构建用 build。
端口映射用 ports。
环境变量用 environment。
数据持久化用 volumes。
容器通信依赖 Compose 网络,服务之间用服务名访问。
启动顺序可以用 depends_on,但服务可用性还要靠健康检查和应用重试。
不要随便执行 docker compose down -v,因为它可能删除数据卷。
从工程角度看,Docker Compose 的最大意义是把运行环境从"口口相传的部署经验"变成"可以版本管理的配置文件"。这也是它在开发、测试、单机部署和小型服务编排中长期流行的原因。
如果说 Docker 解决的是"应用能不能装进容器",那么 Docker Compose 解决的是"多个容器能不能作为一套系统稳定运行"。
掌握 Docker Compose,才算真正迈过了 Docker 工程化使用的第一道门槛。