还记得刚入行那会儿,每次接手新项目,最让我头疼的不是代码逻辑,而是那个写得乱七八糟的 README.md。
“先装个 MySQL 5.7(不能是 8.0 哦),再起个 Redis,然后由左向右分别打开三个终端窗口,依次运行这几行命令……”
我曾经天真地以为,只要我把这些步骤写成一个 start.sh 脚本,就能一劳永逸。直到三年前在一个中型电商项目的重构中,我踩了一个巨大的坑:因为团队里有人的 Python 版本不一致,我的“完美脚本”在他电脑上直接报错,最后还得手把手去调环境。
那天我就在想:要是能像点菜一样,一张单子列清楚,厨房照着做,做完直接端上来,该多好?
这就是我后来死磕 Docker Compose 的起点。今天想站在“过来人”的角度,和大家复盘一下我是怎么用它把团队的开发效率拉回正轨的。
一、 别再用 Shell 脚本“假装”编排了
很多中小团队(包括当年的我们)都有个误区:觉得 Docker Compose 是用来部署生产环境集群的,本地开发用 Shell 脚本或者直接敲命令就够了。
大错特错。
那时候我们维护着一个包含 Web 端、API 服务和数据库的系统。每次启动,我要先确认数据库活了没有,再启动 API,最后起前端。为了解决“依赖顺序”问题,我在脚本里简单粗暴地加了 sleep 10。
结果显而易见:电脑慢的人 10秒不够,服务崩了;电脑快的人白等 10秒,浪费生命。
后来引入 Docker Compose,最大的改变不是技术上的,而是思维模式从“命令式”变成了“声明式”。
看看这个对比,你就懂我的意思了:
以前的笨办法:
# 这种脚本我看一眼就头大
docker run -d --name db postgres:13
sleep 5 # 听天由命的等待
docker run -d -p 8080:8080 --link db:db my-backend
Docker Compose 的优雅(docker-compose.yml):
version: '3.8'
services:
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: secret
backend:
build: ./backend
ports:
- "8080:8080"
depends_on: # 重点在这:显式声明依赖
- db
自从用了 depends_on,我再也不用在那儿猜数据库到底起没起来。更重要的是,这份 YAML 文件成了项目文档的一部分。新来的实习生,只要装好 Docker,一句 docker compose up,三分钟内就能在本地把整个复杂的微服务跑起来。
团队里的后端老哥曾跟我吐槽:“以前配环境要一天,现在只要去楼下买杯咖啡的时间。”
二、 那些年我们写死的 localhost
刚开始用容器的时候,我踩过最痛的一个坑就是网络通讯。
当时为了省事,我在代码配置文件里直接把数据库地址写成了 localhost 或者 127.0.0.1。在非容器环境这没问题,但一旦把服务放进容器里,localhost 指的是容器自己,而不是你的电脑,也不是隔壁的数据库容器。
为了解决这个问题,我甚至干过把宿主机 IP 硬编码进配置文件的蠢事。结果换个 Wi-Fi,IP 变了,服务全挂。
其实,Docker Compose 自带了一个非常聪明的 DNS 服务。
在同一个 docker-compose.yml 里,服务名就是域名。你根本不需要关心 IP 是多少。
实战场景: 假设你的后端服务是用 Node.js 写的,需要连接 Redis。
不要在代码里写 IP,直接用服务名:
services:
my-redis: # 这个服务名就是域名
image: redis:alpine
app:
build: .
environment:
- REDIS_HOST=my-redis # 直接填服务名
我当时为了验证这个,特意进到 app 容器里 ping 了一下 my-redis,看到通了的那一刻,我把项目里所有杂乱的 IP 配置全删了。这不仅解决了连接问题,还让你的架构图极其清晰——看 YAML 文件就知道谁连谁。
三、 环境一致性:消灭“在我这能跑”
做运维或者 Dev 的朋友,最怕听到开发人员说:“奇怪,在我本地是好的呀,怎么上线就崩了?”
这通常是环境变量背的锅。
两年前,我有次周五下午发布(虽然不建议周五发布,但你们懂的),生产环境直接 500 报错。排查了半小时发现,本地开发用的数据库密码是 123456,而生产环境是强密码,且通过环境变量注入的逻辑在代码里有个 Bug。
那个周末过得很不愉快。在那之后,我给团队定了个死规矩:所有变量配置,必须走 .env 文件。
Docker Compose 对 .env 的支持简直是神来之笔。
我的落地方法:
- 项目根目录下放一个
.env.example(模版),里面列出所有需要的变量名,但值留空或给默认值。 .gitignore必须忽略.env(防止把私钥传到 Git 上)。docker-compose.yml里引用这些变量。
# docker-compose.yml
services:
web:
image: my-app:${TAG_VERSION} # 版本号动态控制
ports:
- "${HOST_PORT}:80"
# .env 文件
TAG_VERSION=v1.2.0
HOST_PORT=8080
这样一来,开发环境、测试环境、生产环境使用同一套 docker-compose.yml,只需要切换不同的 .env 文件。
我现在有个习惯,每接手一个新项目,先看有没有 .env.example 和 docker-compose.yml。如果有,说明这个团队的技术规范做得不错;如果没有,我大概率要做好踩坑的心理准备了。
既然聊到这,总结一下
Docker Compose 不是万能的,它不适合管理成百上千个跨服务器的微服务(那是 K8s 的地盘),但对于单机编排、本地开发环境搭建、中小规模的私有化部署,它是目前性价比最高的选择。
它把原本分散在文档、脑子、和各种脚本里的知识,固化成了一份可执行的代码。
如果你想从今天开始改变,建议尝试这 3 个小步骤:
- 盘点现状:找一个你手头最麻烦的、需要起多个服务的项目。
- 编写 YAML:不要追求完美,先写一个能把所有服务都拉起来的
docker-compose.yml,哪怕没有挂载数据卷也没关系。 - 一键启动:删掉原来的启动文档,尝试用
docker compose up -d替代,如果不通,就看日志修 YAML,直到通为止。
当你第一次看着终端里整齐划一的 Started 提示,相信我,你会爱上这种掌控感的。
最后留个话题: 你们团队现在的本地开发环境是怎么搭建的?还在用文档+手动挡,还是已经上了容器化?欢迎在评论区聊聊那些年踩过的环境坑。