用 Docker Compose 搭建本地多服务开发环境的注意事项
最近接手了个老项目,README 里光启动本地环境就列了七八条命令:先起 MySQL,再起 Redis,跑一遍迁移脚本,改环境变量,最后还得手动往 hosts 里加一条本地域名。每次换机器或者拉新同事都要陪着重来一遍,烦得不行,索性花了个晚上用 Docker Compose 把这堆东西全塞了进去。过程不算顺利,踩了几个坑,记录一下,省得下次又忘。
depends_on 不等于“等它就绪了”
这是我踩的第一个坑。很自然地写了 depends_on,让 app 排在 mysql 后面,结果容器全部起来的瞬间,app 就开始报 connection refused。查了半天才知道,这玩意儿只管启动顺序,不管 MySQL 是不是真的能接受连接了。要配合 healthcheck 用:
services:
mysql:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 10
app:
depends_on:
mysql:
condition: service_healthy
这样 compose 会等 healthcheck 通过了才去起 app,世界清净了。
数据库数据别乱删
本地环境用一段时间,总想 docker compose down -v 清理干净,注意这个 -v 会把 named volume 一起删掉,等于数据库直接清库。我有一次就这么把攒了好几天的测试数据干没了... 虽然只是测试数据,也还是心疼了一下。所以清理的时候老老实实 down 不带 -v,真要删 volume 就先想清楚。另外千万别用 bind mount 挂数据库目录,尤其是 Mac 和 Windows 上,性能差到怀疑人生,数据卷用 named volume 就好。
代码挂载进去,热更新会失效
这个坑在 Mac 和 Windows 上特别明显。代码目录 bind mount 进容器之后,里面的 inotify 基本不工作,vite 或者 nodemon 的热更新要么不动,要么疯狂全量刷新。Linux 下没这个问题,所以网上很多教程压根不提,可能是我瞎没搜到吧... 我的做法是代码不打进镜像,用 compose 自带的 watch 做同步:
services:
app:
build: .
develop:
watch:
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: ./package.json
启动不用 up -d,直接 docker compose watch,改代码实时同步进容器,package.json 变了才重新 build。这个功能需要比较新的 Compose 版本,太老的话没准儿不支持,升级一下就好。
不需要在宿主机访问的服务,就别映射端口
一开始我把所有服务的端口全映射出来了,结果本机 3306 被占(之前装过 MySQL),报错还特别隐晦。后来想明白了,容器之间互相访问走的是 compose 自动创建的网络,直接拿服务名当主机名就行,比如 mysql:3306,根本不需要 ports。只把真正要从浏览器或者 Postman 访问的服务暴露出去,端口冲突的问题能少一大半。
子网冲突,遇到了才想起来
某天连上公司 VPN 之后,容器之间突然连不通了,数据库怎么都连不上。查了一圈发现是 compose 默认的子网跟 VPN 网段撞了。这种情况可以在 /etc/docker/daemon.json 里改默认地址池:
{
"default-address-pools": [
{ "base": "172.30.0.0/16", "size": 24 }
]
}
改完 systemctl restart docker 就正常了。规律大概是:不连 VPN 一切正常、一连 VPN 就抽风,八成就是这个。
密码别写死在 compose.yml 里
compose 会自动读同目录下的 .env 文件,所以数据库密码、第三方 key 都放那里,compose.yml 里用 ${MYSQL_ROOT_PASSWORD} 引用。.env 记得加进 .gitignore,仓库里放一个 .env.example 当模板。改完可以跑一下 docker compose config,看看渲染出来的配置对不对,变量没配它会直接警告你。
最后放一份我现在的 compose.yml,大概长这样,目前用着还算舒服:
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
retries: 10
redis:
image: redis:7-alpine
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: mysql://root:${MYSQL_ROOT_PASSWORD}@mysql:3306/app
depends_on:
mysql:
condition: service_healthy
develop:
watch:
- action: sync
path: ./src
target: /app/src
volumes:
mysql_data:
后面再踩到坑再来补。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。