Docker: Dockerfile进阶指令
最后更新:2026-08-26
生产级 Dockerfile 不仅要能运行,还要安全、可配置、可监控——这些靠进阶指令实现。
1. 你将学到
- COPY vs ADD 的选择原则
- ARG 构建参数传递
- ENV 运行时环境变量
- HEALTHCHECK 健康检查配置
- USER 非 root 安全原则
2. 一个安全审计的故事
(1) 痛点:容器以 root 运行是高危漏洞
Charlie 的安全审计报告指出:"容器以 root 运行"是高危漏洞。攻击者如果通过应用漏洞进入容器,就拥有 root 权限,可以修改系统文件、挂载主机目录。审计评分只有 45 分。
(2) Dockerfile 安全加固的解法
Alice 在 Dockerfile 里加了一行 USER appuser,把安全评分从 45 提升到了 85。
DOCKERFILE
# Security hardening: run as non-root user
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Create and switch to non-root user
RUN useradd -m appuser
USER appuser
CMD ["python", "app.py"]
(3) 收益:安全评分翻倍
只加了两行(RUN useradd + USER),安全评分从 45→85,审计通过。
3. COPY vs ADD
(1) 功能对比
| 功能 | COPY |
ADD |
|---|---|---|
| 复制本地文件到镜像 | ✅ | ✅ |
| 从 URL 下载文件 | ❌ | ✅ |
| 自动解压 tar/gzip/bzip2/xz | ❌ | ✅ |
多阶段构建 --from |
✅ | ✅ |
(2) 选择原则
📌 重点: 最佳实践是只用
COPY。ADD 的自动解压和 URL 下载行为不可预期,容易出错。需要解压时显式用 RUN tar 命令,更透明可控。
▶ 示例:COPY 与 ADD 行为差异(难度⭐⭐)
DOCKERFILE
# COPY: simply copies the tar file as-is
COPY archive.tar.gz /tmp/
# Result: /tmp/archive.tar.gz (still compressed)
# ADD: automatically extracts the tar file
ADD archive.tar.gz /tmp/
# Result: /tmp/archive/ (extracted contents)
▶ 示例:多阶段构建 COPY --from(难度⭐⭐⭐)
DOCKERFILE
# Multi-stage: copy artifact from builder stage
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o server
FROM alpine:3.19
COPY --from=builder /src/server /usr/local/bin/server
CMD ["server"]
4. ARG 与 ENV
(1) 作用范围对比
| 维度 | ARG |
ENV |
|---|---|---|
| 可用阶段 | 构建时(docker build) | 运行时(docker run) |
| 持久性 | 不写入镜像元数据 | 写入镜像元数据 |
| 可覆盖 | --build-arg |
docker run -e |
| CMD/ENTRYPOINT 可用 | ❌ | ✅ |
| 安全性 | 不暴露在运行时 | 暴露在运行时(docker inspect 可见) |
graph TB
BUILD["Build Time"] -->|ARG| LAYER["Image Layer"]
RUN["Run Time"] -->|ENV| CONTAINER["Container Env"]
BUILD -->|ENV persists| CONTAINER
▶ 示例:ARG 版本号构建(难度⭐⭐)
DOCKERFILE
# Use ARG for build-time variables
ARG PYTHON_VERSION=3.12
FROM python:${PYTHON_VERSION}-slim
ARG APP_VERSION=1.0
LABEL version="${APP_VERSION}"
WORKDIR /app
COPY . .
CMD ["python", "app.py"]
BASH
# Build with custom Python version
docker build --build-arg PYTHON_VERSION=3.11 -t myapp:3.11 .
# Build with custom app version
docker build --build-arg APP_VERSION=2.0 -t myapp:v2.0 .
▶ 示例:ENV 环境变量配置(难度⭐⭐)
DOCKERFILE
# ENV: available at runtime
FROM python:3.12-slim
ENV APP_PORT=5000 \
APP_HOST=0.0.0.0 \
LOG_LEVEL=info
WORKDIR /app
COPY . .
CMD ["python", "app.py"]
BASH
# Override ENV at runtime
docker run -d -e APP_PORT=8080 -e LOG_LEVEL=debug myapp:1.0
⚠️ 注意: 不要用 ENV 存储敏感信息(密码/密钥),它们会被写入镜像元数据,任何人
docker inspect 都能看到。敏感信息用 docker run -e 在运行时注入,或使用 Docker Secrets。
5. WORKDIR 工作目录
▶ 示例:WORKDIR 设置工作目录(难度⭐)
DOCKERFILE
# WORKDIR sets the working directory for subsequent instructions
FROM python:3.12-slim
# Each WORKDIR creates the directory if it doesn't exist
WORKDIR /app
COPY . .
# WORKDIR can be relative (relative to previous WORKDIR)
WORKDIR src
RUN pwd # Output: /app/src
| 规则 | 说明 |
|---|---|
| 推荐用绝对路径 | WORKDIR /app 而非 WORKDIR app |
| 自动创建 | 目录不存在时自动创建 |
| 影响 RUN/CMD/ENTRYPOINT/COPY | 后续指令基于 WORKDIR 执行 |
不要用 RUN cd |
RUN cd /app && ... 只在当前 RUN 有效,WORKDIR 持续生效 |
6. USER 非 root 安全原则
(1) 容器以 root 运行的风险
| 风险 | 说明 |
|---|---|
| 权限逃逸 | 容器漏洞 + root 权限 = 攻击者获得主机 root 权限 |
| 文件修改 | 可修改容器内任何文件(包括系统文件) |
| 挂载风险 | 挂载主机目录时,root 容器可修改主机文件 |
| CIS Benchmark | "容器以非 root 运行"是 CIS Docker 安全基线的必检项 |
▶ 示例:USER 非 root 运行(难度⭐⭐)
DOCKERFILE
# Best practice: create a dedicated user and switch to it
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Create non-root user with specific UID/GID
RUN groupadd -r appgroup && \
useradd -r -g appgroup -u 1000 -m appuser
# Ensure the app user owns the application files
RUN chown -R appuser:appgroup /app
# Switch to non-root user
USER appuser
CMD ["python", "app.py"]
🔒 安全:
USER appuser 之后的所有指令(RUN/CMD/ENTRYPOINT)都以 appuser 身份执行。即使攻击者进入容器,也没有 root 权限。
7. EXPOSE 端口声明
▶ 示例:EXPOSE 声明端口(难度⭐)
DOCKERFILE
# EXPOSE documents which ports the container listens on
FROM python:3.12-slim
WORKDIR /app
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
⚠️ 注意: EXPOSE 是文档声明,不实际发布端口。容器端口映射仍需要
docker run -p 5000:5000。EXPOSE 的作用:① 文档化;② docker run -P 随机映射时自动选择 EXPOSE 声明的端口。
8. HEALTHCHECK 健康检查
HEALTHCHECK 让 Docker 自动检测容器内应用是否健康,不健康时自动重启。
(1) 健康检查语法
DOCKERFILE
HEALTHCHECK [OPTIONS] CMD command
| 选项 | 默认值 | 说明 |
|---|---|---|
--interval |
30s | 检查间隔 |
--timeout |
30s | 超时时间 |
--start-period |
0s | 容器启动宽限期 |
--retries |
3 | 连续失败次数后标记 unhealthy |
▶ 示例:HEALTHCHECK curl 检查(难度⭐⭐)
DOCKERFILE
# Health check with curl
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
CMD ["python", "app.py"]
BASH
# Check health status
docker inspect myapp --format='{{.State.Health.Status}}'
💻 输出:
TEXT
📖 仅展示
healthy
(1) 健康状态说明
| 状态 | 含义 | Docker 行为 |
|---|---|---|
starting |
宽限期内(start-period) | 不计数 |
healthy |
检查通过 | 正常运行 |
unhealthy |
连续 retries 次失败 | 触发告警,配合 restart 策略可自动重启 |
9. LABEL 元信息
▶ 示例:LABEL 添加镜像元信息(难度⭐)
DOCKERFILE
# LABEL adds metadata to the image
LABEL maintainer="alice@example.com"
LABEL version="1.0"
LABEL description="Flask web application for production"
LABEL org.opencontainers.image.source="https://github.com/example/app"
10. 完整示例:为 Spring Boot 应用编写安全 Dockerfile
DOCKERFILE
# ============================================
# Production-grade Dockerfile for Spring Boot
# Features: non-root user, HEALTHCHECK, ARG, LABEL
# ============================================
# Build argument for JAR file name
ARG JAR_FILE=app.jar
# Stage 1: Build (future multi-stage, here just copy)
FROM eclipse-temurin:21-jre-alpine
# Metadata
LABEL maintainer="dev-team@example.com"
LABEL version="2.0"
# Create non-root user
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup
# Set working directory
WORKDIR /app
# Copy the JAR file (use ARG for flexibility)
COPY target/${JAR_FILE} app.jar
# Ensure app user owns the files
RUN chown -R appuser:appgroup /app
# Switch to non-root user
USER appuser
# Expose application port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
# Run the application
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--server.port=8080"]
BASH
# Build with custom JAR name
docker build --build-arg JAR_FILE=myapp-2.0.jar -t spring-app:2.0 .
# Run with environment override
docker run -d -p 8080:8080 --name spring spring-app:2.0
# Verify health status
docker inspect spring --format='User: {{.Config.User}}, Health: {{.State.Health.Status}}'
💻 输出:
TEXT
📖 仅展示
User: appuser, Health: healthy
❓ 常见问题
Q ADD 能自动解压 tar 包吗?
A 能,但这是"陷阱特性"——如果你只是想复制一个 tar 文件(不解压),ADD 会意外解压。最佳实践:只用 COPY,需要解压时显式
RUN tar -xzf file.tar.gz,行为透明可控。Q ARG 和 ENV 有什么区别?
A ARG 只在构建时可用,不写入镜像;ENV 写入镜像,运行时也可见。简单判断:需要运行时访问用 ENV(如配置端口),只在构建时用用 ARG(如版本号、下载 URL)。存密码用运行时
-e 而不是 ENV。Q HEALTHCHECK 怎么写才能不误报?
A 三个关键:① 设置
--start-period 给应用启动时间(Java 应用至少 30 秒);② 检查应用真实健康端点(如 /health),不要只检查端口是否通;③ --timeout 不超过 --interval 的一半,避免检查堆积。Q 为什么不要用 root 运行容器?
A 容器以 root 运行时,如果攻击者通过应用漏洞进入容器,就获得 root 权限。结合内核漏洞可能导致逃逸到主机。非 root 用户大幅缩小攻击面——CIS Docker Benchmark 将"非 root 运行"列为必须项。
Q EXPOSE 真的能暴露端口吗?
A 不能。EXPOSE 只是文档声明,告诉使用者容器打算使用哪些端口。实际映射需要
docker run -p 或 -P。-P(大写)会自动将 EXPOSE 声明的端口映射到主机随机端口。Q 怎么验证容器以非 root 运行?
A
docker exec <container> whoami 应返回用户名而非 root。或 docker inspect <container> --format='{{.Config.User}}' 查看配置的用户。📖 小节
- COPY vs ADD:只用 COPY,行为透明可控;ADD 的自动解压是不可预期的"陷阱"
- ARG 构建时变量(不写入镜像),ENV 运行时变量(写入镜像)
- WORKDIR 设置工作目录,比
RUN cd更可靠持久 - USER 切换到非 root 用户是安全最佳实践,只需 2 行代码
- HEALTHCHECK 让 Docker 自动检测应用健康状态,配合 restart 策略实现自愈
- EXPOSE 是端口文档声明,实际映射需要 -p 参数
📝 作业
- 基础题(难度⭐):在第 7 课的 Dockerfile 中增加非 root 用户,构建后用
docker exec验证whoami输出不是 root。 - 进阶题(难度⭐⭐):添加 HEALTHCHECK 指令,用 curl 检查应用健康端点。构建运行后用
docker inspect查看 Health.Status。 - 挑战题(难度⭐⭐⭐):为第 7 课的 Flask 应用编写完整的生产级 Dockerfile,包含:ARG 版本号 + ENV 配置 + 非 root 用户 + HEALTHCHECK + LABEL,然后用
docker scout quickview或trivy image扫描镜像安全漏洞。