docker-dockerfile

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Dockerfile — 完整编写指南

Dockerfile — 完整编写指南

Expert reference for writing production-grade Dockerfiles. Every instruction, every pattern, every optimization.
编写生产级Dockerfile的专业参考资料,涵盖所有指令、模式与优化方案。

When to Use

使用场景

ALWAYS use this skill when the user mentions:
  • "Dockerfile", "怎么写 Dockerfile", "Dockerfile 指令"
  • "多阶段构建", "multi-stage build"
  • "Dockerfile 层优化", "layer caching"
  • "Dockerfile 模板", language-specific: "Go Dockerfile", "Java Dockerfile", "Python Dockerfile"
  • Need to create or optimize a Dockerfile
  • "Dockerfile best practices"
当用户提及以下内容时,务必使用本技能:
  • "Dockerfile"、"怎么写 Dockerfile"、"Dockerfile 指令"
  • "多阶段构建"、"multi-stage build"
  • "Dockerfile 层优化"、"layer caching"
  • "Dockerfile 模板"、特定语言相关:"Go Dockerfile"、"Java Dockerfile"、"Python Dockerfile"
  • 需要创建或优化Dockerfile
  • "Dockerfile best practices"

Instruction Reference

指令参考

FROM — Base Image

FROM — 基础镜像

dockerfile
FROM <image>[:<tag>] [AS <stage-name>]
FROM alpine:3.20                           # Tag
FROM alpine:3.20@sha256:abc123...def456     # Digest (production!)
FROM golang:1.22-alpine AS builder          # Named stage
FROM scratch                                 # Empty image (for static binaries)
Best PracticeWhy
Pin digest for productionTags are mutable; digest is immutable
Use Alpine/slim variantsSmaller attack surface, smaller image
FROM scratch
for Go/Rust
Static binaries need nothing else
dockerfile
FROM <image>[:<tag>] [AS <stage-name>]
FROM alpine:3.20                           # 指定标签
FROM alpine:3.20@sha256:abc123...def456     # 指定摘要(生产环境推荐!)
FROM golang:1.22-alpine AS builder          # 命名构建阶段
FROM scratch                                 # 空镜像(适用于静态二进制文件)
最佳实践原因
生产环境固定镜像摘要标签可修改,摘要不可变
使用Alpine/slim变体镜像更小的攻击面,镜像体积更小
Go/Rust使用
FROM scratch
静态二进制文件无需其他依赖

RUN — Execute Commands

RUN — 执行命令

dockerfile
undefined
dockerfile
undefined

✅ Chain commands, clean in same layer

✅ 链式执行命令,在同一层清理

RUN apk add --no-cache curl &&
curl -fsSL https://example.com/script.sh -o /usr/local/bin/script &&
chmod +x /usr/local/bin/script
RUN apk add --no-cache curl &&
curl -fsSL https://example.com/script.sh -o /usr/local/bin/script &&
chmod +x /usr/local/bin/script

❌ Each RUN = new layer (bloat)

❌ 每个RUN创建新层(导致镜像臃肿)

RUN apk add curl RUN curl ... -o /usr/local/bin/script RUN chmod +x /usr/local/bin/script
RUN apk add curl RUN curl ... -o /usr/local/bin/script RUN chmod +x /usr/local/bin/script

Multi-line readability

多行写法提升可读性

RUN set -eux;
apk add --no-cache
curl
ca-certificates
tzdata;
curl -fsSL ... | tar xz -C /usr/local
undefined
RUN set -eux;
apk add --no-cache
curl
ca-certificates
tzdata;
curl -fsSL ... | tar xz -C /usr/local
undefined

COPY — Copy Files

COPY — 复制文件

dockerfile
COPY [--chown=<user>:<group>] <src>... <dest>
COPY . /app
COPY --chown=app:app ./binary /usr/local/bin/
COPY --from=builder /app/build /app       # From another stage (multi-stage)
dockerfile
undefined
dockerfile
COPY [--chown=<user>:<group>] <src>... <dest>
COPY . /app
COPY --chown=app:app ./binary /usr/local/bin/
COPY --from=builder /app/build /app       # 从其他构建阶段复制(多阶段构建)
dockerfile
undefined

✅ Layer-friendly: copy deps first, then source

✅ 友好利用镜像层:先复制依赖文件,再复制源码

