序言
在容器化技术迅速普及的今天,应用架构早已从单体走向微服务,随之而来的,是数量不断增长的容器实例与更加复杂的部署组合。对于开发和运维团队而言,单靠手动方式去创建、启动、管理这些容器,已经变成一件低效且极易出错的工作。
Docker Compose 正是在这样的背景下诞生。它以一个简洁的 YAML 文件,将复杂的多容器应用抽象成“可定义、可复用、可一键启动”的工程,使开发者能够在本地快速搭建完整的服务环境,让运维人员轻松管理多实例部署。通过它,我们可以避免重复繁琐的命令操作,更能像管理一个整体项目一样描述和控制成百上千个容器。
本篇文章将从 Compose 的基础概念、安装方式、常用命令到网络机制、外部容器链接方式等多个角度进行全面梳理,让你在理解 Compose 核心理念的同时,也能掌握实际使用中的细节与技巧,为构建稳定、灵活的容器化环境提供坚实基础。
简介
Docker Compose 是用于定义和运行多容器的云原生应用程序工具。
为什么使用 Docker Compose ?
在前文使用 Docker 容器时,我们需要定义并 build dockerfile 文件,然后启动运行它(不同容器可能还需要配置相关参数)。
但是,我们的系统一般都包含上百的服务,而每个服务又有多个实例,若全部采用手动方式来启动关闭的话,工作量之大可想而知。
因此,为了把程序员从这些繁杂的工作当中解脱出来,Docker Compose 便应运而生!
现状与作用
目前,Docker Compose 已被数百万的开发人员所使用,并在 GitHub 上拥有超过 650,000 个文件,由于它定义了一种基于多容器应用程序简单的与云和编排器无关的方式,因此被开发人员广泛接受。
Docker Compose 通过允许开发人员在单个文件中定义一个复杂的堆栈并使用单个命令运行它,极大地简化了开发人员到云程序和工具链的代码。
这样,便无需手动构建和启动每个容器,从而节省了开发团队的宝贵时间。
下载安装
不论你通过 Windows 还是 Mac 安装完 Docker Desktop ,都会默认自动安装好 docker-compose。
Linux 则需要额外安装,可以通过 docker-compose 下载地址下载,之后执行以下命令即可:
1 | sudo mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose |
基本概念
Docker Compose 允许用户通过一个单独的docker- compose.yml模板文件来定义一组相关联的应用容器为一个项目。
Docker Compose 有两个重要的概念:
- 服务(service):一个应用的容器,实际上可以包括若干运行相同镜像的容器
- 实例项目(project):由一组关联的应用容器组成的一个完整业务单元
Docker Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
Docker Compose 项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容 器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Docker Compose 来进行编排管理。
基本使用
Docker Compose 的使用步骤如下:
- 根据容器相关参数来编写
docker-compose.yml文件 - 执行
docker-compose.yml文件
下面以 MySQL 容器为例:
首先,根据 MySQL 相关参数来编写docker-compose.yml文件:
1 | version: '3.1' |
注意哦:vim 下输入:set paste则粘贴不会出现格式错误
之后就可以执行如下命令来构建容器啦:
1 | docker-compose up -d |
该命令默认会根据当前目录下的docker-compose.yml文件来构建容器。
Docker Compose 命令
以下命令前缀均为docker-compose(如docker-compose ps):
| 命令 | 说明 |
|---|---|
| docker-compose images [options] | 列出所有服务所使用的镜像信息 |
| docker-compose ps | 显示所有容器 |
| docker-compose up -d tomcat | 构建并启动 tomcat 容器 |
| docker-compose exec -it tomcat /bin/bash | 以交互式方式进入 tomcat 容器 |
| docker-compose start tomcat | 启动 tomcat 容器 |
| docker-compose restart tomcat | 重新启动 tomcat 容器 |
| docker-compose stop tomcat | 停止 tomcat 容器 |
| docker-compose rm tomcat | 删除容器(删除前必须关闭容器) |
| docker-compose down | 删除所有 tomcat 容器及镜像 |
| docker-compose build tomcat | 构建镜像 |
| docker-compose logs tomcat | 查看 tomcat 的日志 |
| docker-compose pause tomcat | 暂停 tomcat 容器 |
| docker-compose unpause tomcat | 恢复 tomcat 容器 |
docker-compose -f
默认情况下,Docker Compose 会查找当前目录下的 docker-compose.yml 文件。如果需要使用其他文件名或路径的 Compose 文件,就需要使用 -f 选项来指定。格式如下:
1 | docker-compose -f <file_path> [command] |
<file_path>:指定自定义的 Compose 文件路径。[command]:执行的 Docker Compose 命令,例如上面表格中的up、down、ps等命令。
实战案例
部署 Nginx
docker-compose.yaml文件如下:1
2
3
4
5
6
7
8
9
10
11
12services:
nginx:
image: nginx:stable-alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
volumes:
- ./logs:/var/log/nginx
- ./front:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/nginx.conf
注意,需要提前创建nginx.conf文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
# 注意下面的注释掉,不然可能和匹配规则冲突
#include /etc/nginx/conf.d/*.conf;
server {
listen 80;
server_name localhost;
location /app1/ {
alias /usr/share/nginx/html/app1/;
try_files $uri $uri/ /app1/index.html;
index index.html index.htm;
}
location /app2/ {
alias /usr/share/nginx/html/app2/;
try_files $uri $uri/ /app2/index.html;
index index.html index.htm;
}
location /prod-api/{
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://xxx:8080/;
}
# 避免actuator暴露
if ($uri ~ "/actuator") {
return 403;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
Nginx 映射多个前端服务时,如 app1、app2,前端打包时需要增加配置,比如部署在/app1/前缀下,需要根据前端技术栈修改构建配置:
- Vue.js:
vue.config.js → publicPath: '/app1/' - React (CRA):
package.json → "homepage": "/app1/"
参数详解
1 | Compose和Docker兼容性: |
depends_on
在docker-compose文件中,可以通过depends_on指定某个容器在其他容器启动后再启动,比如nacos容器增加:1
2depends_on:
- nginx
其含义如下:
- 仅控制 启动顺序:nacos 会在 nginx 容器启动后再启动
- 不保证 nginx 已经完全可用(比如已经监听 80 端口)
- 对于简单场景通常够用,但如果你需要确保 nginx 可响应 HTTP 请求,需要额外健康检查
如果希望 nacos 等待 nginx 完全可用(端口可访问)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46services:
nginx:
image: nginx:stable-alpine
container_name: nginx
restart: always
ports:
- "80:80"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 10s
retries: 5
timeout: 5s
nacos:
image: nacos/nacos-server:v2.5.2
container_name: nacos
restart: always
environment:
# === 运行模式 ===
- MODE=standalone
- PREFER_HOST_MODE=hostname
# === 启用外部 MySQL ===
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=192.168.3.42
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos
- MYSQL_SERVICE_USER=root
- MYSQL_SERVICE_PASSWORD=123456
# === 权限系统 ===
- NACOS_AUTH_ENABLE=true
- NACOS_AUTH_IDENTITY_KEY=SecretKey8901234567890123456789
- NACOS_AUTH_IDENTITY_VALUE=securityValue890123456789012345
- NACOS_AUTH_TOKEN=N1t+U5j9SuOT6UFomt8aV/CySbu0Qvy1CyR9g9mmvIc=
# === JVM 参数(根据机器调整) ===
- JVM_XMS=512m
- JVM_XMX=512m
volumes:
- ./nacos_data:/home/nacos/data
- ./nacos_logs:/home/nacos/logs
ports:
- "8848:8848" # 控制台 + OpenAPI
- "9848:9848" # gRPC
- "9849:9849" # gRPC
depends_on:
nginx:
condition: service_healthy
condition: service_healthy表示等待 nginx 健康检查通过后再启动 nacos,这样更加安全,避免 nacos 启动时 nginx 还没监听 80 端口导致请求失败。
链式顺序
我们可能有个疑问,对depends_on参数,会遵循链式前置原则嘛?比如在 nacos 中 depends_on 了 nginx,在 gateway 中又 depends_on 了 nacos,那么 gateway 是不是启动顺序排第三,即以上容器的启动顺序为 nginx、nacso、gateway。
答案是:没错,你的理解正确。
Docker Compose 会按链式顺序启动容器,链式依赖会累积顺序,gateway 会在 nacos 启动后再启动,nacos 会又在 nginx 启动后再启动。
healthcheck
healthcheck 基本结构如下:
1 | healthcheck: |
参数含义
test
test参数用于定义检测容器健康的命令或方式。
其语法格式如下:
exec form 存在CMD 或 CMD-SHELL两种格式可以选择:
1 | # 第一种格式 |
两种含义不同:
CMD: 执行命令数组,返回退出码判断状态0→ healthy- 非 0 → unhealthy
CMD-SHELL: 以 shell 执行字符串命令,支持管道和环境变量,CMD支持偏弱有更复杂的命令时建议使用此格式,上面的CMD-SHELL含义如下:curl -f→ 如果 HTTP 返回码 >= 400 会返回非 0|| exit 1→ 确保命令失败时退出码为 1,触发 unhealthy
test: none:none 表示禁用 healthcheck,即容器无健康检查。
interval
interval代表容器 执行健康检查的间隔时间,用于控制 healthcheck 命令执行频率,默认值为 30 秒。
一般建议默认即可,太短 → 频繁执行,增加 CPU / I/O 消耗,太长 → 容器状态更新慢,依赖服务启动等待更久。
语法示例
1 | interval: 10s |
timeout
timeout参数用于单次健康检查命令 允许执行的最长时间,如果超过这个时间,healthcheck 会认为本次失败。
timeout参数可以防止某些命令卡住或无限挂起,太短 → 健康检查容易失败,太长 → 状态更新慢。
语法示例
1 | timeout: 5s |
retries
retries代表允许连续失败的次数后才判定容器为 unhealthy,可以用来防止偶发网络或临时错误导致容器被误判 unhealthy,默认值为 3。
默认值情况下,前两次检查失败 → 状态仍为 starting,第三次失败 → 状态变为 unhealthy。
语法示例
1 | retries: 3 |
start_period(可选,但推荐)
start_period代表容器启动后的初始宽限期,在此期间,healthcheck 失败不会计入 retries,默认值为 0。
为了避免刚启动就被判定 unhealthy,推荐使用此参数,对于启动慢的服务(比如数据库或 Nacos)非常有用。
语法示例
1 | start_period: 10s |
性能影响
CPU / 内存消耗:
- 执行健康检查命令会占用少量 CPU / 内存
- 对轻量命令(curl 或 pg_isready)影响微乎其微
- 如果 healthcheck 太频繁(interval < 1s)或者命令复杂,会有明显性能影响
I/O 消耗:
- curl、数据库 ping、HTTP 请求都会产生网络 I/O
- 高频请求可能导致网络压力或服务端日志增多
生产推荐
| 参数 | 建议 |
|---|---|
| interval | 10~30s,依赖服务敏感度调整 |
| timeout | 不超过 1/2 interval |
| retries | 3~5 |
| start_period | 服务启动慢时设置,如 20~60s |
| test 命令 | 尽量轻量,避免复杂脚本 |
扩展─锚点合并
有时候,在docker-compose文件中,我们希望定义一组变量,然后在某个配置中继承,这可以使用 YAML 的锚点与合并(YAML Anchors & Merge Keys) 功能进行实现。
核心规则
&xxx= 定义一个锚点(anchor)*xxx= 引用一个锚点(alias)<<:= YAML merge key,用来“把锚点内容合并进来”
示例
配置继承
定义一个公共块:
1 | x-template: |
后续引用并合并:
1 | services: |
实际效果等价于:
1 | services: |
变量替换
1 | x-shared-env: &shared-env |
注意:锚点合并问题
以下配置存在问题:1
2
3
4gateway:
...
<<:
<<:
因为,YAML 不允许同级重复的 << 键
解决方案是把多个锚点放在一个列表里,形式如下:1
2
3gateway:
...
<<: [*nacosHealthy, *loggingStrategy]
文章信息
| 时间 | 说明 |
|---|---|
| 2019-06-11 | 初稿 |
| 2025-12-12 | 从主文章剥离重构 |