5Docker 深度探索

Dockerfile

构建镜像的艺术

Dockerfile指令多阶段构建

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第5章:Dockerfile(Building Docker Images)

一、导读

1.1 本章学习目标

Dockerfile 是镜像构建的蓝图。本章系统讲解每条指令、多阶段构建、以及最佳实践。

本章的核心学习目标包括:

  • 掌握 Dockerfile 的核心指令:FROM、RUN、COPY、ADD、WORKDIR、CMD、ENTRYPOINT。
  • 理解多阶段构建:分离编译环境和运行环境。
  • 掌握最佳实践:最小化层数、利用缓存、非 root 用户。
  • 使用 .dockerignore:排除不需要的文件。

1.2 为什么本章如此重要

Dockerfile 的质量直接影响镜像的大小、构建速度、安全性。一个好的 Dockerfile 可以将镜像从 800MB 减小到 15MB,构建时间从 10 分钟缩短到 1 分钟。

1.3 核心问题引导

CMD 和 ENTRYPOINT 的区别是什么?什么时候用哪个?

COPY 和 ADD 的区别是什么?为什么推荐 COPY?

多阶段构建如何减小镜像大小?

为什么要把变化频繁的层放在后面?

.dockerignore 有什么作用?


二、核心概念详解

2.1 核心指令

FROM

dockerfile
# 选择基础镜像
FROM node:18-alpine    # 推荐:小镜像
FROM python:3.11-slim  # 推荐:slim 版本
FROM ubuntu:22.04      # 不推荐:太大

RUN

dockerfile
# 每条 RUN 创建一层
RUN apt-get update
RUN apt-get install -y curl  # 两层

# 合并命令减少层数
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

COPY vs ADD

dockerfile
# COPY:简单复制,推荐
COPY package.json /app/
COPY . /app/

# ADD:支持自动解压和 URL
ADD app.tar.gz /app/       # 自动解压
ADD https://example.com/file /app/  # 下载文件(不推荐)

WORKDIR

dockerfile
WORKDIR /app
COPY . .       # 相对于 /app
RUN npm install  # 在 /app 中执行

CMD vs ENTRYPOINT

dockerfile
# CMD:默认命令,可被覆盖
CMD ["node", "server.js"]
# docker run myapp → node server.js
# docker run myapp bash → bash(覆盖 CMD)

# ENTRYPOINT:入口点,不可被覆盖(除非 --entrypoint)
ENTRYPOINT ["node"]
CMD ["server.js"]
# docker run myapp → node server.js
# docker run myapp app.js → node app.js(覆盖 CMD,不覆盖 ENTRYPOINT)

ENV 和 ARG

dockerfile
# ENV:运行时环境变量
ENV NODE_ENV=production
ENV PORT=8080

# ARG:构建时变量
ARG VERSION=1.0
RUN echo "Building version $VERSION"

EXPOSE

dockerfile
# 声明容器监听的端口(文档作用,不实际映射)
EXPOSE 8080
# 实际映射需要 docker run -p 8080:8080

2.2 多阶段构建

dockerfile
# 阶段1:编译
FROM golang:1.21-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app ./cmd/server

# 阶段2:运行
FROM alpine:3.18
RUN apk --no-cache add ca-certificates
COPY --from=builder /app /app
USER 1000:1000
EXPOSE 8080
ENTRYPOINT ["/app"]

效果:

  • 单阶段:~800MB(包含 Go 编译器、源码)
  • 多阶段:~15MB(只有编译产物 + Alpine)

2.3 最佳实践

dockerfile
# 1. 使用小的基础镜像
FROM node:18-alpine

# 2. 利用缓存:先复制依赖文件
COPY package*.json ./
RUN npm ci --only=production
COPY . .

# 3. 合并 RUN 命令
RUN apt-get update && apt-get install -y \
    curl \
    wget \
    && rm -rf /var/lib/apt/lists/*

# 4. 使用 .dockerignore
# node_modules
# .git
# *.md
# .env*

# 5. 非 root 用户
RUN addgroup -S app && adduser -S app -G app
USER app

# 6. 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --spider http://localhost:8080/health || exit 1

# 7. 不存储敏感信息
# 不要:ENV DB_PASSWORD=secret
# 应该:运行时通过环境变量注入

2.4 .dockerignore

node_modules
npm-debug.log
.git
.gitignore
.dockerignore
Dockerfile
docker-compose.yml
.env*
*.md
tests/
coverage/

三、实战案例

3.1 React 应用的多阶段构建

dockerfile
# 阶段1:构建
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 阶段2:运行
FROM nginx:1.25-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

3.2 使用 hadolint 检查

bash
# 安装 hadolint
docker pull hadolint/hadolint

# 检查 Dockerfile
docker run --rm -i hadolint/hadolint < Dockerfile
# Dockerfile:5 DL3008 Pin versions in apt get install
# Dockerfile:8 DL3059 Multiple consecutive RUN instructions

四、本章小结

FROM 选择基础镜像,推荐 alpine/slim。

RUN 合并命令减少层数。

COPY 优于 ADD,更安全。

多阶段构建分离编译和运行环境,大幅减小镜像。

最佳实践:利用缓存、非 root 用户、.dockerignore。


五、思考与练习

思考题

为什么 RUN apt-get update && apt-get install 要写在一行?

多阶段构建中,如何只复制编译产物而不复制源码?

为什么推荐 npm ci 而不是 npm install?

练习题

为一个真实项目编写多阶段 Dockerfile。

使用 hadolint 检查 Dockerfile 的最佳实践。

对比单阶段和多阶段构建的镜像大小。

5/8Dock
0%