COPY go.mod go.sum ./ RUN go mod download COPY . .
COPY go.mod go.sum ./ RUN go mod download COPY . .

❌ Source change invalidates dependency cache

❌ 源码变更会使依赖缓存失效

COPY . . RUN go mod download
undefined
COPY . . RUN go mod download
undefined

ADD — Copy + Auto-extract

ADD — 复制+自动解压

dockerfile
undefined
dockerfile
undefined

ADD auto-extracts tar archives

ADD会自动解压tar归档文件

ADD archive.tar.gz /app/
ADD archive.tar.gz /app/

Prefer COPY unless you need tar extraction

除非需要自动解压,否则优先使用COPY

COPY archive.tar.gz /app/ RUN tar xzf /app/archive.tar.gz -C /app
undefined
COPY archive.tar.gz /app/ RUN tar xzf /app/archive.tar.gz -C /app
undefined

WORKDIR — Set Working Directory

WORKDIR — 设置工作目录

dockerfile
WORKDIR /app
dockerfile
WORKDIR /app

All subsequent RUN/COPY/CMD use /app as base

后续所有RUN/COPY/CMD命令均以/app为基础目录

Prefer over:

优于以下写法:

RUN cd /app && npm install # ❌ cd doesn't persist
undefined
RUN cd /app && npm install # ❌ cd命令不会持久生效
undefined

ENV & ARG

ENV & ARG

dockerfile
undefined
dockerfile
undefined

ARG: build-time only (not in final image)

ARG:仅构建阶段有效(不会保留在最终镜像中)

ARG VERSION=1.0.0 FROM myapp:${VERSION}
ARG VERSION=1.0.0 FROM myapp:${VERSION}

ENV: runtime (persists in image)

ENV:运行时有效(会保留在镜像中)

ENV NODE_ENV=production
PORT=8080
ENV NODE_ENV=production
PORT=8080

Combine: pass ARG to ENV

组合使用:将ARG传递给ENV

ARG APP_VERSION ENV APP_VERSION=${APP_VERSION}
undefined
ARG APP_VERSION ENV APP_VERSION=${APP_VERSION}
undefined

EXPOSE — Document Ports

EXPOSE — 声明端口

dockerfile
EXPOSE 8080
EXPOSE 8080/tcp     # Protocol-specific
EXPOSE 8080/udp
dockerfile
EXPOSE 8080
EXPOSE 8080/tcp     # 指定协议
EXPOSE 8080/udp

Note: EXPOSE does NOT publish ports. Use -p at runtime:

注意:EXPOSE不会自动发布端口,需在运行时使用-p参数:

docker run -p 8080:8080 myapp

docker run -p 8080:8080 myapp

undefined
undefined

CMD vs ENTRYPOINT

CMD vs ENTRYPOINT

dockerfile
undefined
dockerfile
undefined

CMD: default command (overridable)

CMD:默认命令(可被覆盖)

CMD ["nginx", "-g", "daemon off;"]
CMD ["nginx", "-g", "daemon off;"]

docker run myimage echo hello → overrides CMD

docker run myimage echo hello → 会覆盖CMD

ENTRYPOINT: fixed entry (not overridable)

ENTRYPOINT:固定入口(不可被直接覆盖)

ENTRYPOINT ["docker-entrypoint.sh"] CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["docker-entrypoint.sh"] CMD ["nginx", "-g", "daemon off;"]

docker run myimage → runs: docker-entrypoint.sh nginx -g 'daemon off;'

docker run myimage → 执行:docker-entrypoint.sh nginx -g 'daemon off;'

docker run myimage echo hello → runs: docker-entrypoint.sh echo hello

docker run myimage echo hello → 执行:docker-entrypoint.sh echo hello

Common pattern: script + default args

常见模式:脚本+默认参数

ENTRYPOINT ["/entrypoint.sh"] CMD ["start"]
undefined
ENTRYPOINT ["/entrypoint.sh"] CMD ["start"]
undefined

USER — Switch to Non-Root

USER — 切换至非root用户

dockerfile
undefined
dockerfile
undefined

Create user and group

创建用户和用户组

RUN addgroup --system app && adduser --system --ingroup app app USER app
undefined
RUN addgroup --system app && adduser --system --ingroup app app USER app
undefined

HEALTHCHECK

HEALTHCHECK

dockerfile
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget -qO- http://localhost:8080/health || exit 1
dockerfile
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget -qO- http://localhost:8080/health || exit 1

