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.gitDockerfile 指令详解
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_DIRUSER - 设置用户
dockerfile
# 使用用户 ID
USER 1000
# 使用用户名
USER appuser
# 使用用户和组
USER appuser:appgroup
# 创建用户并切换
RUN useradd -r -s /bin/false appuser
USER appuserCMD 和 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
*.logDockerfile 最佳实践
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 . /app3. 安全最佳实践
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 . /app4. 多阶段构建优化
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,了解如何编排多容器应用。