从 docker run 到 docker compose:手把手改造与多环境配置管理
从 docker run 到 docker compose:手把手改造与多环境配置管理
摘要:当服务器上的 docker run 命令越积越多,参数越改越长,"哪个容器是谁起的、为什么挂了不自动重启"变成解不开的谜团。本文以一套真实的数据库加应用双容器架构为例,完整演示从 docker run 到 docker compose 的改造过程,覆盖启动顺序控制、环境变量分层、多环境配置切换与升级回滚,最后给出一份迁移对照表,适用于把零散容器统一为可维护编排的实战场景。
一、场景引入:一条长命令的崩塌
系列前面几篇里出现过这样的启动命令:
docker run -d \
--name timescaledb \
-p 5432:5432 \
-e POSTGRES_PASSWORD=xxx \
-v /data/pgdata:/var/lib/postgresql/data \
--restart always \
timescale/timescaledb:2.28.0-pg16
docker run -d \
--name backend \
-p 9018:8080 \
-e SPRING_DATASOURCE_URL=jdbc:postgresql://timescaledb:5432/mydb \
--restart unless-stopped \
backend:runtime
单容器时勉强能用,问题出在规模化之后:
- 配置不可追溯——半年后想改密码,得先
docker inspect翻历史再看 shell history,运气差一点就找不回完整参数; - 启动顺序失控——机器重启后 timescaledb 还在初始化,backend 已经起来连库报错,靠 Spring 重试机制侥幸扛过去,但没人知道能扛几次;
- 环境差异靠手工——测试机、预发、生产三套环境,靠三个运维各自维护一份 run 命令,差异点只能口口相传。
docker compose 把这三个问题一次性收编:所有配置进 yml、启动顺序靠声明、环境差异靠文件切换。
二、第一步:把 run 命令翻译成 compose
改造过程是一对一的参数映射,先看完整可用的 yml:
services:
timescaledb:
image: timescale/timescaledb:2.28.0-pg16
container_name: timescaledb
restart: always
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- /data/pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
start_period: 30s
networks:
- app-net
backend:
image: backend:runtime
container_name: backend
restart: unless-stopped
ports:
- "9018:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://timescaledb:5432/mydb
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
depends_on:
timescaledb:
condition: service_healthy
networks:
- app-net
networks:
app-net:
driver: bridge
几个关键映射关系:
--restart always→restart: always;-v 宿主机:容器→volumes:下的同一写法;-e KEY=value→environment:字段;- 隐式默认 bridge 网络里无法用容器名解析的问题,被顶层
networks:一并解决——两服务都进了自定义网络,backend 直接用timescaledb:5432连库,JDBC 配置从上一篇文章的"绕宿主机 IP"优雅升级为"服务名直连"。
改造完一条docker compose up -d拉起全栈,docker compose down一键停掉并清理网络,比逐个 run/stop 简洁得多。
三、关键陷阱:depends_on 不等于服务就绪
这是 compose 迁移中最经典的翻车点。
只写:
depends_on:
- timescaledb
只保证容器启动顺序,不保证数据库真正可连接——compose 只等 timescaledb 容器状态变成 running,而 PG 这时候可能还在初始化数据目录,backend 立刻连库就是 Connection refused。
正确姿势是给数据库配 healthcheck,让依赖方声明 condition: service_healthy:
depends_on:
timescaledb:
condition: service_healthy
compose 会一直等 pg_isready 探测通过才启动 backend,不需要 sleep 也不需要 wait-for-it sh 脚本。start_period 参数容易被忽略——它表示容器启动后的前 30 秒内 healthcheck 失败不计入重试,给慢启动服务留缓冲,MySQL/PG 这类一定要给够。
三种 condition 的适用场景:
| condition | 等待条件 | 典型场景 |
|---|---|---|
service_started |
容器启动(默认) | 真正无依赖关系的并行服务 |
service_healthy |
健康检查通过 | 数据库、缓存等持续服务 |
service_completed_successfully |
一次性任务退出码为 0 | 数据库迁移、初始化脚本 |
四、环境变量分层:三层配置策略
环境变量管理是 compose 迁移的第二大深水区,三种来源名字长得像但作用完全不同:
.env文件:用于 compose 文件本身的变量插值,解决${DB_PASSWORD}这种占位符;env_file:字段:把指定文件里的变量注入容器运行时环境;environment:字段:在 yml 里硬编码,优先级最高。
推荐的三层结构:
# docker-compose.yml 里只放结构,不放真实值
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
# .env.common(提交到 git,不含敏感信息)
DB_HOST=timescaledb
DB_PORT=5432
DB_NAME=mydb
# .env.prod(不提交 git,敏感信息)
DB_PASSWORD=prod_real_password
启动时切换:
docker compose --env-file .env.prod up -d
优先级口诀:命令行 -e > environment 字段 > env_file > .env 文件 > 系统环境变量。同名变量按这个顺序覆盖,排错时按这个顺序倒查即可。
一个高频坑:改了环境变量后 docker compose restart 不会让新变量生效——restart 只是重启进程,配置注入发生在容器创建阶段。必须 docker compose up -d(compose 会检测配置变化重建容器)。
五、多环境配置的标准姿势
三套环境的最小差异配置方案:
project/
├── docker-compose.yml # 基础结构(必放)
├── docker-compose.override.yml # 本地开发覆盖(自动加载)
├── .env.common # 公共变量
├── .env.dev # 开发环境变量
├── .env.prod # 生产环境变量(gitignore)
└── .env.example # 模板文件,给新同事参考
两种组织思路:
- 单文件 + 多 env(推荐小中型):所有环境共用一份 yml,差异全部抽到 env 文件,启动时
--env-file切换; - 多 yml 覆盖(适合结构差异大的环境):base 文件加 override 文件,compose 自动合并后者。预发环境端口不同、生产环境挂载路径不同这类结构性差异用这种方式更清晰。
.env.example是团队协作的关键——新人入职照着模板建自己的 env 文件,不用追着问"这个变量哪儿来的"。
六、迁移不是一次性动作:升级与回滚
compose 化之后,日常运维从"翻命令"变成"改文件":
升级服务:
# 改 yml 中的 image 版本
docker compose pull
docker compose up -d
compose 自动比对配置差异,只重建受影响的服务,依赖它的服务也会跟着重建。
回滚:
# yml 改回上一版本
git checkout HEAD~1 -- docker-compose.yml
docker compose up -d
yml 进 git 就是天然的回滚凭证,这是 docker run 时代无法想象的便利。
查看实际生效的配置(排错时必用):
docker compose config
它把变量插值后的最终 yml 打印出来,环境变量没生效时第一个命令就是它。
七、迁移对照表
| docker run 参数 | compose 字段 | 备注 |
|---|---|---|
--name |
container_name: |
同项目内建议保留 |
-p |
ports: |
列表形式,注意加引号防 YAML 解析坑 |
-v |
volumes: |
bind 与命名卷写法一致 |
-e |
environment: |
敏感值用 ${VAR} 插值 |
--restart always |
restart: always |
完全对应 |
--network |
顶层 networks: + 服务级引用 |
自定义网络才能容器名解析 |
--memory/--cpus |
deploy.resources.limits: |
compose v2 下直接生效 |
--add-host |
extra_hosts: |
数组形式 |
| 启动顺序 | depends_on + condition |
配合 healthcheck 使用 |
八、总结
docker run 到 compose 的改造,本质是把命令行的命令式操作升级为文件的声明式管理——配置可追溯(进 git)、依赖可声明(healthcheck + condition)、环境可切换(env 分层)、回滚可执行(git checkout + up)。
迁移决策点只有两个:单容器简单场景维持 docker run 即可;一旦出现多服务、有启动顺序依赖、需要在多环境间切换,就该 compose 化。改造时把 healthcheck、env 分层、networks 三件事一次做对,后面所有运维操作都会顺畅得多。
- 点赞
- 收藏
- 关注作者
评论(0)