Docker 与容器化
问题
Go 项目如何进行 Docker 容器化?为什么 Go 的 Docker 镜像特别小?
答案
多阶段构建(标准方案)
# 阶段 1:编译
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server
# 阶段 2:运行(最小镜像)
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]
| 镜像 | 大小 |
|---|---|
| golang:1.22 | ~800MB |
| alpine + 二进制 | ~15MB |
| scratch + 二进制 | ~8MB |
| distroless | ~10MB |
CGO_ENABLED=0
设为 0 产生纯静态二进制,不依赖 libc,可以在 scratch 空镜像上运行。如果用了 CGO(如 SQLite),需要用 alpine 镜像。
编译优化
# -s 去掉符号表,-w 去掉调试信息 → 减小约 30% 体积
go build -ldflags="-s -w" -o server
# 注入版本信息
go build -ldflags="-s -w -X main.version=1.0.0 -X main.buildTime=$(date -u +%Y%m%d%H%M%S)"
Docker Compose 开发环境
# docker-compose.yml
services:
app:
build: .
ports: ["8080:8080"]
depends_on: [mysql, redis]
environment:
- DB_HOST=mysql
- REDIS_ADDR=redis:6379
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: app
ports: ["3306:3306"]
redis:
image: redis:7-alpine
ports: ["6379:6379"]
常见面试问题
Q1: 为什么 Go 的 Docker 镜像可以那么小?
答案:Go 编译为单个静态二进制,不需要 JVM、Node.js 等运行时。设 CGO_ENABLED=0 后甚至不依赖 libc,可以直接放在空镜像(scratch)上运行。
Q2: scratch vs alpine vs distroless?
答案:
| 基础镜像 | 大小 | 特点 |
|---|---|---|
scratch | 0 | 无壳、无工具,最小 |
alpine | ~5MB | 有 Shell,可 debug |
distroless | ~2MB | Google 出品,无 Shell 但有 CA 证书 |
生产推荐 distroless 或 alpine,方便排查问题。纯安全考虑用 scratch。