Skip to content

Dockerfile 详解

本章将深入讲解 Dockerfile 的语法、指令和最佳实践,帮助你掌握如何编写高效、安全的 Dockerfile 来构建自定义镜像。

Dockerfile 基础

什么是 Dockerfile?

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

Dockerfile 的基本结构

dockerfile
# 注释
FROM base_image:tag
LABEL maintainer="your-email@example.com"
RUN command
COPY source destination
WORKDIR /app
EXPOSE 8080
CMD ["executable", "param1", "param2"]

构建上下文

构建上下文是 docker build 命令发送给 Docker 守护进程的文件和目录集合:

bash
# 当前目录作为构建上下文
docker build -t myapp:v1.0 .

# 指定构建上下文
docker build -t myapp:v1.0 /path/to/context

# 从 Git 仓库构建
docker build -t myapp:v1.0 https://github.com/user/repo.git

Dockerfile 指令详解

FROM - 基础镜像

dockerfile
# 基本用法
FROM ubuntu:20.04

# 使用多阶段构建
FROM node:16 AS builder
FROM nginx:alpine AS runtime

# 使用 ARG 变量
ARG BASE_IMAGE=node:16
FROM ${BASE_IMAGE}

# 指定平台
FROM --platform=linux/amd64 node:16

最佳实践:

  • 使用具体的标签而不是 latest
  • 优先选择官方镜像
  • 使用轻量级的基础镜像(如 Alpine)

RUN - 执行命令

