小陈的知识图谱
DockerL2 进阶核心重点

Dockerfile 与镜像构建

Dockerfile 指令详解、多阶段构建、镜像优化

Dockerfile 指令详解

核心指令

指令用途示例
FROM指定基础镜像FROM openjdk:17-slim
WORKDIR设置工作目录WORKDIR /app
COPY复制文件到镜像COPY target/app.jar .
ADD复制并支持自动解压ADD archive.tar.gz /tmp/
RUN构建时执行命令RUN apt-get update && apt-get install -y curl
ENV设置环境变量ENV JAVA_HOME=/usr/lib/jvm/java-17
EXPOSE声明端口EXPOSE 8080
CMD默认启动命令CMD ["java", "-jar", "app.jar"]
ENTRYPOINT入口点ENTRYPOINT ["java", "-jar"]
ARG构建参数ARG APP_VERSION=1.0.0
LABEL元数据标签LABEL maintainer="team@example.com"

CMD vs ENTRYPOINT

# CMD:参数可被覆盖
CMD ["java", "-jar", "app.jar"]

docker run myapp → java -jar app.jar

docker run myapp ls → ls(CMD 被覆盖)

ENTRYPOINT:主命令固定

ENTRYPOINT ["java", "-jar"] CMD ["app.jar"]

docker run myapp → java -jar app.jar

docker run myapp other.jar → java -jar other.jar(CMD 被覆盖,ENTRYPOINT 保留)

最佳实践: ENTRYPOINT 定义不可变的主命令,CMD 提供默认参数。

多阶段构建

多阶段构建用一个 Dockerfile 定义多个阶段,最终只保留需要的产物,大幅减小镜像体积。

应用场景:Java Spring Boot

# ===== 阶段 1:构建 =====
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline  # 缓存依赖
COPY src ./src
RUN mvn package -DskipTests

===== 阶段 2:运行 =====

FROM eclipse-temurin:17-jre-alpine WORKDIR /app

只复制构建产物,不包含 Maven 和源码

COPY --from=builder /build/target/app.jar . EXPOSE 8080 CMD ["java", "-jar", "app.jar"]

应用场景:前端 + Nginx

# ===== 阶段 1:前端构建 =====
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

===== 阶段 2:Nginx 分发 =====

FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80

效果对比

方案镜像大小构建时间安全风险
单阶段(含 Maven)~800MB包含构建工具
多阶段~180MB中等(依赖缓存)仅含运行时
多阶段 + distroless~120MB中等最小攻击面

镜像层缓存机制

Docker 构建时每一条指令生成一个(Layer),层会被缓存复用。

缓存生效规则

Dockerfile 指令                   缓存策略
─────────────────────────────────────────────────
FROM base                         ✅ 镜像存在即命中
RUN apt-get update                ✅ 命令字符串相同即命中
COPY package.json .               ❌ 文件内容变化则失效
RUN npm install                   ❌ 上层缓存失效则本级失效
COPY . .                          ❌ 始终检查文件变更

优化技巧:利用缓存分层

# ❌ 低效:源码变动让 npm install 每次都重跑
COPY . .
RUN npm install
RUN npm run build

✅ 高效:先复制依赖描述文件,利用缓存

COPY package.json package-lock.json ./ RUN npm ci # 依赖不变时命中缓存 COPY . . RUN npm run build

镜像大小优化

优化策略对比

策略方法效果
选择最小基础镜像alpine/slim/distroless从 ~1GB 降到 ~150MB
多阶段构建分离构建和运行环境减少 70-80%
清理临时文件同层 RUN 中清理减少 10-30%
减少层数合并 RUN 指令减少存储碎片
.dockerignore排除无用文件减少上下文大小

基础镜像对比

镜像大小内容适用场景
ubuntu:22.04~77MB完整 Ubuntu需要系统包
alpine:3.19~7MBmusl + busybox最小体积
eclipse-temurin:17-jre~200MBJREJava 运行
eclipse-temurin:17-jre-alpine~170MBJRE + AlpineJava + 小体积
gcr.io/distroless/java17~120MB仅运行时安全敏感

# 极致优化示例:distroless
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json ./
RUN npm ci && npm run build

FROM gcr.io/distroless/nodejs20-debian12
COPY --from=build /app/dist /app
CMD ["/app/server.js"]

不含 shell、包管理器,攻击面最小

.dockerignore

类似 .gitignore,排除无需发送给 Docker daemon 的文件:

.git/
node_modules/
target/
*.log
.env
.idea/
.vscode/

为什么重要: docker build整个上下文(当前目录)发送给 daemon。没有 .dockerignorenode_modules 等大目录会极大拖慢构建。

构建上下文

# 上下文是当前目录
docker build -t myapp .

指定 Dockerfile 位置

docker build -t myapp -f docker/Dockerfile .

指定上下文为其他目录

docker build -t myapp ./backend

关键理解: COPY . . 复制的是构建上下文docker build 的最后一个参数),不是 Dockerfile 所在目录。

核心要点

  • Dockerfile 核心指令与最佳实践
  • 多阶段构建原理与实现
  • 镜像层缓存机制
  • 镜像大小优化策略
  • 构建上下文与 .dockerignore