This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
2026-05-18 00:17:59 +08:00

151 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [docker, container, dockerfile, buildkit, image-optimization]
create time: 2026-05-18 00:45
---
# Dockerfile 最佳实践 — 教程
## 概述
Dockerfile 是微服务交付的标准起点。本文档从多阶段构建出发,详解层缓存技巧、BuildKit 高级特性和常见问题排查。更多话题:多架构构建见 [[../01-容器化/02-多架构构建]],镜像安全见 [[../01-容器化/03-镜像安全]]。
## Go 多阶段构建(推荐)
```dockerfile
# ========== 阶段 1: 构建 ==========
FROM golang:1.22-alpine AS builder
RUN apk --no-cache add git ca-certificates
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ARG LDFLAGS="-s -w -extldflags '-static'"
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "$LDFLAGS" -o server .
# ========== 阶段 2: 运行时 ==========
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /app
COPY --from=builder /app/server .
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:8080/healthz || exit 1
CMD ["./server"]
```
> [!question] 为什么要用多阶段构建?
>
> 单阶段构建中,编译工具和源码都在最终镜像里——一个 Go 项目的镜像轻松超过 800MB。**多阶段构建**把构建和运行拆成两个独立的镜像层,第二阶段只 COPY 二进制文件,最终镜像缩小到十几 MB。
## 关键优化点速查
| 优化项 | 方法 | 效果 |
|--------|------|------|
| **多阶段构建** | 编译和运行分离 | 镜像从 800MB → 15MB |
| **alpine 基础镜像** | 替代 debian/ubuntu | 减小体积 |
| **非 root 运行** | `USER appuser` | 安全合规 |
| **静态链接** | `CGO_ENABLED=0` | 不依赖系统库 |
| **layer cache** | `go.mod` 先 COPY | CI 加速构建 |
| **健康检查** | HEALTHCHECK 指令 | K8s 原生支持 |
## `.dockerignore` — 别忽略的文件
```
.git
.gitignore
*.md
vendor/
tests/
*.log
.DS_Store
.idea/
.vscode/
```
> [!tip] 为什么 .dockerignore 很重要?
>
> 如果不排除 `.git` 目录,整个版本历史都会被打包进镜像(增加数百 MB)。如果排除不当,可能遗漏必要的配置文件。建议每个项目根目录都包含此文件。
## 层缓存技巧
### ❌ 差的缓存策略
```dockerfile
# 差:任何文件改动都会让后续所有 layer 失效
COPY . .
RUN go build
```
### ✅ 好的缓存策略
```dockerfile
# 好:只有 go.mod/go.sum 变化时才重新下载依赖
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build
```
核心原则:**经常变动的内容靠近 COPY,少变的放前面**。这样当代码频繁变更时,依赖下载和构建步骤仍能命中缓存。
## BuildKit 特性
BuildKit 是新一代构建引擎,默认在 Docker 18.09+ 和所有 Docker Desktop 中启用:
```bash
# 启用 BuildKit 加速
export DOCKER_BUILDKIT=1
# 利用远程缓存 (需配合 registry)
docker build --cache-from=harbor.example.com/app/cache:latest .
# SSH agent forwarding(拉取私有依赖用)
docker build --ssh default .
# 秘密变量注入(不进 Docker history)
docker build --secret id=token,env=GITHUB_TOKEN .
```
```dockerfile
# syntax=docker/dockerfile:1
# ^^^ 必须声明语法版本以启用 BuildKit
FROM golang:1.22-alpine AS builder
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go mod download && go build -o server .
```
> [!info] `--mount=type=cache` vs RUN 缓存
>
> Docker 默认的层缓存会在源文件变化时跳过整层;而 `type=cache` 是增量更新本地缓存目录,不受构建上下文变化影响,适合依赖下载和编译缓存。
## 常见问题排查
| 症状 | 原因 | 解决 |
|------|------|------|
| `exec: "app": not found` | 多阶段 COPY 路径错误 | 检查绝对路径和文件名拼写 |
| `standard_init_linux.go: exec user process caused: permission denied` | 没有执行权限或缺少换行符 | `chmod +x`, 确保 Linux 换行 |
| `service unavailable` (K8s) | HEALTHCHECK 未就绪 | 增大 `--start-period` |
| 镜像体积异常大 | `.git` 未排除或多层 RUN 未清理 | 检查 `.dockerignore`,合并 RUN 并 `rm -rf` |
| `cannot execute: executable file not found` | 架构不匹配(arm64 → amd64) | 确认 `TARGETARCH` 或手动指定 `--platform` |
## 关联笔记
- [[../01-容器化/02-多架构构建]] — 一次构建全平台镜像
- [[../01-容器化/03-镜像安全]] — Trivy 扫描与 distroless 镜像
- [[../hhs/MS/05-部署运维/02-Kubernetes]] — K8s 以 Pod 为部署单元,镜像来自 Docker