共计 3613 个字符,预计需要花费 10 分钟才能阅读完成。
Docker 30 天实战系列 · Day 15
你有没有经历过这样的场景:项目需要同时运行数据库、缓存、后端 API、前端应用,每次启动要敲五六条 docker run 命令,参数一长串,漏一个端口映射整个系统就跑不通?
Docker Compose 就是来终结这种痛苦的。 一个 YAML 文件定义所有服务,一条命令全部拉起。
什么是 Docker Compose
Docker Compose 是 Docker 官方提供的多容器编排工具。它的核心思想很简单:用一个 docker-compose.yml 文件,声明式地描述应用所需的所有服务、网络和存储卷,然后通过 docker compose up 一键启动整个应用栈。
你可以把它理解为一份 " 应用部署说明书 "。以前这份说明书在你脑子里,现在它变成了一个可版本控制、可复现、可分享的文件。
核心原理
Docker Compose 的工作流程分三步:
第一步:解析。 Compose 读取 docker-compose.yml,解析出每个服务的镜像、端口、环境变量、依赖关系等配置。
第二步:构建基础设施。 自动创建一个专属的 bridge 网络(默认以项目目录名为前缀),所有服务加入同一网络。同时创建声明的数据卷。
第三步:按依赖顺序启动容器。 根据 depends_on 声明的依赖关系,决定启动顺序。每个服务名自动注册为 DNS 名称——在容器内部,你可以直接用服务名当主机名访问其他服务。
这意味着:你再也不需要手动查容器 IP,也不需要 --link 做容器互联。Compose 内置的 DNS 解析帮你搞定一切。
YAML 配置详解
一个标准的 docker-compose.yml 长这样:
version: "3.8"
services:
# 后端 API 服务
backend:
build: ./backend # 从 Dockerfile 构建
ports:
- "8080:8080" # 宿主机端口: 容器端口
environment:
- DB_HOST=db # 直接用服务名作为主机名
- DB_PORT=5432
- REDIS_HOST=cache
depends_on:
- db
- cache
restart: unless-stopped
# 前端服务
frontend:
build: ./frontend
ports:
- "3000:3000"
depends_on:
- backend
# PostgreSQL 数据库
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret123
volumes:
- db_data:/var/lib/postgresql/data # 持久化存储
ports:
- "5432:5432"
# Redis 缓存
cache:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
db_data: # 声明命名卷
几个关键配置项拆解说明:
| 配置项 | 作用 | 注意事项 |
|---|---|---|
build |
指定 Dockerfile 路径并构建镜像 | 与 image 二选一,也可同时使用 |
image |
直接拉取已有镜像 | 指定版本号,避免用 latest |
ports |
端口映射 | 格式为 宿主机: 容器 ,注意端口冲突 |
volumes |
数据持久化 | 命名卷需要在顶层 volumes 声明 |
depends_on |
启动顺序控制 | 只保证启动顺序,不保证服务就绪 |
environment |
环境变量注入 | 敏感信息建议使用 .env 文件 |
restart |
重启策略 | 生产环境推荐 unless-stopped |
常用命令速查
掌握以下 8 个命令,日常使用基本够了:
# 启动所有服务(后台运行)docker compose up -d
# 查看运行状态
docker compose ps
# 查看日志(实时跟踪)docker compose logs -f backend
# 停止所有服务
docker compose stop
# 停止并删除容器、网络(保留数据卷)docker compose down
# 停止并删除一切(包括数据卷)——慎用
docker compose down -v
# 重新构建镜像并启动
docker compose up -d --build
# 进入某个服务的容器
docker compose exec backend sh
实战案例:全栈博客系统
假设我们要搭建一个博客系统:React 前端 + Node.js API + MongoDB 数据库 + Nginx 反向代理。
version: "3.8"
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- frontend
- api
frontend:
build: ./frontend
expose:
- "3000" # 只在内部网络暴露,不映射到宿主机
api:
build: ./api
expose:
- "4000"
environment:
- MONGO_URI=mongodb://mongo:27017/blog
depends_on:
- mongo
mongo:
image: mongo:7
volumes:
- mongo_data:/data/db
# 不映射端口到宿主机,只允许内部访问——更安全
volumes:
mongo_data:
这个配置有三个设计要点值得注意:
第一,Nginx 统一入口。 所有外部流量只通过 80 端口进入,由 Nginx 分发到前端或 API,减少暴露面。
第二,expose 代替 ports。 前端和 API 使用 expose 仅在 Compose 内部网络可达,外部无法直接访问。
第三,数据库不暴露端口。 MongoDB 没有映射到宿主机,只有同网络的 API 服务能连接,安全性大幅提升。
环境变量管理
硬编码密码在 YAML 里是大忌。推荐使用 .env 文件:
# .env(必须加入 .gitignore)POSTGRES_PASSWORD=my_secure_password
API_SECRET_KEY=a1b2c3d4e5
# docker-compose.yml 中引用
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
Compose 会自动读取同目录下的 .env 文件,将变量注入配置中。
常见问题与排坑
Q1:depends_on 设置了,但后端连不上数据库?
depends_on 只保证容器启动顺序,不保证服务就绪。PostgreSQL 容器启动后还需要几秒初始化。解决方案有两种:在应用代码中加入重连机制(推荐),或使用 healthcheck 配合 depends_on.condition:
services:
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 5s
timeout: 3s
retries: 5
backend:
depends_on:
db:
condition: service_healthy
Q2:修改了代码,docker compose up -d 没生效?
Compose 会复用已有镜像。代码变更后需要重新构建:docker compose up -d --build。
Q3:数据怎么没了?
如果你用了 docker compose down -v,数据卷会被一起删除。日常停止服务用 docker compose stop 或 docker compose down(不带 -v)。
Q4:多个项目端口冲突怎么办?
两个项目都映射了 5432:5432 就会冲突。解决方法:修改宿主机端口(如 5433:5432),或者不映射到宿主机——只要同一 Compose 网络内的服务能互相访问就够了。
Compose 与生产环境
Docker Compose 非常适合以下场景:本地开发、CI/CD 测试环境、个人或小团队的单机部署。
但它不适合大规模生产环境。原因很直接:Compose 只能管理单台主机上的容器,没有跨节点调度、自动扩缩容、滚动更新等能力。当你需要管理多台服务器上的容器集群时,应该考虑 Kubernetes 或 Docker Swarm。
一个务实的路径是: 用 Compose 把开发环境跑通,验证架构可行性,再迁移到 K8s 做生产编排。 两者的 YAML 配置思路相通,迁移成本并不高。
小结
Docker Compose 的价值可以用一句话概括: 把 " 一堆容器的启动方式 " 变成 " 一个可版本管理的配置文件 "。
你需要记住的核心操作就三步:写好 docker-compose.yml,执行 docker compose up -d,然后专注于写业务代码。
明天 Day 16,我们聊 Docker 网络模型——理解了网络,你才能真正掌控容器之间的通信。
如果这篇文章对你有帮助,欢迎点赞、在看、转发,这是我持续更新的最大动力。