速读#
Compose 解决的不是少敲几条命令,而是把整套服务环境写成可提交、可复现、可 diff 的文件。这篇真正要记住三件事:环境该是代码,有状态服务必须有命名卷,启动顺序不等于服务就绪。
手敲的问题:下次很难和上次一样#
拿这个博客说:WordPress、数据库、Redis 三个服务。一个个 docker run,命令又长又容易错——漏环境变量、端口写反,排半天。
更早做 Django + Redis + Celery,光本地环境搭起来就半小时。手敲最大的问题不是慢,是下次重建,大概率和上次不一样。
环境该是代码#
Compose 把整套服务写进一个 compose.yml。选择它的理由就一句:环境该是代码——可提交、可复现、可 diff。
这三个「可」各自值钱:可提交,环境的每次变动都有记录,坏了能回到任何一版;可复现,换机器一句 docker compose up -d 拉起,和原来一样;可 diff,改动在提交前就能看清动了哪一行——手敲命令行时代,这三样一样都没有。而且 up 是幂等的:改完 yml 再执行,只重建发生变化的服务,敢反复跑。
services: app: build: . depends_on: [db] environment: DB_HOST: db db: image: mysql:8 volumes: - db_data:/var/lib/mysql
volumes: db_data:差点删没数据:破坏性藏在 -v 里#
有状态服务必须有命名卷。 容器的文件系统跟着容器走,容器删了就没了;命名卷独立于容器存在,容器怎么换,数据都在。一句话:容器可以扔,数据不能跟着扔。我曾经没挂卷,删容器把数据全弄丢过。
docker compose down # 默认保留命名卷docker compose down -v # 连数据卷一起删| 命令 | 数据卷 |
|---|---|
down | 保留 |
down -v | 删除 |
看上去只是「停服务」,加个 -v 就把数据带走。破坏性命令绝不盲执行:删卷前先 docker volume ls,想清楚卷里有没有要留的东西。
有状态的服务(容器里存了数据的),必须有命名卷兜底,否则别上线。数据不是配置,删了恢复不回来。
服务名就是主机名#
DB_HOST: db——app 用服务名 db 连库。Compose 起服务时会自动建一个内部网络,并带内部 DNS 把服务名解析成容器地址;不用手动建网络,更不用查容器 IP——容器重建后 IP 会变,服务名不会。
这个机制的适用范围比想象大:后来我把 cloudflared 和应用放进同一个 Compose 网络,隧道配置里直接写 app:8080 连容器,一样成立。同一 Compose 网络里,服务名即主机名——记住这一条,容器间互连基本不用再碰 IP。
depends_on 只管启动顺序#
depends_on 让 db 先起、app 后起。但要清醒:
进程起了 ≠ 依赖就绪。 db 容器起来了,不代表 MySQL 已经能接连接。app 可能在库还没 ready 时去连,直接失败。
| 概念 | 含义 |
|---|---|
| 启动顺序 | 容器谁先 start |
| 就绪 | 进程内服务是否真能接请求 |
要让 depends_on 真等到「能接连接」,得给依赖配 healthcheck,再声明等它健康:
db: image: mysql:8 healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s retries: 10
app: depends_on: db: condition: service_healthy就算配了这个,应用侧最好也带连接重试——healthcheck 只保证启动那一刻就绪,不保证之后 db 不重启。两道兜底不是重复,是各管一段时间窗。
这篇留下的不是 Compose 语法表,是三件事:
- 环境该是代码
- 有状态服务必须有卷
- 启动顺序 ≠ 就绪
三者一起,才支撑「换机器一键复现,且数据不丢」。
