5人小团队实战:用 Docker Compose 管好开发、测试、生产三套环境
创业初期就我们几个开发,没钱买 GitLab CI 服务器,也不想折腾 Jenkins,每次改完代码手敲命令部署,一不小心就在生产环境翻车。直到用了 Docker Compose + 环境分层的思路,才把这事管明白。
小团队的真正痛点是什么?
不是工具不够用,是流程太重。大厂那套 CI/CD Pipeline 对五个人来说完全是杀鸡用牛刀。我们要的是:改一行代码 → 两分钟搞定所有环境更新。
三层架构:一主多用的设计思路
核心思路很简单:docker-compose.yml 只写公共部分,环境差异全部抽离出来。
your-project/
├── docker-compose.base.yml # 所有环境的共同配置
├── docker-compose.dev.yml # 开发专用覆盖层
├── docker-compose.prod.yml # 生产专用覆盖层
├── .env # 默认变量(gitignore)
├── .env.dev # 开发环境变量(gitignore)
└── .env.prod # 生产环境变量(gitignore)
第一层:公共配置 base
# docker-compose.base.yml
version: '3.8'
services:
app:
build: .
restart: unless-stopped
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
第二层:开发环境的差异化
# docker-compose.dev.yml (开发热重载模式)
services:
app:
command: npm run dev # 热重载启动命令
ports:
- "3000:3000"
- "9229:9229" # 开个调试端口方便 node --inspect 连上去断点调试
db:
environment:
POSTGRES_DB: myapp_dev # 每个开发者本地独立数据库名,不会互相污染
networks:
default:
name: myapp_dev_network # 本地网络隔离,防止端口冲突影响其他项目
# env_file 自动加载,不需要手动指定,compose 会识别同名的 .env.dev 或通过 -f 指定时自动关联行为,但注意需要显式指定 env_file 参数才能生效,这里我们通过命令行参数来处理,见下方完整示例。
第三层:生产环境的加固
# docker-compose.prod.yml (生产安全模式)
services:
app:
command: npm start # 生产直接跑编译好的产物,不开热重载省资源
volumes:
postgres_data:
# 生产不暴露多余端口,外网流量统一走 nginx 反向代理,数据库不对外暴露连接,只允许内网访问。
命令行入口:一个脚本解决所有问题
每次 docker compose up 带一堆参数太容易出错,写个简单的 shell 别名或者脚本更实在:
#!/bin/bash
ENV=${1:-dev}
case $ENV in
dev)
COMPOSE_FILES="-f docker-compose.base.yml -f docker-compose.dev.yml"
ENV_FILE=".env.dev"
;;
prod)
COMPOSE_FILES="-f docker-compose.base.yml -f docker-compose.prod.yml"
ENV_FILE=".env.prod"
;;
esac
echo "==> Loading environment: $ENV"
docker compose $COMPOSE_FILES --env-file $ENV_FILE "$@"
保存为 ./dc,加上执行权限,之后的操作就变成了:
./dc dev up # 起开发环境,带热重载和调试端口
./dc prod up -d # 后台起生产服务
./dc prod logs -f # 看生产日志实时滚动
./dc dev down # 关掉开发服务
数据库迁移的注意事项
很多新手卡在这个环节——每次重新 up 数据库数据就丢了。在 docker-compose.base.yml 里加 volumes 就解决了:
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
# 但注意!如果想每次全新干净的环境,跑这个清理旧卷:
docker volume rm your-project_postgres_data # 卷名字是 项目名_卷名 的格式,可以用 `docker volume ls` 查看实际名字。
一键初始化脚本的完整形态
给新来的同事看的,让他 clone 代码后只需要跑一条命令就能干活:
#!/bin/bash
set -e
echo "==> Initializing development environment..."
if [ ! -f .env ]; then cp .env.example .env; fi
echo "==> Pulling latest images..." && ./dc pull || true
echo "==> Starting services..." && ./dc dev up --build
echo ""
echo "✅ Done! App should be running at http://localhost:3000"
首次 clone 的同事只要保证机器上装了 Docker Desktop,点两下就跑起来了。不用装 Node、不用装 Postgres、不用配各种全局依赖。
我们踩过的坑总结
问题一:macOS 上 Docker Desktop 的文件挂载性能差得要命,开发时 npm install 卡死。
解法:在 Dockerfile 里预先安装依赖,本地只挂源代码目录,或者干脆把 node_modules 也做 volume,但更推荐的做法是在容器内完成构建后同步回本地,或者接受第一次慢后面快的缓存逻辑。
问题二:Windows 和 macOS 上换行符不一致导致 shell script 执行报错。
解法:克隆仓库时强制使用 LF:git config --global core.autocrlf input,或者 Windows 用户装 WSL2 把项目放 Ubuntu 子系统里跑,体验接近原生 Linux。
问题三:多人同时修改 .env.prod 导致 git merge 一团糟。
解法:.env.* 文件全部加进 .gitignore,每个人本地自己维护一份副本,通过密码管理器或者私聊传递敏感信息。更正规的做法是用 sops/gopass secrets management 管理密钥,但这对五人小队来说已经属于过度工程化了,根据实际情况来。
这套方案的适用边界在哪里?
如果你的项目变成十几二十个人,或者出现了多个微服务需要相互调用,那这套文件的堆叠方式就会变得难以维护,建议升级到 Kubernetes 或者 Nomad。但在那之前,这套东西足够让你撑过 MVP 到产品 PMF 这个阶段了,省下来的精力用来写业务代码它不香吗?