Dockerfile without shell (scratch):

无shell环境的Dockerfile(如scratch):

HEALTHCHECK --interval=30s CMD /app/healthcheck || exit 1
undefined
HEALTHCHECK --interval=30s CMD /app/healthcheck || exit 1
undefined

SHELL — Change Default Shell

SHELL — 修改默认Shell

dockerfile
SHELL ["/bin/bash", "-euxo", "pipefail", "-c"]
RUN echo "Now using bash with strict mode"
dockerfile
SHELL ["/bin/bash", "-euxo", "pipefail", "-c"]
RUN echo "现在使用开启严格模式的bash"

Multi-Stage Build Patterns

多阶段构建模式

Pattern 1: Build Binary + Scratch (Go/Rust)

模式1:构建二进制文件 + Scratch(Go/Rust)

dockerfile
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="-w -s" -o server .

FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/server /server
USER 1000:1000
CMD ["/server"]
dockerfile
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="-w -s" -o server .

FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/server /server
USER 1000:1000
CMD ["/server"]

Pattern 2: Build + Minimal Runtime (Java)

模式2:构建 + 轻量运行时(Java)

dockerfile
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

FROM eclipse-temurin:21-jre-alpine
RUN addgroup --system app && adduser -S -G app app
COPY --from=builder /app/target/*.jar /app.jar
USER app
CMD ["java", "-jar", "/app.jar"]
dockerfile
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

FROM eclipse-temurin:21-jre-alpine
RUN addgroup --system app && adduser -S -G app app
COPY --from=builder /app/target/*.jar /app.jar
USER app
CMD ["java", "-jar", "/app.jar"]

Pattern 3: Layer-Optimized (Spring Boot)

模式3:镜像层优化(Spring Boot)

dockerfile
FROM eclipse-temurin:21-jre-alpine AS builder
WORKDIR /app
COPY build/libs/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM eclipse-temurin:21-jre-alpine
RUN addgroup --system app && adduser -S -G app app
dockerfile
FROM eclipse-temurin:21-jre-alpine AS builder
WORKDIR /app
COPY build/libs/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM eclipse-temurin:21-jre-alpine
RUN addgroup --system app && adduser -S -G app app

Layers in dependency order (max cache)

按依赖顺序分层(最大化缓存利用率)

COPY --from=builder /app/dependencies/ ./ COPY --from=builder /app/spring-boot-loader/ ./ COPY --from=builder /app/snapshot-dependencies/ ./ COPY --from=builder /app/application/ ./ USER app ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
undefined
COPY --from=builder /app/dependencies/ ./ COPY --from=builder /app/spring-boot-loader/ ./ COPY --from=builder /app/snapshot-dependencies/ ./ COPY --from=builder /app/application/ ./ USER app ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
undefined

Pattern 4: Build + Alpline (Node.js)

模式4:构建 + Alpine(Node.js)

dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
RUN addgroup --system app && adduser -S -G app app
COPY --from=builder /app/dist /app
COPY --from=builder /app/node_modules /app/node_modules
USER app
CMD ["node", "/app/index.js"]
dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
RUN addgroup --system app && adduser -S -G app app
COPY --from=builder /app/dist /app
COPY --from=builder /app/node_modules /app/node_modules
USER app
CMD ["node", "/app/index.js"]

Pattern 5: Python Dependencies + Slim Runtime

模式5:Python依赖 + 轻量运行时

dockerfile
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.12-slim
RUN groupadd --system app && useradd --system -g app app
COPY --from=builder /root/.local /home/app/.local
COPY . /app
ENV PATH=/home/app/.local/bin:$PATH
USER app
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0"]
dockerfile
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.12-slim
RUN groupadd --system app && useradd --system -g app app
COPY --from=builder /root/.local /home/app/.local
COPY . /app
ENV PATH=/home/app/.local/bin:$PATH
USER app
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0"]

Layer Caching Strategy

镜像层缓存策略

Docker builds layers from top to bottom.
A changed layer invalidates ALL layers below it.

✅ CORRECT order:
  FROM base                  ← rarely changes
  RUN install-system-deps    ← changes with system updates
  COPY go.mod go.sum ./      ← changes with dependency changes
  RUN go mod download        ← changes with dependency changes
  COPY . .                   ← changes every commit ← MUST BE LAST

❌ WRONG order (slow builds):
  COPY . .                   ← changes every commit
  RUN go mod download        ← reruns every time!
Docker从上到下构建镜像层。
某一层变更会使其下方所有层的缓存失效。

✅ 正确顺序:
  FROM base                  ← 极少变更
  RUN install-system-deps    ← 随系统更新变更
  COPY go.mod go.sum ./      ← 随依赖变更变更
  RUN go mod download        ← 随依赖变更变更
  COPY . .                   ← 每次提交都会变更 ← 必须放在最后

❌ 错误顺序(构建缓慢):
  COPY . .                   ← 每次提交都会变更
  RUN go mod download        ← 每次都会重新执行!

.dockerignore

.dockerignore

dockerfile
undefined
dockerfile
undefined

.dockerignore

.dockerignore

.git .gitignore .md .env .env. Dockerfile docker-compose*.yml node_modules pycache *.pyc .git .idea .vscode *.log tmp/
undefined
.git .gitignore .md .env .env. Dockerfile docker-compose*.yml node_modules pycache *.pyc .git .idea .vscode *.log tmp/
undefined

Workflow — 推荐编写流程

工作流程 — 推荐编写流程

Step 1: 确定语言和运行时: Go/Java/Node.js/Python → 选择基础镜像 Step 2: 选择构建模式: 单阶段/多阶段/Spring Boot 分层 → 从 templates 选模板 Step 3: 编写 Dockerfile: 先 COPY 依赖 → RUN install → COPY 源码 → CMD Step 4: 验证:
docker build -t app .
+
docker run
+ dive 分析镜像大小 Step 5: 生产加固: USER 非 root、HEALTHCHECK、固定 digest、Security 检查
Step 1: 确定语言和运行时: Go/Java/Node.js/Python → 选择基础镜像 Step 2: 选择构建模式: 单阶段/多阶段/Spring Boot分层 → 从模板中选择 Step 3: 编写Dockerfile: 先COPY依赖文件 → RUN安装依赖 → COPY源码 → CMD Step 4: 验证:
docker build -t app .
+
docker run
+ dive分析镜像大小 Step 5: 生产加固: 使用非root用户USER、添加HEALTHCHECK、固定镜像摘要、安全检查

Gotchas — Common Pitfalls

常见陷阱

  • COPY . .
    before
    RUN install
    : Every code change invalidates the dependency layer — rebuilds from scratch. → Recovery: Always copy deps first:
    COPY package.json . → RUN install → COPY src/ .
    .
  • Root user by default: Always
    USER <non-root>
    at the end of Dockerfile. Escaping the container as root = host root. → Recovery: Add
    RUN addgroup -S app && adduser -S app -G app
    +
    USER app
    ; verify with
    docker exec myapp whoami
    .
  • ENV SECRET=value
    in Dockerfile
    : Baked into image layers forever. → Recovery: Use BuildKit
    --mount=type=secret
    or runtime injection
    docker run -e SECRET=$VAL
    .
  • No
    .dockerignore
    : Sends entire project to build context — slow and leaks sensitive files. → Recovery: Create
    .dockerignore
    with at minimum
    .git node_modules .env
    ; verify with
    docker build --no-cache . 2>&1 | head -1
    .
  • RUN apt update && apt install
    without cleanup
    : Leaves package lists in layer. → Recovery: Chain with
    && rm -rf /var/lib/apt/lists/*
    ; for apk:
    --no-cache
    flag.
  • Heavy base image:
    ubuntu:22.04
    (77 MB) vs
    alpine:3.20
    (7 MB). → Recovery: Prefer alpine; if glibc needed, use
    debian:bookworm-slim
    ; check size with
    docker images
    .
  • COPY . .
    在
    RUN install
    之前
    : 每次代码变更都会使依赖层缓存失效 — 导致从头构建。→ 解决方法: 始终先复制依赖文件:
    COPY package.json . → RUN install → COPY src/ .
    。
  • 默认使用root用户: 务必在Dockerfile末尾添加
    USER <non-root>
    。以root身份逃逸容器会获得主机root权限。→ 解决方法: 添加
    RUN addgroup -S app && adduser -S app -G app
    +
    USER app
    ;使用
    docker exec myapp whoami
    验证。
  • 在Dockerfile中使用
    ENV SECRET=value
    : 会永久嵌入镜像层中。→ 解决方法: 使用BuildKit的
    --mount=type=secret
    或运行时注入
    docker run -e SECRET=$VAL
    。
  • 未配置
    .dockerignore
    : 会将整个项目发送到构建上下文 — 构建缓慢且可能泄露敏感文件。→ 解决方法: 创建
    .dockerignore
    ,至少包含
    .git node_modules .env
    ;使用
    docker build --no-cache . 2>&1 | head -1
    验证。
  • RUN apt update && apt install
    未清理
    : 会在镜像层中留下包列表。→ 解决方法: 链式添加
    && rm -rf /var/lib/apt/lists/*
    ;对于apk,使用
    --no-cache
    参数。
  • 使用重型基础镜像:
    ubuntu:22.04
    (77 MB)对比
    alpine:3.20
    (7 MB)。→ 解决方法: 优先选择alpine;若需要glibc,使用
    debian:bookworm-slim
    ;使用
    docker images
    查看镜像大小。

Boundary — 能力边界(适用与不适用场景)

能力边界(适用与不适用场景)

分类场景说明
✅ 能做编写生产级 Dockerfile14 条指令完整参考 + 最佳实践
✅ 能做多阶段构建(Go/Java/Node/Python)5 种语言专属模板
✅ 能做层缓存优化COPY 依赖优先 + RUN 合并 + BuildKit cache mount
✅ 能做镜像瘦身5 步法路线图:多阶段→Alpine→distroless→清理→dive
⚠️ 需条件私有依赖安装需配合 BuildKit --secret 或 SSH forwarding
⚠️ 需条件CMD vs ENTRYPOINT 选择见指令参考中的决策树(工具用 ENTRYPOINT,服务用 CMD)
❌ 超范围docker build 命令执行使用
docker-build
❌ 超范围多平台构建(arm64/amd64)使用
docker-buildx
❌ 超范围容器编排(多容器)使用
docker-compose
分类场景说明
✅ 能做编写生产级Dockerfile14条指令完整参考 + 最佳实践
✅ 能做多阶段构建(Go/Java/Node/Python)5种语言专属模板
✅ 能做镜像层缓存优化COPY依赖优先 + RUN命令合并 + BuildKit缓存挂载
✅ 能做镜像瘦身5步法路线图:多阶段→Alpine→distroless→清理→dive
⚠️ 需条件私有依赖安装需配合BuildKit --secret或SSH转发
⚠️ 需条件CMD与ENTRYPOINT选择参考指令部分的决策树(工具类用ENTRYPOINT,服务类用CMD)
❌ 超范围docker build命令执行使用
docker-build
技能
❌ 超范围多平台构建(arm64/amd64)使用
docker-buildx
技能
❌ 超范围容器编排(多容器)使用
docker-compose
技能

When NOT to Use This Skill

不适用本技能的场景

❌ Skip✅ Use Instead
Building images (
docker build ...
)
docker-build
Multi-platform builds
docker-buildx
Compose file authoring
docker-compose
Docker basics
docker-basics
Running containers
docker-run
❌ 跳过本技能✅ 使用以下技能
构建镜像(
docker build ...
)
docker-build
多平台构建
docker-buildx
Compose文件编写
docker-compose
Docker基础
docker-basics
运行容器
docker-run

Security & Stability

安全与稳定性

  • All Dockerfile templates are educational. Review and harden before production use.
  • Never embed secrets in Dockerfile. Use BuildKit
    --mount=type=secret
    or runtime injection.
  • Always
    USER <non-root>
    for production. Use
    COPY --chown
    when copying files for that user.
  • Pin base image digests for production reproducibility.
  • 所有Dockerfile模板仅作学习参考,生产使用前需审核并加固。
  • 切勿在Dockerfile中嵌入密钥。使用BuildKit的
    --mount=type=secret
    或运行时注入。
  • 生产环境务必使用
    USER <non-root>
    。为该用户复制文件时使用
    COPY --chown
    。
  • 生产环境固定基础镜像摘要以保证可复现性。

📚 官方文档参考

📚 官方文档参考

🧭 Docker Skills Journey

🧭 Docker技能学习路径

📍 You are here:
docker-dockerfile
— Dockerfile 编写
basics → dockerfile → build → buildx → run → compose → ...
→ Next:
docker-build
— Build images with
docker build
📍 当前位置:
docker-dockerfile
— Dockerfile 编写
basics → dockerfile → build → buildx → run → compose → ...
→ 下一步:
docker-build
— 使用
docker build
构建镜像