一、什么是 Dockerfile

Dockerfile 是一个文本文件,其中包含了一系列构建 Docker 镜像所需的指令和参数。通过 Dockerfile,我们可以自动化地构建出符合业务需求的自定义镜像,实现应用的标准化交付。

Dockerfile 的核心价值:将应用环境配置、依赖安装、代码部署等过程代码化,实现基础架构即代码(IaC),让环境构建变得可版本控制、可复现、可审计。


二、Dockerfile 常用指令详解

2.1 指令速查表

指令 用途 示例
FROM 指定基础镜像 FROM ubuntu:22.04
RUN 执行命令(构建时) RUN apt update && apt install -y nginx
COPY 复制文件到镜像中 COPY ./app /app/
ADD 类似 COPY,支持解压和 URL ADD app.tar.gz /app/
WORKDIR 设置工作目录 WORKDIR /app
CMD 容器启动时的默认命令 CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT 容器启动时的固定命令入口 ENTRYPOINT ["java", "-jar"]
ENV 设置环境变量 ENV JAVA_HOME=/usr/lib/jvm/java-11
ARG 构建参数(构建时传入) ARG VERSION=latest
EXPOSE 声明容器监听的端口 EXPOSE 8080
VOLUME 声明数据卷挂载点 VOLUME /data
USER 切换运行用户 USER appuser
LABEL 添加元数据标签 LABEL version="1.0"
HEALTHCHECK 检查容器健康状态 HEALTHCHECK CMD curl -f http://localhost/
ONBUILD 触发器指令(子镜像构建时执行) ONBUILD COPY . /app

三、核心指令深度解析

3.1 FROM——指定基础镜像

FROM 是 Dockerfile 的第一条指令,指定构建所用的基础镜像。

# 使用官方 Ubuntu 镜像
FROM ubuntu:22.04

# 使用 Alpine(轻量级 Linux,体积小)
FROM alpine:3.18

# 使用特定语言运行环境
FROM python:3.11-slim
FROM node:18-alpine
FROM openjdk:11-jre-slim
FROM golang:1.21-alpine
FROM nginx:alpine
FROM mysql:5.7

💡 选择建议:优先选择官方镜像,生产环境推荐使用 -slim-alpine 版本以减小镜像体积。

3.2 RUN——执行构建命令

RUN 在构建阶段执行命令,每执行一次 RUN 都会生成一个新的镜像层。

# shell 格式(默认 /bin/sh -c)
RUN apt update && apt install -y curl wget vim

# exec 格式(推荐,更明确)
RUN ["apt", "update"]
RUN ["apt", "install", "-y", "curl"]

