一、Dockerfile 基本概念

Dockerfile 是一个文本文件,包含一系列指令,用于自动化构建 Docker 镜像。每条指令都会在镜像中创建一个新的层。

基本格式规范

# 注释使用 # 号开头
# 指令不区分大小写(但惯例使用大写)
# 指令 参数

指令 参数1 参数2 ...

二、Dockerfile 核心命令详解

1. FROM - 指定基础镜像(必须)

作用:设置构建所基于的镜像,必须是第一条非注释指令。

# 语法
FROM [--platform=<平台>] <镜像名>[:<标签>] [AS <名称>]

# 示例
FROM ubuntu:22.04
FROM alpine:latest
FROM nginx:1.25-alpine
FROM python:3.11-slim
FROM --platform=linux/arm64 ubuntu:22.04  # 指定平台

# 多阶段构建中的别名
FROM golang:1.21 AS builder
FROM alpine:latest

最佳实践:

  • 使用官方镜像作为基础

  • 选择 Alpine 版本减小体积

  • 指定具体标签而不是 latest

2. RUN - 执行命令

作用:在构建镜像时执行命令,每个 RUN 创建一个新层。

# shell 形式(默认使用 /bin/sh -c)
RUN <命令>

# exec 形式(推荐)
RUN ["可执行文件", "参数1", "参数2"]

# 示例
RUN apt-get update && apt-get install -y curl
RUN ["apt-get", "update"]
RUN pip install requests flask

