第4章 Docker Compose编排:YAML语法、服务定义、依赖管理、环境变量注入
聊到Docker Compose,我得先坦白一件事。我刚接触容器编排那会儿,觉得Dockerfile已经够用了,干嘛还要多学一个工具?直到我在一个微服务项目里手动启动了十几个容器,端口冲突、启动顺序乱套、环境变量配错……折腾了一整天。嗯,从那以后,Compose就成了我工具箱里的标配。
说白了,Docker Compose就是让你用一份YAML文件,把多个容器的启动、网络、依赖、环境变量全部管起来。你想想看,一个命令docker-compose up,所有服务就绪,多省心。
4.1 YAML语法基础——别让缩进坑了你
YAML的语法其实很简单,但越简单的东西越容易出幺蛾子。我个人习惯用两个空格做缩进,坚决不用Tab。为什么?因为YAML解析器对Tab的态度,就像我对周一早上的闹钟——完全不兼容。
来看一个最基本的YAML结构:
# 键值对
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "80:80"
# 列表(用短横线)
volumes:
- ./data:/app/data
- ./logs:/app/logs
# 多行字符串(用 | 保留换行)
config: |
server {
listen 80;
server_name example.com;
}
记住三个要点:
- 缩进必须一致——要么全用空格,要么全用Tab,别混用
- 冒号后面要加空格——
key: value,不是key:value - 列表项用短横线+空格——
- item
4.2 服务定义——把每个容器说清楚
服务定义是Compose文件的核心。每个services下面的条目,就对应一个容器。我一般这样组织:
version: '3.8'
services:
# 前端服务
frontend:
image: myapp-frontend:1.2.0
container_name: app-frontend
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- API_URL=http://backend:8080
depends_on:
- backend
restart: unless-stopped
# 后端服务
backend:
build: ./backend
image: myapp-backend:latest
ports:
- "8080:8080"
volumes:
- ./backend/logs:/app/logs
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_USER=appuser
- DB_PASSWORD=${DB_PASSWORD}
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
# 数据库
db:
image: postgres:15-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:
这里有几个关键点:
- image vs build:用
image直接拉取现成镜像,用build从Dockerfile构建。我建议开发阶段用build,生产环境用image配合CI/CD推送的镜像。 - container_name:给容器起个固定的名字,方便用
docker logs或docker exec操作。但注意,如果做水平扩展,就别指定这个名字了。 - restart策略:
unless-stopped是我最常用的——除非你手动停了它,否则挂了自动重启。
4.3 依赖管理——谁先启动,谁后启动
微服务之间通常有依赖关系。比如前端依赖后端,后端依赖数据库。如果启动顺序乱了,前端连不上后端,直接报错。
depends_on就是干这个的。但这里有个坑——depends_on只保证启动顺序,不保证服务就绪。什么意思呢?比如数据库容器启动了,但PostgreSQL还没完成初始化,这时候后端去连它,照样连不上。
✅ 最佳实践:配合healthcheck使用condition: service_healthy。这样Compose会等数据库的健康检查通过后,才启动后端服务。
services:
backend:
depends_on:
db:
condition: service_healthy
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 5s
timeout: 3s
retries: 10
start_period: 30s # 给数据库30秒的初始化时间
我记得有一次生产环境出问题,就是因为没加start_period。数据库第一次启动要初始化数据,花了40秒,健康检查在30秒内重试了6次都失败,Compose直接标记为unhealthy,导致后端一直没启动。加了start_period后,问题就解决了。
4.4 环境变量注入——别把密码写死在文件里
环境变量是容器化应用传递配置的标准方式。但怎么管理这些变量,学问可不少。
方式一:直接在文件中写死
environment:
- DB_HOST=localhost
- DB_PORT=5432
这种方式只适合本地开发。千万别在生产环境这么干——你的数据库密码、API密钥都暴露在代码仓库里了。
方式二:引用宿主机的环境变量
environment:
- DB_PASSWORD=${DB_PASSWORD}
- API_KEY=${API_KEY}
启动时从宿主机环境变量读取。但问题来了——你需要在每台机器上都设置这些变量,维护起来很麻烦。
方式三:使用.env文件(我最推荐的方式)
在Compose文件同目录下创建一个.env文件:
# .env 文件
DB_PASSWORD=MyS3cretP@ss
API_KEY=abc123xyz
REDIS_HOST=redis.example.com
然后在docker-compose.yml里直接引用:
services:
backend:
environment:
- DB_PASSWORD=${DB_PASSWORD}
- API_KEY=${API_KEY}
- REDIS_HOST=${REDIS_HOST}
启动时,Compose会自动读取.env文件里的变量。而且.env文件可以加入.gitignore,避免提交到代码仓库。
💡 我的习惯:在项目里放一个.env.example文件,里面列出所有需要的变量名和示例值。新同事拉取代码后,复制一份改成.env,填上真实值就行。这样既安全又方便。
方式四:使用Docker Secrets(生产环境推荐)
对于敏感信息,比如数据库密码、TLS证书,我建议用Docker Secrets:
services:
backend:
secrets:
- db_password
- tls_cert
secrets:
db_password:
file: ./secrets/db_password.txt
tls_cert:
file: ./secrets/tls_cert.pem
Secrets会被挂载到容器的/run/secrets/目录下,应用从文件读取。这种方式比环境变量更安全——不会在进程列表、日志里泄露。
4.5 实战:一个完整的Compose文件
最后,我分享一个我在项目中实际用过的Compose文件模板。它包含了前面讲的所有要点:
version: '3.8'
services:
# Nginx反向代理
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/ssl:/etc/nginx/ssl
- static_volume:/static
depends_on:
- frontend
- backend
restart: unless-stopped
# Vue前端
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.prod
image: myapp/frontend:${VERSION:-latest}
volumes:
- static_volume:/app/dist
environment:
- VUE_APP_API_URL=https://api.example.com
restart: unless-stopped
# Django后端
backend:
build:
context: ./backend
dockerfile: Dockerfile
image: myapp/backend:${VERSION:-latest}
expose:
- "8000"
volumes:
- static_volume:/app/static
- media_volume:/app/media
environment:
- DJANGO_SETTINGS_MODULE=config.settings.${ENV:-production}
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_URL=redis://redis:6379/0
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
secrets:
- django_secret_key
restart: unless-stopped
# PostgreSQL数据库
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
# Redis缓存
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
postgres_data:
redis_data:
static_volume:
media_volume:
secrets:
django_secret_key:
file: ./secrets/django_secret_key.txt
这个模板涵盖了:
- 多服务定义(Nginx、前端、后端、数据库、缓存)
- 依赖管理(
depends_on+healthcheck) - 环境变量注入(
.env文件 + Secrets) - 数据持久化(命名卷)
- 构建与镜像分离(开发用build,生产用image)
你想想看,如果没有Compose,这些配置你得手动敲多少docker run命令?而且每次重启都要按顺序来,漏一个就全乱套。有了Compose,一个docker-compose up -d就搞定了。
📌 核心要点回顾:
- YAML缩进用空格,别用Tab
- 服务定义时,区分
image和build的使用场景 - 依赖管理要配合
healthcheck,别只靠depends_on - 环境变量用
.env文件管理,敏感信息用Secrets - 生产环境加上
restart: unless-stopped保证高可用
好了,这一章的内容就到这里。下一章我们会聊Docker Swarm集群管理——到时候你会看到,Compose文件稍微改改,就能直接部署到Swarm集群上,实现真正的容器编排和高可用。