# 最佳实践:合并 RUN 命令减少层数
RUN apt update && \
    apt install -y curl wget && \
    apt clean && \
    rm -rf /var/lib/apt/lists/*

⚠️ 注意:每一条 RUN 都会产生一个新的镜像层,建议将相关命令用 && 合并成一行,减少镜像层数。

3.3 COPY vs ADD——复制文件

对比项 COPY ADD
复制本地文件 ✅ 支持 ✅ 支持
自动解压压缩包 ❌ 不支持 ✅ 支持(.tar.gz 等)
从 URL 下载 ❌ 不支持 ✅ 支持
推荐程度 ⭐⭐⭐⭐⭐ 优先使用 ⭐⭐ 仅在需要解压时使用
# COPY 基本用法
COPY ./app.jar /app/app.jar
COPY --chown=appuser:appuser ./config/ /app/config/

# ADD 用法(解压压缩包)
ADD ./app.tar.gz /app/

# ADD 从 URL 下载
ADD https://example.com/app.tar.gz /tmp/

💡 最佳实践:能使用 COPY 就使用 COPYADD 在需要自动解压时再考虑。

3.4 WORKDIR——设置工作目录

WORKDIR 切换当前工作目录,后续的 RUNCMDENTRYPOINT 等指令都会在该目录下执行。

WORKDIR /app

# 相当于:cd /app
RUN mkdir logs
COPY . .

推荐:使用绝对路径,让 Dockerfile 更加清晰。

3.5 CMD 与 ENTRYPOINT——容器启动命令

对比项 CMD ENTRYPOINT
作用 提供默认启动命令 固定启动命令入口
是否可被覆盖 ✅ 启动时可被覆盖 ❌ 启动时不可被覆盖(--entrypoint 可覆盖)
配合使用方式 为 ENTRYPOINT 提供默认参数 定义固定命令,CMD 传参
# CMD 三种写法
CMD echo "Hello Docker"              # shell 格式
CMD ["echo", "Hello Docker"]          # exec 格式(推荐)
CMD ["nginx", "-g", "daemon off;"]    # 使用 JSON 数组

# ENTRYPOINT 两种写法
ENTRYPOINT ["java", "-jar"]           # exec 格式(推荐)
ENTRYPOINT java -jar                  # shell 格式

# 经典组合:ENTRYPOINT + CMD
ENTRYPOINT ["java", "-jar"]
CMD ["app.jar"]
# 启动时:docker run -it myapp  => java -jar app.jar
# 启动时传参:docker run -it myapp other.jar => java -jar other.jar

💡 选择建议

  • 应用入口固定 → 使用 ENTRYPOINT
  • 提供可替换的默认命令 → 使用 CMD
  • 两者结合 → ENTRYPOINT 固定,CMD 传参

3.6 ENV 与 ARG——变量管理

对比项 ENV ARG
作用范围 构建时 + 运行时 仅构建时
是否持久化 ✅ 在镜像中持久化 ❌ 构建完成后不可见
运行时访问 ✅ 可在容器内访问 ❌ 容器内不可访问
传参方式 构建时用 --build-arg 构建时用 --build-arg
# ARG:构建参数
ARG APP_VERSION=1.0.0
RUN echo "Building version ${APP_VERSION}"

# ENV:环境变量
ENV JAVA_HOME=/usr/lib/jvm/java-11
ENV APP_ENV=production \
    APP_PORT=8080
RUN echo $JAVA_HOME

# 构建时传参
# docker build --build-arg APP_VERSION=2.0.0 -t myapp .

3.7 EXPOSE——声明端口

EXPOSE 仅用于文档说明端口映射提示,实际端口映射仍需要在 docker run 时使用 -p

# 声明一个端口
EXPOSE 8080

# 声明多个端口
EXPOSE 80 443 8080

# 声明 UDP 端口
EXPOSE 53/udp

3.8 VOLUME——声明数据卷

VOLUME 声明容器中的挂载点,方便数据持久化。

# 声明单个挂载点
VOLUME /data

# 声明多个挂载点
VOLUME ["/data", "/logs", "/config"]

注意VOLUME 只是声明,实际挂载仍需在 docker run 时使用 -v 指定。

3.9 HEALTHCHECK——健康检查

HEALTHCHECK 让 Docker 定期检查容器健康状态,状态显示为 healthyunhealthy

# 基本格式
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost/ || exit 1

# 参数说明
# --interval=30s  每隔 30 秒检查一次
# --timeout=3s    检查命令超时时间
# --retries=3     连续失败 3 次标记为 unhealthy

3.10 USER——切换用户

为了安全性,不建议使用 root 用户运行应用

# 创建用户并切换
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser:appgroup

# 后续命令都以 appuser 身份执行
RUN whoami   # 输出 appuser

四、实战案例

4.1 案例一:构建 Nginx 静态站点镜像

# 1. 指定基础镜像
FROM nginx:alpine

# 2. 设置维护者标签
LABEL maintainer="dev@example.com"

# 3. 删除默认页面
RUN rm -rf /usr/share/nginx/html/*

# 4. 复制静态网站文件
COPY ./dist /usr/share/nginx/html

# 5. 复制自定义 Nginx 配置
COPY ./nginx.conf /etc/nginx/conf.d/default.conf

# 6. 声明端口
EXPOSE 80

# 7. 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget -q -O - http://localhost/ || exit 1

# 8. 启动命令(Nginx 官方镜像已定义,无需再次指定)

构建与运行:

# 构建镜像
docker build -t my-website:v1 .

# 运行容器
docker run -d --name website -p 8080:80 my-website:v1

# 验证
curl http://localhost:8080

4.2 案例二:构建 Java Spring Boot 应用镜像

# 1. 基础镜像(使用 OpenJDK)
FROM openjdk:11-jre-slim

# 2. 设置元数据
LABEL version="1.0" \
      description="Spring Boot Application" \
      maintainer="dev@example.com"

# 3. 设置工作目录
WORKDIR /app

# 4. 设置时区(可选)
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 5. 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 6. 复制 JAR 包
COPY --chown=appuser:appuser target/app.jar app.jar

# 7. 暴露端口
EXPOSE 8080

# 8. 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

# 9. 切换用户
USER appuser

# 10. 启动命令
ENTRYPOINT ["java", "-jar"]
CMD ["app.jar"]

构建与运行:

# 构建镜像
docker build -t spring-app:v1 .

# 带环境变量运行
docker run -d --name spring-app \
  -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e DB_HOST=mysql \
  spring-app:v1

# 查看日志
docker logs -f spring-app

4.3 案例三:构建 Python Flask 应用镜像

# 1. 基础镜像
FROM python:3.11-slim

# 2. 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

# 3. 设置工作目录
WORKDIR /app

# 4. 复制依赖文件(利用 Docker 缓存层)
COPY requirements.txt .

# 5. 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt

# 6. 复制应用代码
COPY . .

# 7. 创建非 root 用户
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

# 8. 暴露端口
EXPOSE 5000

# 9. 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]

构建与运行:

# 构建镜像
docker build -t flask-app:v1 .

# 运行容器(挂载代码目录,便于开发调试)
docker run -d --name flask-app \
  -p 5000:5000 \
  -v $(pwd):/app \
  flask-app:v1

4.4 案例四:多阶段构建(Multi-stage Build)优化镜像大小

多阶段构建允许在 Dockerfile 中定义多个 FROM 阶段,最终只将需要的文件复制到最终镜像中,有效减少镜像体积

# ============ 第一阶段:构建阶段 ============
FROM golang:1.21-alpine AS builder

WORKDIR /build

# 复制 go.mod 和 go.sum(利用缓存)
COPY go.mod go.sum ./
RUN go mod download

# 复制源代码并编译
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o app main.go

# ============ 第二阶段:运行阶段 ============
FROM alpine:latest

# 安装 CA 证书(用于 HTTPS 请求)
RUN apk --no-cache add ca-certificates

WORKDIR /app

# 从构建阶段复制二进制文件
COPY --from=builder /build/app .

# 创建非 root 用户
RUN adduser -D -u 1000 appuser && chown appuser:appuser /app/app
USER appuser

EXPOSE 8080

CMD ["./app"]

对比效果

  • 传统方式:Go 编译镜像约 800MB
  • 多阶段构建后:最终运行镜像仅 15MB 🎉

构建与运行:

docker build -t go-app:v1 .
docker run -d --name go-app -p 8080:8080 go-app:v1

4.5 案例五:Node.js 应用 + 多阶段构建

# ============ 第一阶段:构建 ============
FROM node:18-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# ============ 第二阶段:运行 ============
FROM nginx:alpine

# 复制构建产物到 Nginx 静态目录
COPY --from=builder /app/dist /usr/share/nginx/html

# 复制 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

五、Dockerfile 最佳实践总结

5.1 指令编写规范

序号 最佳实践 原因
1 使用 COPY 而非 ADD,除非需要自动解压 ADD 行为复杂,容易引起混淆
2 合并 RUN 命令,减少镜像层数 每层都有开销,影响构建和拉取速度
3 利用 Docker 缓存机制,将变动频率低的指令前置 提高构建速度
4 使用 .dockerignore 排除无关文件 减少构建上下文体积
5 指定具体的基础镜像版本,避免 latest 保证构建的可复现性
6 尽量使用 alpineslim 版本基础镜像 减小镜像体积
7 使用多阶段构建 大幅减小最终镜像大小
8 切换非 root 用户运行应用 提高安全性
9 添加 HEALTHCHECK 健康检查 便于监控容器状态
10 使用 LABEL 添加元数据 方便镜像管理和版本追踪

5.2 .dockerignore 示例

# Git 相关
.git/
.gitignore

# 依赖目录
node_modules/
target/
*.pyc
__pycache__/

# 构建产物
*.log
*.tmp
*.swp
.DS_Store

# IDE 配置
.idea/
.vscode/
*.iml

# 敏感信息
.env
*.pem
*.key
secrets/

六、常用构建与调试命令

指令 说明 示例
docker build 构建镜像 docker build -t myapp:v1 .
docker build --no-cache 不使用缓存构建 docker build --no-cache -t myapp:v1 .
docker build --build-arg 传入构建参数 docker build --build-arg VERSION=2.0 -t myapp:v2 .
docker history 查看镜像构建历史 docker history myapp:v1
docker image inspect 查看镜像详情 docker image inspect myapp:v1
docker save 导出镜像为 tar 包 docker save -o myapp.tar myapp:v1

Dockerfile 是实现 “一次构建,处处运行” 的关键,掌握它意味着你可以将任何应用以标准化、可复现的方式打包交付。当你需要部署时,只需一条 docker run 命令,整个环境就能在任意机器上完美复现——这正是现代云原生时代的基础能力。


如果这篇文章对你有帮助,欢迎收藏、分享,也欢迎在评论区交流讨论!

更多推荐