4. Docker Compose:从单容器到多服务编排
说实话,Docker 用顺手之后,你很快就会发现一个问题——单容器跑着挺爽,但真实项目哪有这么简单?一个 Web 应用背后,通常连着数据库、缓存、消息队列……手动一个个启动容器,再配置网络、管理依赖,那感觉就像在玩杂耍。
这时候,Docker Compose 就派上用场了。我个人习惯把它叫做「一键启动全家桶」。你只需要写一个 YAML 文件,把服务、网络、卷都定义好,一条命令就能拉起整个应用栈。
4.1 Compose 文件编写:YAML 里的「服务说明书」
Compose 文件的核心,说白了就是告诉 Docker:「我要跑哪些服务,它们之间怎么通信,数据存哪里。」
先看一个最基础的例子:
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
嗯,这里要注意几个关键点:
- version:指定 Compose 文件格式版本。3.8 是目前比较稳定的版本,兼容性好。
- services:定义所有容器服务。每个服务就是一个容器。
- volumes:声明持久化存储卷。数据库这类有状态服务,数据必须持久化。
.env 文件,把密码、端口这些敏感信息抽离出来。Compose 文件里用 ${VAR_NAME} 引用,既安全又灵活。
4.2 多容器应用编排:让服务「认识彼此」
多容器编排最核心的问题是什么?是服务发现。容器 A 怎么找到容器 B?
在 Compose 里,这个问题被优雅地解决了——服务名就是主机名。你想想看,web 服务要连接数据库,直接写 db:3306 就行了,不用管 IP 地址。
来看一个完整的 Web + DB 应用示例:
version: '3.8'
services:
web:
build: ./web
ports:
- "5000:5000"
depends_on:
- db
environment:
DB_HOST: db
DB_USER: app_user
DB_PASSWORD: ${DB_PASSWORD}
db:
image: postgres:14-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: app_user
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pg_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
pg_data:
这里有个细节——depends_on。它确保 db 服务先启动,web 再启动。但注意,它只保证启动顺序,不保证数据库已经就绪。我在项目中遇到过好几次,web 启动时数据库还没初始化完成,导致连接失败。
depends_on 的「假启动」问题,排查了整整一下午。解决方案是在应用代码里加重试机制,或者用 wait-for-it.sh 脚本等待端口就绪。
4.3 Compose 常用命令:每天都会敲的那些
说实话,Compose 的命令不多,但每个都很实用。我整理了一份常用命令表:
| 命令 | 作用 | 我的使用场景 |
|---|---|---|
docker-compose up -d |
后台启动所有服务 | 日常开发,一键拉起环境 |
docker-compose down |
停止并移除所有容器 | 清理环境,重新开始 |
docker-compose logs -f |
实时查看日志 | 调试时盯着看,特别有用 |
docker-compose ps |
查看服务状态 | 快速确认哪些服务在跑 |
docker-compose exec |
进入运行中的容器 | 比如 exec web bash 进容器排查 |
docker-compose build |
重新构建镜像 | 代码改了,需要重新打包 |
你可能会问:up 和 start 有什么区别?up 会创建并启动容器,start 只启动已经存在的容器。我一般只用 up,省心。
4.4 Compose 实战:Web + DB 应用完整部署
光说不练假把式。咱们来一个完整的实战——部署一个 Flask Web 应用 + PostgreSQL 数据库。
先看项目结构:
myapp/
├── docker-compose.yml
├── web/
│ ├── Dockerfile
│ ├── app.py
│ └── requirements.txt
└── db/
└── init.sql
第一步:写 Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
第二步:写 Compose 文件
version: '3.8'
services:
web:
build: ./web
ports:
- "8000:5000"
environment:
DB_HOST: db
DB_NAME: myapp
DB_USER: myuser
DB_PASSWORD: mypass
depends_on:
- db
db:
image: postgres:14-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypass
volumes:
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
第三步:启动
docker-compose up -d
就这么简单。等几秒钟,访问 http://localhost:8000,你的应用就跑起来了。
- 数据库初始化脚本放在
/docker-entrypoint-initdb.d/目录下,容器首次启动时会自动执行 - Web 服务通过环境变量获取数据库连接信息,不要硬编码
- 数据卷
pgdata保证数据库重启后数据不丢失
我记得有一次,团队新来的同事把数据库密码直接写死在代码里,结果上线前才发现要改十几个文件。从那以后,我强制要求所有项目必须用环境变量或 .env 文件管理配置。
好了,这一章的内容就到这里。Docker Compose 的核心思想就是「声明式管理」——你描述想要的状态,Compose 负责实现。下一章我们会聊聊更复杂的编排场景,比如多环境配置和扩展服务。