强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Docker 完全指南 / 06 - Dockerfile 详解

06 - Dockerfile 详解

全面掌握 Dockerfile 指令、多阶段构建、缓存优化与镜像构建最佳实践。


6.1 Dockerfile 基础

Dockerfile 是一个文本文件,包含一系列指令,用于自动化构建 Docker 镜像。

基本结构

# 注释
INSTRUCTION arguments

最小 Dockerfile 示例

FROM alpine:3.19
RUN echo "Hello, Docker!" > /hello.txt
CMD ["cat", "/hello.txt"]
# 构建镜像
docker build -t my-hello .

# 运行
docker run --rm my-hello
# 输出: Hello, Docker!

6.2 核心指令详解

6.2.1 FROM — 基础镜像

# 指定基础镜像(必须是第一条非注释指令)
FROM ubuntu:22.04

# 使用最小基础镜像
FROM alpine:3.19

# 使用 scratch(空镜像,用于静态二进制)
FROM scratch

# 多架构构建中使用 $TARGETPLATFORM
FROM --platform=$BUILDPLATFORM node:20-alpine
基础镜像大小包管理器适用场景
alpine:3.19~7MBapk最小镜像,Go/Rust 静态二进制
debian:bookworm-slim~75MBapt需要 glibc 的应用
ubuntu:22.04~77MBapt通用场景
python:3.11-slim~125MBaptPython 应用
node:20-alpine~130MBapkNode.js 应用
scratch0MB静态编译的 Go 二进制

6.2.2 RUN — 执行命令

# Shell 格式(通过 /bin/sh -c 执行)
RUN apt-get update && apt-get install -y curl

# Exec 格式(直接执行,不经过 shell)
RUN ["apt-get", "install", "-y", "curl"]

