小Cの已经记不起来的博客

用 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:

后面再踩到坑再来补。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放