dockerfile
# Shell 形式(推荐用于复杂命令)
RUN apt-get update && apt-get install -y \
    curl \
    vim \
    && rm -rf /var/lib/apt/lists/*

# Exec 形式(推荐用于简单命令)
RUN ["apt-get", "update"]

# 多行命令
RUN apt-get update \
    && apt-get install -y curl \
    && curl -sL https://deb.nodesource.com/setup_16.x | bash - \
    && apt-get install -y nodejs \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# 使用 heredoc(Docker 20.10+)
RUN <<EOF
apt-get update
apt-get install -y curl
apt-get clean
rm -rf /var/lib/apt/lists/*
EOF

最佳实践:

  • 合并多个 RUN 指令减少层数
  • 在同一层中清理缓存和临时文件
  • 使用 && 连接命令确保失败时停止

COPY 和 ADD - 复制文件

dockerfile
# COPY - 基本文件复制(推荐)
COPY package.json /app/
COPY src/ /app/src/
COPY . /app/

# 设置文件权限
COPY --chown=user:group files/ /app/

# 从其他阶段复制
COPY --from=builder /app/dist /usr/share/nginx/html

# ADD - 高级复制功能
ADD https://example.com/file.tar.gz /tmp/
ADD archive.tar.gz /app/  # 自动解压

# 复制多个文件
COPY file1.txt file2.txt /app/
COPY ["file with spaces.txt", "/app/"]

COPY vs ADD:

  • 优先使用 COPY,功能更明确
  • ADD 支持 URL 下载和自动解压
  • ADD 可能带来安全风险

WORKDIR - 设置工作目录

dockerfile
# 设置工作目录
WORKDIR /app

# 相对路径(基于当前 WORKDIR)
WORKDIR src
WORKDIR ../config

# 使用变量
ENV APP_HOME /app
WORKDIR $APP_HOME

# 创建目录并设置为工作目录
WORKDIR /app/data  # 如果不存在会自动创建

最佳实践:

  • 使用绝对路径
  • 避免使用 RUN cd 命令
  • 为不同的操作设置合适的工作目录

ENV - 环境变量

dockerfile
# 设置单个环境变量
ENV NODE_ENV production

# 设置多个环境变量
ENV NODE_ENV=production \
    PORT=3000 \
    DEBUG=false

# 使用环境变量
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . $APP_HOME

# 在 RUN 中使用
RUN echo "Node environment: $NODE_ENV"

ARG - 构建参数

dockerfile
# 定义构建参数
ARG VERSION=latest
ARG BUILD_DATE

# 使用构建参数
FROM node:${VERSION}
LABEL build-date=${BUILD_DATE}

# 在 RUN 中使用
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y curl

# 多阶段构建中的 ARG
FROM node:16 AS builder
ARG API_URL
ENV REACT_APP_API_URL=${API_URL}
RUN npm run build

构建时传递参数:

bash
docker build --build-arg VERSION=16 --build-arg BUILD_DATE=$(date) -t myapp .

EXPOSE - 暴露端口

dockerfile
# 暴露单个端口
EXPOSE 8080

# 暴露多个端口
EXPOSE 8080 8443

# 指定协议
EXPOSE 8080/tcp
EXPOSE 53/udp

# 使用变量
ENV PORT 8080
EXPOSE $PORT

注意: EXPOSE 只是声明,不会自动发布端口。

VOLUME - 数据卷

dockerfile
# 声明数据卷
VOLUME /data

# 声明多个数据卷
VOLUME ["/data", "/logs"]

# 使用变量
ENV DATA_DIR /app/data
VOLUME $DATA_DIR

USER - 设置用户

dockerfile
# 使用用户 ID
USER 1000

# 使用用户名
USER appuser

# 使用用户和组
USER appuser:appgroup

# 创建用户并切换
RUN useradd -r -s /bin/false appuser
USER appuser

CMD 和 ENTRYPOINT - 启动命令

dockerfile
# CMD - 默认命令(可被覆盖)
CMD ["nginx", "-g", "daemon off;"]
CMD nginx -g "daemon off;"

# ENTRYPOINT - 入口点(不可被覆盖)
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

# 组合使用
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]

# Shell 形式 vs Exec 形式
CMD echo "Hello World"              # Shell 形式
CMD ["echo", "Hello World"]         # Exec 形式(推荐)

CMD vs ENTRYPOINT:

  • CMD:提供默认命令,可被 docker run 参数覆盖
  • ENTRYPOINT:定义容器启动时的固定命令
  • 组合使用:ENTRYPOINT 定义命令,CMD 提供默认参数

LABEL - 元数据标签

dockerfile
# 添加标签
LABEL version="1.0"
LABEL description="This is a web application"
LABEL maintainer="developer@example.com"

# 多个标签
LABEL version="1.0" \
      description="Web application" \
      maintainer="developer@example.com"

# 使用变量
ARG VERSION=1.0
LABEL version=${VERSION}

HEALTHCHECK - 健康检查

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

# 使用 wget
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1

# 禁用健康检查
HEALTHCHECK NONE

# 自定义脚本
COPY healthcheck.sh /usr/local/bin/
HEALTHCHECK --interval=30s CMD /usr/local/bin/healthcheck.sh

多阶段构建

基本多阶段构建

dockerfile
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# 运行阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

复杂多阶段构建

dockerfile
# 依赖安装阶段
FROM node:16-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# 构建阶段
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 运行阶段
FROM node:16-alpine AS runner
WORKDIR /app

# 创建用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# 复制必要文件
COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist

USER nextjs
EXPOSE 3000
CMD ["node", "dist/index.js"]

构建特定阶段

bash
# 只构建到指定阶段
docker build --target builder -t myapp:builder .

# 构建最终阶段
docker build --target runner -t myapp:latest .

实际应用示例

Node.js 应用 Dockerfile

dockerfile
# 使用官方 Node.js 镜像
FROM node:16-alpine

# 设置工作目录
WORKDIR /app

# 安装 dumb-init(处理信号)
RUN apk add --no-cache dumb-init

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

# 复制 package 文件
COPY --chown=nextjs:nodejs package*.json ./

# 安装依赖
USER nextjs
RUN npm ci --only=production && npm cache clean --force

# 复制应用代码
COPY --chown=nextjs:nodejs . .

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node healthcheck.js

# 启动应用
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]

Python Flask 应用 Dockerfile

dockerfile
FROM python:3.9-slim

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

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

# 创建应用用户
RUN useradd --create-home --shell /bin/bash app

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY requirements.txt .

# 安装 Python 依赖
RUN pip install -r requirements.txt

# 复制应用代码
COPY . .

# 更改文件所有者
RUN chown -R app:app /app

# 切换到应用用户
USER app

# 暴露端口
EXPOSE 5000

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

# 启动应用
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]

Go 应用多阶段构建

dockerfile
# 构建阶段
FROM golang:1.19-alpine AS builder

# 安装 git(可能需要获取依赖)
RUN apk add --no-cache git

# 设置工作目录
WORKDIR /app

# 复制 go mod 文件
COPY go.mod go.sum ./

# 下载依赖
RUN go mod download

# 复制源代码
COPY . .

# 构建应用
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# 运行阶段
FROM alpine:3.16

# 安装 ca-certificates(HTTPS 请求需要)
RUN apk --no-cache add ca-certificates

# 创建应用目录
WORKDIR /root/

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

# 创建非 root 用户
RUN adduser -D -s /bin/sh appuser
USER appuser

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1

# 启动应用
CMD ["./main"]

.dockerignore 文件

.dockerignore 语法

dockerignore
# 注释
node_modules
npm-debug.log*
.git
.gitignore
README.md
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# 日志文件
logs
*.log

# 运行时数据
pids
*.pid
*.seed
*.pid.lock

# 覆盖范围
coverage

# nyc 测试覆盖
.nyc_output

# 依赖目录
node_modules/
jspm_packages/

# 可选的 npm 缓存目录
.npm

# 可选的 REPL 历史
.node_repl_history

# 输出的编译二进制文件
*.tgz

# Yarn 完整性文件
.yarn-integrity

# dotenv 环境变量文件
.env

# 下一个.js 构建输出
.next

# Nuxt.js 构建输出
.nuxt

# Gatsby 文件
.cache/
public

# Storybook 构建输出
.out
.storybook-out

# 临时文件夹
tmp/
temp/

# IDE 文件
.vscode/
.idea/
*.swp
*.swo
*~

# OS 生成的文件
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

.dockerignore 最佳实践

dockerignore
# 1. 排除版本控制文件
.git
.gitignore
.gitattributes

# 2. 排除构建工具文件
Dockerfile*
docker-compose*.yml
.dockerignore

# 3. 排除开发依赖
node_modules
.npm
.yarn

# 4. 排除测试文件
test/
tests/
**/*test.js
**/*spec.js
coverage/

# 5. 排除文档文件
README.md
CHANGELOG.md
LICENSE
docs/

# 6. 排除环境配置
.env*
!.env.example

# 7. 排除临时文件
*.tmp
*.temp
*.log

Dockerfile 最佳实践

1. 镜像大小优化

dockerfile
# ❌ 不好的做法
FROM ubuntu:20.04
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN apt-get install -y git

# ✅ 好的做法
FROM ubuntu:20.04
RUN apt-get update && \
    apt-get install -y \
        curl \
        vim \
        git \
    && rm -rf /var/lib/apt/lists/*

2. 缓存优化

dockerfile
# ❌ 不好的做法 - 代码变化会使依赖缓存失效
COPY . /app
RUN npm install

# ✅ 好的做法 - 依赖文件单独复制
COPY package*.json /app/
RUN npm install
COPY . /app

3. 安全最佳实践

dockerfile
# ✅ 使用非 root 用户
FROM node:16-alpine
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001
USER nextjs

# ✅ 使用具体的版本标签
FROM node:16.17.0-alpine

# ✅ 扫描漏洞
# 在构建后运行: docker scan myapp:latest

# ✅ 最小权限原则
COPY --chown=nextjs:nodejs . /app

4. 多阶段构建优化

dockerfile
# ✅ 生产镜像只包含运行时需要的文件
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --production

FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

调试 Dockerfile

构建调试技巧

bash
# 1. 逐步构建
docker build --target builder -t debug-builder .
docker run -it debug-builder /bin/bash

# 2. 不使用缓存
docker build --no-cache -t myapp .

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

# 4. 保留中间容器
docker build --rm=false -t myapp .

# 5. 查看镜像历史
docker history myapp:latest

常见错误和解决方案

dockerfile
# 错误 1: 路径问题
# ❌ COPY ../config.json /app/  # 不能访问构建上下文外的文件
# ✅ COPY config.json /app/     # 文件必须在构建上下文内

# 错误 2: 权限问题
# ❌ USER appuser
#    RUN mkdir /app             # 权限不足
# ✅ RUN mkdir /app && chown appuser:appuser /app
#    USER appuser

# 错误 3: 环境变量问题
# ❌ RUN echo $MY_VAR           # 可能为空
# ✅ ARG MY_VAR
#    RUN echo ${MY_VAR}

# 错误 4: 信号处理问题
# ❌ CMD node app.js            # 不能正确处理信号
# ✅ CMD ["node", "app.js"]     # 使用 exec 形式

本章小结

本章深入讲解了 Dockerfile 的各个方面:

关键要点:

  • 基础指令:FROM, RUN, COPY, WORKDIR 等核心指令
  • 高级特性:多阶段构建、健康检查、信号处理
  • 最佳实践:镜像优化、安全配置、缓存策略
  • 实际应用:不同语言和框架的 Dockerfile 示例
  • 调试技巧:构建问题排查和解决方案

重要原则:

  • 使用轻量级基础镜像
  • 合并 RUN 指令减少层数
  • 优化构建缓存
  • 遵循安全最佳实践
  • 使用多阶段构建

在下一章中,我们将学习 Docker Compose,了解如何编排多容器应用。

延伸阅读

本站内容仅供学习和研究使用。