Docker Compose 用一个 compose.yaml 描述一组相互依赖的容器。它最适合本地开发、集成测试和临时验证:让 API、数据库、缓存等依赖以可重复的方式一起启动,而不是维护一长串 docker run 命令。
本文只讲日常最常用的内容。生产编排、Kubernetes 转换、复杂扩缩容等问题应在服务本身稳定后再单独处理。
1. Compose 解决的是什么问题
一个后端服务往往不只需要应用进程,还需要数据库、缓存或消息队列。Compose 将每个容器声明为一个 service,并统一管理它们的镜像、端口、环境变量、网络和数据卷。
最重要的结果是:新同事拉取代码后,只需运行同一条命令,就能得到一致的本地依赖环境。
现代 Docker 使用 docker compose 子命令。下文使用 Compose Specification 的 compose.yaml 格式,不依赖旧的 docker-compose 独立命令。
2. 一个够用的 compose.yaml
以下示例启动一个 Go API 和 PostgreSQL。它覆盖了最常用的 build、image、ports、environment、depends_on、healthcheck 和 volumes:
services:
api:
build:
context: .
ports:
- "${API_PORT:-8080}:8080"
environment:
APP_ENV: development
DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app?sslmode=disable
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "${POSTGRES_PORT:-5432}:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 10
volumes:
postgres-data:
需要理解的只有几件事:
api和db是服务名;同一 Compose 项目内,API 通过db:5432访问数据库,不需要使用localhost。build.context: .表示 API 镜像由当前目录中的 Dockerfile 构建。ports左侧是宿主机端口,右侧是容器端口。若只有其他容器需要数据库,可删除数据库的ports,避免暴露到宿主机。postgres-data是命名 volume。删除数据库容器不会删除卷中的数据。depends_on配合 healthcheck 可以让 API 等待数据库健康后再启动,但应用仍应具备连接重试能力,因为运行中依赖仍可能重启或短暂不可用。
3. 最常用的命令
在包含 compose.yaml 的目录执行:
# 检查变量替换后的最终配置
docker compose config
# 前台构建并启动,适合首次排查启动问题
docker compose up --build
# 后台启动
docker compose up -d --build
# 查看服务状态与日志
docker compose ps
docker compose logs -f api
# 在数据库容器中执行命令
docker compose exec db psql -U app -d app
服务停止后:
# 停止并移除容器和默认网络,保留命名 volume 中的数据
docker compose down
# 同时删除命名 volume,会清空示例中的数据库数据
docker compose down -v
down -v 是破坏性操作,只应在明确不再需要本地数据时使用。
4. 环境变量与本地密钥
在项目根目录创建未提交的 .env:
POSTGRES_PASSWORD=local-only-password
API_PORT=8080
POSTGRES_PORT=5432
Compose 会用 .env 的值替换 ${POSTGRES_PASSWORD}、${API_PORT:-8080} 等占位符。.env 主要用于 Compose 文件变量替换;它不会自动把所有变量注入每一个容器。容器实际需要的变量应在 environment 中显式声明,或使用 env_file 指向专门的运行时变量文件。
不要提交真实密码。仓库应提供 .env.example,只保留变量名和安全的本地示例值。生产环境的密钥应由部署平台或密钥系统注入,而不是复用本地 .env。
5. 修改代码后如何更新
如果 API 镜像包含编译后的代码,修改源码后重新构建 API:
docker compose up -d --build api
如果只修改了 environment、端口映射或 Compose 配置,重建容器即可:
docker compose up -d --force-recreate api
数据库数据由 postgres-data 保存,通常不需要在 API 更新时重建数据库容器。只有修改数据库镜像、初始化策略或明确需要重置数据时,才处理 volume。
本地开发也可以把源码目录绑定挂载到容器中实现热更新,但这会引入宿主机权限、文件监听和依赖缓存差异。先保证镜像构建路径可用,再决定是否需要热更新优化。
6. Compose 中的网络和数据边界
同一 Compose 项目的服务默认加入隔离网络,可以直接通过服务名发现彼此。多数本地项目不需要手动声明 networks;只有需要连接外部现有网络或划分明确隔离域时,再增加网络配置。
容器可写层会随容器删除而消失。对数据库等有状态服务使用命名 volume;对本地代码、配置模板等由宿主机维护的文件,才考虑绑定挂载。不要把生产数据放在没有备份策略的宿主机路径中。
7. 常见问题
API 启动后仍连不上数据库
检查 DATABASE_URL 是否使用 db 而不是 localhost,再检查:
docker compose ps
docker compose logs db
docker compose exec db pg_isready -U app -d app
depends_on 只处理启动依赖,不能替代应用层的重试、超时和错误处理。
端口已经被占用
修改 .env 中的宿主机端口,例如将 POSTGRES_PORT 改为 15432,无需修改容器内的 5432。服务间通信仍然使用 db:5432。
数据库状态不符合预期
先确认是否仍在复用旧 volume。若确实需要重置本地数据,先备份需要的内容,再执行 docker compose down -v 并重新启动。
8. 日常检查清单
-
docker compose config能成功渲染最终配置。 - 服务通过服务名访问依赖,不在容器中使用
localhost。 - 本地密码放在未提交的
.env,仓库提供.env.example。 - 数据库等状态服务使用命名 volume。
- 应用对依赖连接具备超时和重试,不依赖固定启动顺序。
- 常用日志、状态、停止与清理命令已写入项目 README。
掌握这一个文件和这些命令,已经能覆盖大多数本地多服务开发场景。更复杂的部署需求应交给专门的生产编排方案,而不是让本地 Compose 文件无限膨胀。