# 多条命令合并为一层(减少镜像层数)
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        wget \
        ca-certificates && \
    rm -rf /var/lib/apt/lists/*

最佳实践: 每个 RUN 指令创建一个镜像层。将相关命令合并到一个 RUN 中,并在最后清理缓存。

6.2.3 COPY 与 ADD

# COPY: 复制本地文件到镜像
COPY requirements.txt /app/
COPY src/ /app/src/
COPY --chown=appuser:appgroup config.yml /app/

# ADD: 类似 COPY,但有额外功能
ADD app.tar.gz /app/          # 自动解压 tar 文件
ADD https://example.com/file /app/  # 支持 URL 下载(不推荐)
特性COPYADD
复制本地文件
自动解压 tar
支持 URL✅(不推荐)
推荐使用✅ 首选仅在需要自动解压时

建议: 优先使用 COPY,语义更清晰。仅在需要自动解压 .tar.gz 时使用 ADD

6.2.4 CMD 与 ENTRYPOINT

# CMD: 容器启动时的默认命令(可被 docker run 参数覆盖)
CMD ["nginx", "-g", "daemon off;"]

# ENTRYPOINT: 容器的入口点(不会被轻易覆盖)
ENTRYPOINT ["python", "app.py"]

# 组合使用: ENTRYPOINT + CMD
ENTRYPOINT ["python"]
CMD ["app.py"]
# docker run my-app              → python app.py
# docker run my-app test.py      → python test.py
指令可被覆盖用途
CMDdocker run 参数覆盖默认命令/参数
ENTRYPOINT--entrypoint 覆盖固定入口程序

6.2.5 ENV — 环境变量

# 设置环境变量(构建时和运行时都生效)
ENV NODE_ENV=production
ENV APP_HOME=/app \
    APP_USER=appuser

# 构建时使用,运行时不生效
ARG VERSION=1.0
ARG BUILD_DATE
指令构建时运行时覆盖方式
ENVdocker run -e
ARGdocker build --build-arg

6.2.6 EXPOSE — 声明端口

# 声明容器监听的端口(仅文档作用,不实际发布端口)
EXPOSE 80
EXPOSE 443
EXPOSE 8080/tcp
EXPOSE 9090/udp

注意: EXPOSE 不会自动发布端口。运行时仍需 -p 选项映射端口。

6.2.7 WORKDIR — 工作目录

# 设置工作目录(不存在会自动创建)
WORKDIR /app
# 后续的 RUN, CMD, COPY 等指令都基于此目录

避免: 不要使用 RUN cd /app,因为每条 RUN 是独立层,cd 不会持续。

6.2.8 USER — 指定用户

# 创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# 切换到非 root 用户
USER appuser

# 之后的命令都以 appuser 身份运行

6.2.9 VOLUME — 声明挂载点

# 声明数据卷挂载点
VOLUME ["/data", "/var/log"]

注意: VOLUME 指令后的层对挂载目录的修改不会持久化。尽量在 Dockerfile 最后使用。

6.2.10 ARG — 构建参数

ARG NODE_VERSION=20
ARG APP_VERSION

FROM node:${NODE_VERSION}-alpine

# 注意: ARG 在 FROM 之后需要重新声明
ARG APP_VERSION
RUN echo "Building version $APP_VERSION"

# 使用 ARG 设置默认 ENV
ARG DEFAULT_PORT=3000
ENV PORT=$DEFAULT_PORT
# 传递构建参数
docker build --build-arg NODE_VERSION=18 --build-arg APP_VERSION=1.0.0 -t my-app .

6.3 完整 Dockerfile 示例

Python Flask 应用

# ---- 基础镜像 ----
FROM python:3.11-slim AS base

# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1

WORKDIR /app

# ---- 依赖安装(利用缓存) ----
FROM base AS deps
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# ---- 生产镜像 ----
FROM base AS production

# 从 deps 阶段复制已安装的依赖
COPY --from=deps /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=deps /usr/local/bin /usr/local/bin

# 创建非 root 用户
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser

# 复制应用代码
COPY --chown=appuser:appgroup . .

# 切换用户
USER appuser

EXPOSE 5000

# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')"

CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]

Node.js 应用

FROM node:20-alpine

# 设置工作目录
WORKDIR /app

# 复制依赖文件(利用缓存层)
COPY package.json package-lock.json ./

# 安装依赖
RUN npm ci --only=production

# 复制应用代码
COPY . .

# 创建非 root 用户
RUN chown -R node:node /app
USER node

EXPOSE 3000

CMD ["node", "server.js"]

Go 应用(静态二进制)

# ---- 构建阶段 ----
FROM golang:1.22-alpine AS builder

WORKDIR /build

# 下载依赖(缓存层)
COPY go.mod go.sum ./
RUN go mod download

# 编译应用
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app ./cmd/server

# ---- 运行阶段 ----
FROM scratch

COPY --from=builder /app /app
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080

ENTRYPOINT ["/app"]

6.4 多阶段构建(Multi-Stage Build)

多阶段构建是减小镜像大小的关键技术。

基本语法

# 阶段 1: 构建
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o server .

# 阶段 2: 运行
FROM alpine:3.19
COPY --from=builder /app/server /usr/local/bin/
CMD ["server"]

多阶段构建的优势

对比单阶段构建多阶段构建
镜像大小包含编译器和源码 (1GB+)仅包含运行时 (10-50MB)
安全性包含构建工具,攻击面大最小化攻击面
复杂度简单稍复杂
推荐场景简单脚本生产环境

命名阶段与选择性复制

# 阶段 1: 依赖
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# 阶段 2: 构建
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# 阶段 3: 生产
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package*.json ./

USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]

# 阶段 4: 测试(仅在构建测试镜像时使用)
FROM node:20-alpine AS test
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
CMD ["npm", "test"]
# 构建生产镜像(默认最后一个阶段)
docker build -t my-app:prod .

# 构建测试镜像
docker build --target test -t my-app:test .

# 构建到特定阶段
docker build --target build -t my-app:build .

从外部镜像复制

# 从其他镜像复制文件(无需安装)
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=busybox:latest /bin/sh /bin/sh

6.5 缓存优化策略

Docker 构建缓存原理

构建过程:
  指令 1: FROM ubuntu:22.04       → 使用缓存 ✅
  指令 2: RUN apt-get update      → 检查缓存...
    └─ 缓存命中 → 跳过执行 ✅
    └─ 缓存未命中 → 执行命令 ❌,此后的所有层都重新构建

缓存优化规则

# ❌ 错误: COPY . . 会破坏缓存
COPY . .
RUN pip install -r requirements.txt

# ✅ 正确: 先复制依赖文件,再复制代码
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

优化排序原则

# 按变化频率从低到高排序
FROM python:3.11-slim

# 1. 系统依赖(很少变化)
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq-dev && \
    rm -rf /var/lib/apt/lists/*

# 2. 应用依赖(偶尔变化)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 3. 应用代码(频繁变化)
COPY . .

CMD ["python", "app.py"]

BuildKit 缓存挂载

# 使用 --mount=type=cache 缓存包管理器目录
RUN --mount=type=cache,target=/var/cache/apt \
    apt-get update && apt-get install -y libpq-dev

# 缓存 pip 下载目录
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

# 缓存 npm 目录
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# 缓存 Go 模块
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build -o /app .

6.6 .dockerignore 文件

# .dockerignore 示例

# Git
.git
.gitignore

# IDE
.vscode
.idea
*.swp
*.swo

# 依赖目录
node_modules
__pycache__
*.pyc
.venv

# 构建产物
dist
build
target

# 文档
README.md
docs
*.md

# Docker 相关
Dockerfile*
docker-compose*.yml
.dockerignore

# 系统文件
.DS_Store
Thumbs.db

# 测试
tests
*.test
coverage

重要: .dockerignore 必须放在构建上下文的根目录(与 Dockerfile 同级)。


6.7 健康检查(HEALTHCHECK)

# 基本健康检查
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# 使用 wget(Alpine 没有 curl)
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD wget -qO- http://localhost:8080/health || exit 1

# 禁用基础镜像的健康检查
HEALTHCHECK NONE
参数默认值说明
--interval30s检查间隔
--timeout30s超时时间
--start-period0s启动等待时间
--retries3失败重试次数
# 查看健康状态
docker inspect --format '{{.State.Health.Status}}' my-container

# 查看健康检查日志
docker inspect --format '{{json .State.Health.Log}}' my-container | jq .

6.8 入口点脚本(Entrypoint Script)

典型入口点脚本

# Dockerfile
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
#!/bin/sh
# docker-entrypoint.sh
set -e

# 等待数据库就绪
if [ "$DB_HOST" ]; then
    echo "Waiting for database at $DB_HOST..."
    while ! nc -z "$DB_HOST" "${DB_PORT:-5432}"; do
        sleep 1
    done
    echo "Database is ready!"
fi

# 执行数据库迁移
if [ "$RUN_MIGRATIONS" = "true" ]; then
    echo "Running migrations..."
    python manage.py migrate
fi

# 执行用户传入的命令(CMD)
exec "$@"

6.9 构建命令详解

# 基本构建
docker build -t my-app:v1.0 .

# 指定 Dockerfile 路径
docker build -f docker/Dockerfile.prod -t my-app:prod .

# 传递构建参数
docker build --build-arg VERSION=1.0 --build-arg ENV=prod -t my-app .

# 不使用缓存
docker build --no-cache -t my-app .

# 指定目标阶段
docker build --target production -t my-app:prod .

# 使用 BuildKit(默认启用)
DOCKER_BUILDKIT=1 docker build -t my-app .

# 构建并输出到本地(Buildx)
docker buildx build --load -t my-app .

# 构建并推送到仓库(Buildx)
docker buildx build --push -t my-user/my-app:v1.0 .

6.10 镜像标签策略

# 语义化版本
docker build -t my-app:1.0.0 -t my-app:1.0 -t my-app:1 -t my-app:latest .

# Git commit hash
GIT_SHA=$(git rev-parse --short HEAD)
docker build -t my-app:$GIT_SHA -t my-app:latest .

# 日期标签
DATE=$(date +%Y%m%d)
docker build -t my-app:$DATE .
标签策略示例适用场景
语义版本1.0.0, 1.0正式发布
Git SHAa1b2c3dCI/CD 流水线
日期20240101每日构建
分支名feature-login开发分支
latestlatest仅用于开发,不用于生产

要点回顾

要点核心内容
指令顺序按变化频率排序:系统依赖 → 应用依赖 → 应用代码
多阶段构建分离构建环境和运行环境,大幅减小镜像大小
缓存优化.dockerignore + 合理 COPY 顺序 + BuildKit 缓存挂载
安全使用非 root 用户、最小基础镜像、健康检查
CMD vs ENTRYPOINTCMD 可覆盖,ENTRYPOINT 不可覆盖

注意事项

不要在镜像中存储密钥: 不要在 Dockerfile 中硬编码密码、API Key 等敏感信息。使用构建密钥 (--secret) 或运行时环境变量。

合并 RUN 层: 每个 RUN 指令创建一层。将相关命令合并,减少层数和镜像大小。

清理缓存: 在同一 RUN 中安装包后清理缓存(如 rm -rf /var/lib/apt/lists/*),避免增大镜像。


下一步

07 - 网络模型:学习 Docker 的网络模式和端口映射。