第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:80802.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 的最佳实践。
对比单阶段和多阶段构建的镜像大小。