用Docker Compose搞定多容器应用的那些事

Newb.旗施 工具 阅读 1,131
赞 20 收藏
二维码
手机扫码查看
反馈

我的 docker-compose.yml 写法,亲测靠谱

我一般写 Docker Compose 文件不会一上来就堆 services,而是先想清楚几个问题:这个项目要不要本地开发联调?要不要持久化数据?环境变量怎么管?网络怎么配?想明白了再动手,不然改来改去特别烦。

用Docker Compose搞定多容器应用的那些事

这是我现在用得最多的一种结构:

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
    env_file:
      - .env
    depends_on:
      - db
    stdin_open: true
    tty: true

  db:
    image: postgres:14
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: myapp_dev
      POSTGRES_USER: devuser
      POSTGRES_PASSWORD: devpass
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U devuser -d myapp_dev"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  pgdata:

这里有几个点我说说为啥这么写:

  • 使用 volume 排除 node_modules:你可能看到 - /app/node_modules 这一行有点奇怪,这是为了防止宿主机的 node_modules 覆盖容器里的,避免依赖执行异常。我之前没加这个,结果 npm install 装的东西全被清了,折腾了半天才发现是挂载覆盖的问题。
  • healthcheck 加了才靠谱depends_on 默认只等容器启动,不等服务真正 ready。PostgreSQL 启动要几秒,Node 服务一上来就连肯定失败。加上 healthcheck 之后,Compose 才会真正等数据库能连了再起 app。
  • env_file + environment 混着用:敏感配置放 .env 文件(记得 gitignore),通用的比如 NODE_ENV 直接写进去。别图省事全塞 environment 里,后期换环境容易炸。

这几种错误写法,别再踩坑了

下面这些是我和同事都犯过的错,有些现在看挺离谱,但当时真就这么干了……

1. 不加 healthcheck,瞎写 depends_on

services:
  web:
    depends_on:
      - db
  db:
    image: mysql:8.0

这种写法等于没写。web 是等 db 容器起来了才启动,但 MySQL 可能还在初始化,根本连不上。结果就是 Node 服务报 connect ECONNREFUSED,重试几次就崩了。我以前以为 depends_on 是“等它准备好”,后来才知道它只是“等它跑起来”。

正确做法要么加 healthcheck,要么在应用层加重试逻辑(比如用 backoff 库连数据库)。

2. 把所有东西写死在 yaml 里

environment:
  DATABASE_URL: postgres://devuser:devpass@db:5432/myapp_dev
  REDIS_URL: redis://redis:6379
  API_KEY: abc123xyz

千万别把密码、密钥这些东西直接写进 compose 文件!尤其是一不小心推到 GitHub 上,等于送黑客大礼包。API_KEY 这种应该走 env_file 或 secret 管理。

再说,开发、测试、生产环境 URL 肯定不一样,你难道每个环境都改一遍 yaml?太傻了。用 ${} 占位符才是正道:

environment:
  DATABASE_URL: ${DATABASE_URL:-postgres://devuser:devpass@db:5432/myapp_dev}

这样本地没设就用默认值,CI 或服务器上设置了就用外部值,灵活又安全。

3. 忘记命名 volume,数据一删就没

volumes:
  - ./data:/var/lib/mysql

这种叫“匿名挂载”,看着方便,实际巨坑。一旦你 docker-compose down -v,或者机器重启后路径变了,数据就没了。而且不好迁移。

我有个项目用户上传的图片全存在本地卷,一开始用的是上面这种写法,结果某次清理容器时手滑删了,恢复不了,产品经理差点把我开了……

现在我都显式定义 named volume:

volumes:
  pgdata:
    driver: local

这样 Docker 会统一管理,docker volume ls 能看到,备份迁移也方便。

实际项目中的坑

环境隔离做得不到位

我见过最乱的项目是:dev、staging、prod 全用一个 docker-compose.yml,靠手动改端口和镜像 tag 来区分。结果有一次上线,把 staging 的 DB 配置复制到了 prod 启动脚本里,连错了库,写坏数据。

现在我的做法是:

  • docker-compose.yml 放通用配置
  • docker-compose.override.yml 放本地开发覆盖(比如挂载代码、开热重载)
  • docker-compose.prod.yml 放生产配置(用 built 镜像、关调试、加 restart)

启动的时候:

# 本地开发
docker-compose up

# 生产部署
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

这样各环境隔离清晰,不容易搞混。

build 和 image 混着用,CI 构建慢

有段时间我们每次部署都在服务器上 build,因为 compose 文件里写了 build。结果每次都要拉代码、装依赖、编译,五六分钟起不来服务,发布体验极差。

后来改成:CI 里用 Docker Buildx 打包镜像,打上 tag 推到 registry;prod compose 文件只写 image,不写 build。

关键点是:build 只该在开发环境用。生产必须用预构建镜像,否则不可控也不可复现。

忘了设 restart 策略

线上服务器重启一下,服务全挂,还得手动上去 docker-compose up,这就离谱。一定要加:

restart: unless-stopped

这样除非你明确 stop,否则容器崩溃或机器重启都会自动拉起来。别偷懒。

还有些小建议

  • 别用 latest 标签。image: nginx:latest 看着省事,哪天 nginx 更新 break change,你就哭了。固定版本更稳。
  • 给容器起个好名字,方便排查。用 container_name 显式指定,比如 container_name: app-db,不然默认是 project_service_num,难读。
  • 日志别不管。加个 logging 配置,避免某个服务疯狂打日志把磁盘撑爆:
logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"
  • http 服务挂反向代理的话,别把 Nginx 也塞进主 compose 文件。单独拆出来做 reverse proxy 更灵活,多个项目可以共用。
  • 如果要用 secrets 或 configs(比如 TLS 证书),别硬编码。虽然大多数小项目用不到,但提前了解下机制没坏处。

最后一点:别指望 docker-compose 解决一切

docker-compose 在本地开发和小型部署里很好用,但上了规模就得考虑 Kubernetes 或 Nomad 了。别在 prod 死磕 compose 做集群、滚动更新、服务发现——不是它的活。

另外,compose 文件写得太复杂也不是好事。超过 50 行我就考虑拆成模块,或者用 Makefile 封装常用命令,比如:

up:
	docker-compose up -d

logs:
	docker-compose logs -f

down:
	docker-compose down --remove-orphans

prod-up:
	docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

这样团队新人也能快速上手,不用记一堆参数。

以上是我总结的最佳实践

这套写法从踩坑、重构、再踩坑过来,现在算是稳定了。当然也不是完美方案,比如多架构构建(arm/vs amd)还得额外处理,但对大多数中小型项目够用了。

如果你有更好的组织方式,或者更优雅的环境管理策略,欢迎评论区交流。咱们都是搬砖的,互相抄作业才能早点下班。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论