Go 项目使用docker compose的正确方式

我第一次接手一个"成熟"的 Go 项目时,光是让它在本地跑起来就花了一整个下午。docker compose up 之后,终端疯狂报错,数据库连接被拒绝、Redis 连不上、Kafka 还没就绪。翻了一圈文档,发现前人留下的"解决方案"是:在启动脚本里加了个 sleep 10

后来同事告诉我,这个 sleep2 秒一直加到 10 秒,因为"有时候跑得起来,有时候跑不起来"。更糟的是,这个 compose.yml 文件已经膨胀到快 300 行,里面有五六个服务、一堆硬编码的环境变量,还有三个被注释掉的配置块------没人敢删。

这不是个别现象,是我见过的绝大多数 Go 项目的真实写照。

如果你也有类似的经历,下面这六个模式能帮你在不跟 Docker Compose 较劲的前提下,让本地开发环境稳定、测试可靠、构建高效。

模式一:健康检查比"等几秒"靠谱一万倍

depends_on 的语义是"等容器启动",不是"等服务就绪"。Postgres 容器起来了,但数据库引擎可能还在初始化,你的 Go 程序一启动就执行 db.Ping(),结果只能是崩溃。

正确的做法是告诉 Compose:"等到这个服务真正能干活了,再启动我的应用。"

yaml 复制代码
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypass
      POSTGRES_DB: mydb
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 10s

  app:
    build: .
    depends_on:
      postgres:
        condition: service_healthy

这样,应用会一直等到 Postgres 真正可以接受连接才启动。同样的方法适用于任何支持健康检查的服务:Redis、MySQL、Kafka------全都通用。

模式二:别把所有服务塞进一个文件

一个 compose.yml 管所有,意味着每次启动都要拉起整个架构。新人想改一行代码,结果启动了数据库、缓存、消息队列、监控面板、Worker 进程------这些东西他一个都不需要。

分层配置是更好的做法:

复制代码
compose.yaml          # 基础设施:Postgres、Redis
compose.dev.yaml      # 开发专用:挂载源码、热加载、调试端口
compose.test.yaml     # 测试专用:临时数据库、独立端口
compose.override.yaml # 个人配置,不提交
bash 复制代码
# docker-compose.dev.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/app
    ports:
      - "8080:8080"
      - "2345:2345"   # delve debugger
    environment:
      DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?sslmode=disable
      REDIS_URL: redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy

这样,新人跑 docker compose -f compose.yaml -f compose.dev.yaml up 就能开工,不会被多余的组件干扰。

模式三:多阶段构建让镜像从 1GB 缩到 20MB

很多项目的 Dockerfile 直接从 golang:1.23 开始,把整个编译工具链塞进最终镜像。结果就是 1.2GB 的镜像,CI 推一次等三分钟。

合理的 Go Dockerfile 应该分层:

dockerfile 复制代码
# ---- 依赖缓存 ----
FROM golang:1.23-alpine AS deps
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

# ---- 构建 ----
FROM deps AS builder
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server

# ---- 运行 ----
FROM scratch
COPY --from=builder /server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/server"]

这样做的效果:依赖缓存不会因为改了一行业务代码就失效;最终镜像只包含一个静态二进制文件,大多数 Go 服务的镜像能压缩到 20MB 以内。

模式四:用 .env 管配置,别往 YAML 里写密码

POSTGRES_PASSWORD: admin123 这种写法,迟早会出事。密码泄露只是时间问题。

Docker Compose 原生支持 .env 文件,你只需要把配置写进去:

复制代码
# .env(本地开发,不提交)
POSTGRES_USER=dev
POSTGRES_PASSWORD=devpass
POSTGRES_DB=devdb

提交一个 .env.example 作为模板就够了。新人复制、改名、填空,就能跑起来。

模式五:测试数据库跟开发数据库分开

集成测试直接跑在开发数据库上,是在玩火。一个清空表的测试用例就能让你一整个下午的调试数据灰飞烟灭。

测试环境应该独立,而且最好用内存存储:

yaml 复制代码
# compose.test.yaml
services:
  test_db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: testuser
      POSTGRES_PASSWORD: testpass
      POSTGRES_DB: testdb
    ports:
      - "5433:5432"
    tmpfs:
      - /var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U testuser"]
      interval: 2s
      timeout: 2s
      retries: 10

tmpfs 让数据跑在内存里,测试结束数据自动消失,速度比磁盘快一个量级。

总结

这些模式的核心思路只有一条:把不确定性变成确定性

健康检查让启动顺序可靠,分层配置让环境可维护,多阶段构建让镜像高效,环境变量让配置安全,隔离测试让数据安全,Makefile 让操作简单。

一个合格的 Go 项目目录,看起来应该是这样的:

复制代码
.
├── cmd/server/main.go
├── compose.yaml
├── compose.dev.yaml
├── compose.test.yaml
├── Dockerfile
├── .env.example
└── scripts/test.sh

这些东西不花哨,但它能保证:三个月后,你或者你的同事,不用再对着 200 行的 compose.yml 发愁。

相关推荐
Dr.kangder3 小时前
嵌入式总线设备解析——TTE总线应用与实践
开发语言·网络·算法·嵌入式·多任务·同步机制
-银雾鸢尾-3 小时前
C#中的多线程
开发语言·c#
2401_858286114 小时前
OS82.【Linux】设计线程池
java·linux·运维·服务器·开发语言·算法·线程池
数据知道4 小时前
IDOR 不安全的直接对象引用:API 越权实战检测
java·开发语言·网络·安全·网络安全
c238564 小时前
# Mini OJ — 项目详解文档
开发语言·数据库·c++
2401_894915535 小时前
Geo 优化源码部署常见报错排查:连接失败、地图加载、地域词失效解决方案
服务器·开发语言·python·安全·php
c238565 小时前
C/C++每日一练20
c语言·开发语言·c++
可爱系程序猿5 小时前
msvcp140_codecvt_ids.dll 缺失的排查记录:C++ 程序启动异常时如何核对运行库版本
开发语言·c++·程序人生·电脑