# 多行命令(使用 \ 换行)
RUN apt-get update && \
    apt-get install -y \
        curl \
        wget \
        vim && \
    rm -rf /var/lib/apt/lists/*

最佳实践:

  • 将多个命令合并到一个 RUN(减少层数)

  • 使用 && 连接命令

  • 清理临时文件减小镜像体积

3. CMD - 容器启动默认命令

作用:提供容器启动时的默认命令,可被 docker run 覆盖。每个 Dockerfile 只能有一个 CMD。

# exec 形式(推荐)
CMD ["可执行文件", "参数1", "参数2"]

# 作为 ENTRYPOINT 的默认参数
CMD ["参数1", "参数2"]

# shell 形式
CMD 命令 参数1 参数2

# 示例
CMD ["nginx", "-g", "daemon off;"]
CMD ["python", "app.py"]
CMD echo "Hello World"  # shell形式

# 作为 ENTRYPOINT 的参数
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

区别:

  • CMD 会被 docker run 后的命令覆盖

  • 如果 Dockerfile 有多个 CMD,只有最后一个生效

4. ENTRYPOINT - 容器启动入口点

作用:配置容器启动时执行的命令,不会被 docker run 覆盖(除非使用 --entrypoint)。

# exec 形式(推荐)
ENTRYPOINT ["可执行文件", "参数1", "参数2"]

# shell 形式
ENTRYPOINT 命令 参数1 参数2

# 示例
ENTRYPOINT ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["python", "app.py"]

# 配合 CMD 使用
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["nginx"]

CMD vs ENTRYPOINT:

场景用法
固定命令ENTRYPOINT ["nginx"]
固定命令+可变参数ENTRYPOINT ["nginx"] + CMD ["-g", "daemon off;"]
完全可覆盖CMD ["python", "app.py"]

使用示例:

# 示例1:可执行工具
FROM alpine
ENTRYPOINT ["ping"]
CMD ["localhost"]
# 运行:docker run myping google.com → ping google.com

# 示例2:数据库启动脚本
FROM mysql
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld"]

5. COPY - 复制文件

作用:从构建上下文复制文件/目录到镜像中。

# 语法
COPY [--chown=<用户>:<组>] <源路径>... <目标路径>

# exec 形式(路径包含空格时使用)
COPY ["源路径", "目标路径"]

# 示例
COPY . /app
COPY package.json /app/
COPY --chown=www-data:www-data index.html /var/www/
COPY --chmod=755 script.sh /usr/local/bin/
COPY --from=builder /app/dist /app/dist  # 多阶段构建

# 通配符
COPY hom* /mydir/
COPY abc?.txt /mydir/

注意事项:

  • 源路径必须在构建上下文中(不能使用 ../xxx)

  • 目标路径不存在时会自动创建

  • 复制目录时,会复制目录内容(不是目录本身)

6. ADD - 增强版 COPY

作用:类似 COPY,但支持更多功能(URL、自动解压)。

# 语法
ADD [--chown=<用户>:<组>] <源路径>... <目标路径>

# 示例
ADD https://example.com/file.tar.gz /tmp/  # 下载文件
ADD app.tar.gz /app/                        # 自动解压
ADD --chown=www-data:www-data . /app/

# 特殊功能
ADD *.txt /mydir/      # 通配符
ADD dir /app           # 复制目录内容

ADD vs COPY:

功能COPYADD
复制本地文件✓✓
自动解压 tar✗✓
下载 URL✗✓
推荐程度推荐仅在需要自动解压时使用

最佳实践:优先使用 COPY,除非需要自动解压功能。

7. WORKDIR - 设置工作目录

作用:设置 RUN, CMD, ENTRYPOINT, COPY, ADD 的工作目录。

# 语法
WORKDIR <目录路径>

# 示例
WORKDIR /app
WORKDIR /usr/local/src

# 相对路径
WORKDIR /app
WORKDIR src      # 实际变为 /app/src

# 使用环境变量
WORKDIR /home/${USERNAME}

# 多级目录会自动创建
WORKDIR /a/b/c

注意事项:

  • 如果目录不存在会自动创建

  • 可以多次使用,支持相对路径

  • 建议使用绝对路径避免混淆

8. ENV - 设置环境变量

作用:设置环境变量,在构建和运行时都有效。

# 语法
ENV <键> <值>           # 单个变量(空格分隔)
ENV <键>=<值> ...       # 多个变量(等号分隔)

# 示例
ENV NODE_VERSION 18.17.0
ENV APP_HOME=/app PATH=/app/bin:$PATH
ENV USER=admin PASSWORD=123456

# 使用变量
ENV APP_DIR /app
WORKDIR ${APP_DIR}
RUN echo $APP_DIR

# 持久化到容器
ENV DATABASE_URL=postgres://user:pass@db:5432/db

变量替换:

  • $variable 或 ${variable}

  • ${variable:-default} - 如果变量未设置,使用默认值

  • ${variable:+override} - 如果变量已设置,使用覆盖值

9. ARG - 构建参数

作用:定义构建时的变量,仅在构建过程中有效。

# 语法
ARG <变量名>[=<默认值>]

# 示例
ARG VERSION=1.0
ARG DEBIAN_FRONTEND=noninteractive
ARG BUILD_DATE

# 使用
FROM ubuntu:${VERSION}
ARG APP_VERSION
ENV APP_VERSION=${APP_VERSION}
RUN echo "Building version ${APP_VERSION}"

# 构建时传递参数
# docker build --build-arg VERSION=22.04 --build-arg APP_VERSION=2.0 .

ARG vs ENV:

特性ARGENV
作用范围仅构建过程构建+运行时
持久化不保留在镜像保留在镜像
覆盖方式--build-arg构建时固定

10. EXPOSE - 声明端口

作用:声明容器运行时监听的端口(仅为文档说明,不实际映射)。

# 语法
EXPOSE <端口>[/<协议>] ...

# 示例
EXPOSE 80
EXPOSE 443/tcp
EXPOSE 53/udp
EXPOSE 8080 3000 5000

# 示例:Web应用
FROM nginx
EXPOSE 80 443

注意事项:

  • 只是文档性说明,实际端口映射需要在 docker run -p 或 -P 时指定

  • -P 会暴露所有 EXPOSE 的端口

11. VOLUME - 创建挂载点

作用:声明容器中的目录为数据卷,用于数据持久化。

# 语法
VOLUME ["<路径1>", "<路径2>", ...]
VOLUME <路径>

# 示例
VOLUME /data
VOLUME ["/var/log", "/var/db"]
VOLUME /var/lib/mysql

# 完整示例
FROM mysql:8.0
VOLUME /var/lib/mysql
EXPOSE 3306

12. USER - 切换用户

作用:指定运行容器时使用的用户名或 UID。

# 语法
USER <用户名|UID>[:<组名|GID>]

# 示例
USER www-data
USER 1000
USER node:node
USER 1000:1000

# 完整示例
FROM node:18-alpine
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001
USER nodejs
WORKDIR /app
COPY --chown=nodejs:nodejs . .

最佳实践:

  • 不要使用 root 运行应用

  • 创建非特权用户运行服务

  • 使用 --chown 配合 COPY

13. LABEL - 添加元数据

作用:为镜像添加元数据(版本、描述、维护者等)。

# 语法
LABEL <键>=<值> <键>=<值> ...

# 示例
LABEL version="1.0"
LABEL maintainer="john@example.com"
LABEL description="This is my application"

# 多标签
LABEL version="1.0" \
      com.example.vendor="ACME" \
      com.example.release-date="2024-01-01"

# 查看标签
# docker inspect --format='{{json .Config.Labels}}' 镜像名

常用标签规范(OpenContainer Initiative):

LABEL org.opencontainers.image.title="MyApp"
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.description="My awesome app"
LABEL org.opencontainers.image.created="2024-01-01T00:00:00Z"
LABEL org.opencontainers.image.authors="support@example.com"

14. HEALTHCHECK - 健康检查

作用:告诉 Docker 如何检查容器是否健康。

# 语法
HEALTHCHECK [选项] CMD <命令>
HEALTHCHECK NONE  # 禁用健康检查

# 选项
--interval=<时间>    # 检查间隔(默认30s)
--timeout=<时间>     # 超时时间(默认30s)
--start-period=<时间> # 启动等待时间(默认0s)
--retries=<次数>     # 失败重试次数(默认3次)

# 示例
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost/ || exit 1

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/health || exit 1

# 检查数据库
HEALTHCHECK --interval=10s --timeout=5s \
  CMD mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD || exit 1

健康状态:

  • starting - 初始状态

  • healthy - 健康

  • unhealthy - 不健康

15. SHELL - 更改默认 Shell

作用:更改 RUN、CMD、ENTRYPOINT 使用的默认 shell。

# 语法
SHELL ["可执行文件", "参数"]

# 示例
# Linux 默认使用 ["/bin/sh", "-c"]
SHELL ["/bin/bash", "-c"]
RUN echo "Hello from bash"

# Windows 示例
SHELL ["powershell", "-Command"]
RUN Write-Host "Hello from PowerShell"

# 使用不同的 shell
SHELL ["/bin/sh", "-c"]
RUN apk add --no-cache curl

SHELL ["/bin/bash", "-c"]
RUN source venv/bin/activate && python app.py

16. STOPSIGNAL - 设置停止信号

作用:设置容器停止时发送的系统调用信号。

# 语法
STOPSIGNAL <信号>

# 示例
STOPSIGNAL SIGTERM   # 默认
STOPSIGNAL SIGQUIT
STOPSIGNAL SIGKILL
STOPSIGNAL SIGUSR1

17. ONBUILD - 触发指令

作用:设置当镜像被用作其他镜像的基础时触发的指令。

# 语法
ONBUILD <其他指令>

# 示例:构建基础镜像
FROM node:18
ONBUILD COPY package.json /app/
ONBUILD RUN npm install
ONBUILD COPY . /app/
WORKDIR /app
CMD ["npm", "start"]

# 使用时的效果(自动执行 ONBUILD 指令)
FROM my-node-base:latest  # 会自动执行 COPY + RUN

适用场景:

  • 构建基础框架镜像

  • 需要在子镜像中自动执行的配置

三、完整示例:不同场景的 Dockerfile

示例 1:Node.js 应用

# 多阶段构建
FROM node:18-alpine AS builder

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

FROM node:18-alpine
WORKDIR /app

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

# 复制依赖和代码
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --chown=nodejs:nodejs . .

USER nodejs

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node healthcheck.js

CMD ["node", "server.js"]

示例 2:Python 应用

FROM python:3.11-slim

# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    APP_HOME=/app

WORKDIR $APP_HOME

# 安装系统依赖
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        gcc \
        libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制并安装 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 创建非 root 用户
RUN addgroup --system app && \
    adduser --system --group app && \
    chown -R app:app $APP_HOME

USER app

EXPOSE 8000

CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]

示例 3:Nginx + 静态文件

FROM nginx:alpine

# 设置维护者信息
LABEL maintainer="admin@example.com"
LABEL version="1.0"

# 删除默认配置
RUN rm /etc/nginx/conf.d/default.conf

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

# 复制静态文件
COPY --chown=nginx:nginx ./dist /usr/share/nginx/html

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

EXPOSE 80

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

示例 4:Java/Spring Boot 应用

FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

# 添加构建参数
ARG JAR_FILE=target/*.jar

# 复制 JAR 文件
COPY ${JAR_FILE} app.jar

# 创建非 root 用户
RUN addgroup -S spring && \
    adduser -S spring -G spring && \
    chown spring:spring app.jar

USER spring

# JVM 优化
ENV JAVA_OPTS="-Xms256m -Xmx512m"

EXPOSE 8080

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

示例 5:数据库初始化

FROM postgres:15-alpine

# 设置环境变量
ENV POSTGRES_DB=myapp \
    POSTGRES_USER=myuser \
    POSTGRES_PASSWORD=mypassword

# 复制初始化脚本
COPY init.sql /docker-entrypoint-initdb.d/
COPY scripts/*.sh /docker-entrypoint-initdb.d/

# 设置数据卷
VOLUME /var/lib/postgresql/data

EXPOSE 5432

HEALTHCHECK --interval=30s --timeout=5s \
  CMD pg_isready -U myuser -d myapp || exit 1

四、Dockerfile 最佳实践

1. 层级优化

# ❌ 不好:多个 RUN 创建多个层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*

# ✅ 好:合并到单个 RUN
RUN apt-get update && \
    apt-get install -y curl nginx && \
    rm -rf /var/lib/apt/lists/*

2. 利用构建缓存

# ✅ 将不常变化的指令放在前面
FROM node:18
WORKDIR /app
COPY package*.json ./   # 依赖文件先复制
RUN npm install         # 只有 package.json 变化才重新安装
COPY . .                # 源代码后复制

3. 使用 .dockerignore

# .dockerignore 文件内容
node_modules
.git
*.log
.env
.DS_Store
dist
coverage

4. 最小化镜像体积

# 使用 Alpine 版本
FROM node:18-alpine
FROM python:3.11-alpine
FROM golang:1.21-alpine

# 清理临时文件
RUN apk add --no-cache curl && \
    rm -rf /var/cache/apk/*

# 多阶段构建
FROM golang:1.21 AS builder
# ... 构建二进制文件

FROM alpine:latest
COPY --from=builder /app/binary /usr/local/bin/

5. 安全性实践

# 不要以 root 运行
RUN adduser -D myuser
USER myuser

# 使用特定版本标签,避免 latest
FROM python:3.11.4-slim

# 限制资源
# docker run --memory=512m --cpus=1

# 使用 COPY 而不是 ADD(除非需要自动解压)
COPY . /app

五、常用组合模式

模式 1:开发环境

FROM node:18
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]

模式 2:生产环境

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci --only=production

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]

模式 3:调试友好

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt debugpy
COPY . .
ENV PYTHONBREAKPOINT=ipdb.set_trace
CMD ["python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "app.py"]

六、Dockerfile 命令优先级

构建阶段:
ARG (构建参数)
    ↓
FROM (基础镜像)
    ↓
LABEL / MAINTAINER (元数据)
    ↓
ARG / ENV (变量)
    ↓
RUN (构建命令)
    ↓
COPY / ADD (复制文件)
    ↓
WORKDIR (工作目录)
    ↓
EXPOSE / VOLUME (声明)
    ↓
USER (用户切换)
    ↓
HEALTHCHECK (健康检查)
    ↓
ENTRYPOINT / CMD (入口点)

七、调试技巧

# 1. 查看构建过程
docker build --progress=plain -t myapp .

# 2. 查看镜像层
docker history myapp

# 3. 临时运行中间层
docker run -it --rm <中间层镜像ID> /bin/sh

# 4. 查看构建缓存
docker builder du

# 5. 禁用缓存
docker build --no-cache -t myapp .

更多推荐