Docker: Dockerfile进阶指令

最后更新:2026-08-26

生产级 Dockerfile 不仅要能运行,还要安全、可配置、可监控——这些靠进阶指令实现。

1. 你将学到


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) 选择原则

📌 重点: 最佳实践是只用 COPYADD 的自动解压和 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 可见)
100%
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}}' 查看配置的用户。

📖 小节


📝 作业

  1. 基础题(难度⭐):在第 7 课的 Dockerfile 中增加非 root 用户,构建后用 docker exec 验证 whoami 输出不是 root。
  2. 进阶题(难度⭐⭐):添加 HEALTHCHECK 指令,用 curl 检查应用健康端点。构建运行后用 docker inspect 查看 Health.Status。
  3. 挑战题(难度⭐⭐⭐):为第 7 课的 Flask 应用编写完整的生产级 Dockerfile,包含:ARG 版本号 + ENV 配置 + 非 root 用户 + HEALTHCHECK + LABEL,然后用 docker scout quickviewtrivy image 扫描镜像安全漏洞。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