Dockerfile 基础概述
什么是 Dockerfile?
Dockerfile 是一个文本文件,包含了一系列用于构建 Docker 镜像的指令。它定义了镜像的基础环境、依赖项、配置和启动命令等。通过 docker build 命令,我们可以根据 Dockerfile 构建出可重复的、一致的镜像。
Dockerfile 的基本结构
# 基础镜像
FROM ubuntu:20.04
# 维护者信息
LABEL maintainer="[email protected]"
# 环境变量
ENV APP_HOME /app
ENV NODE_ENV production
# 工作目录
WORKDIR $APP_HOME
# 复制文件
COPY package*.json ./
# 执行命令
RUN npm install
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["npm", "start"]
核心指令详解
FROM
指定基础镜像,是 Dockerfile 的第一个指令。
# 使用官方镜像
FROM node:14-alpine
# 使用特定版本
FROM ubuntu:20.04
# 使用多阶段构建的基础镜像
FROM golang:1.16 as builder
RUN
执行命令并创建新的镜像层。
# 执行单个命令
RUN apt-get update && apt-get install -y nginx
# 执行多个命令(使用 && 连接,减少镜像层)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
wget \
git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY vs ADD
COPY:简单地复制文件和目录。
# 复制单个文件
COPY package.json ./
# 复制目录
COPY src/ ./src/
ADD:除了复制功能外,还支持解压缩文件和从 URL 下载文件。
# 复制并解压缩 tar 文件
ADD app.tar.gz /app/
# 从 URL 下载文件(不推荐,建议使用 RUN curl 或 RUN wget)
ADD https://example.com/file.txt /app/
WORKDIR
设置工作目录,后续的命令会在该目录下执行。
# 设置工作目录
WORKDIR /app
# 相对路径(相对于前一个 WORKDIR)
WORKDIR src
ENV
设置环境变量,可在后续的命令中使用。
# 设置单个环境变量
ENV NODE_ENV production
# 设置多个环境变量
ENV APP_HOME=/app \
PORT=3000 \
DEBUG=false
EXPOSE
声明容器运行时暴露的端口。
# 暴露单个端口
EXPOSE 80
# 暴露多个端口
EXPOSE 80 443
CMD vs ENTRYPOINT
CMD:设置容器的默认命令,可被 docker run 命令覆盖。
# exec 格式(推荐)
CMD ["npm", "start"]
# shell 格式
CMD npm start
ENTRYPOINT:设置容器的入口点,不可被 docker run 命令直接覆盖。
# exec 格式(推荐)
ENTRYPOINT ["npm"]
CMD ["start"]
# 这样运行:docker run my-app test
# 实际执行:npm test
VOLUME
创建挂载点,用于持久化数据。
# 创建挂载点
VOLUME ["/app/data"]
# 或者
VOLUME /app/data
USER
指定后续命令的执行用户。
# 创建用户并切换到该用户
RUN useradd -ms /bin/bash appuser
USER appuser
ARG
定义构建参数,可在 docker build 命令中通过 --build-arg 覆盖。
# 定义构建参数
ARG NODE_VERSION=14
# 使用构建参数
FROM node:${NODE_VERSION}-alpine
构建命令:
docker build --build-arg NODE_VERSION=16 -t my-app .
多阶段构建
多阶段构建是一种通过多个 FROM 指令在单个 Dockerfile 中创建多个构建阶段的技术,用于减小最终镜像的大小。
基本示例:Go 应用
# 第一阶段:构建
FROM golang:1.16 as builder
WORKDIR /app
COPY . .
RUN go build -o app .
# 第二阶段:运行
FROM alpine:3.14
WORKDIR /app
# 从构建阶段复制可执行文件
COPY --from=builder /app/app .
# 安装运行时依赖
RUN apk add --no-cache ca-certificates
# 运行应用
CMD ["./app"]
高级示例:Node.js 应用
# 第一阶段:安装依赖
FROM node:14-alpine as deps
WORKDIR /app
COPY package*.json ./
RUN npm install --production
# 第二阶段:构建应用
FROM node:14-alpine as builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN npm run build
# 第三阶段:运行
FROM node:14-alpine as runner
WORKDIR /app
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S appuser -u 1001
# 复制构建结果和依赖
COPY --from=builder /app/build ./build
COPY --from=deps /app/node_modules ./node_modules
COPY package*.json ./
# 切换到非 root 用户
USER appuser
# 暴露端口
EXPOSE 3000
# 启动应用
CMD ["npm", "start"]
镜像大小优化技巧
1. 使用轻量级基础镜像
# 不推荐:使用完整的 Ubuntu 镜像
FROM ubuntu:20.04
# 推荐:使用轻量级的 Alpine 镜像
FROM alpine:3.14
# 或者使用特定语言的 Alpine 镜像
FROM node:14-alpine
FROM python:3.9-alpine
FROM golang:1.16-alpine
2. 最小化镜像层
# 不推荐:多个 RUN 指令,创建多个镜像层
RUN apt-get update
RUN apt-get install -y nginx
RUN apt-get clean
# 推荐:单个 RUN 指令,使用 && 连接命令
RUN apt-get update && \
apt-get install -y --no-install-recommends nginx && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
3. 清理不需要的文件
# 清理包管理器缓存
RUN apt-get clean && rm -rf /var/lib/apt/lists/* # Debian/Ubuntu
RUN yum clean all && rm -rf /var/cache/yum/* # CentOS/RHEL
RUN apk add --no-cache package && rm -rf /var/cache/apk/* # Alpine
# 清理 npm 缓存
RUN npm install && npm cache clean --force
# 清理源码和构建文件
RUN go build -o app . && rm -rf /go/src
4. 使用多阶段构建
如前面的示例所示,使用多阶段构建可以只包含运行时需要的文件,而不包含构建工具和依赖项。
5. 合理使用 .dockerignore 文件
创建 .dockerignore 文件,排除不需要复制到镜像中的文件:
# 排除版本控制文件
.git
.gitignore
# 排除依赖目录
node_modules
vendor
# 排除构建输出目录
build
dist
# 排除测试文件
test
__tests__
*.test.js
# 排除环境配置文件
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# 排除文档
README.md
LICENSE
安全性最佳实践
1. 使用官方或经过验证的基础镜像
# 推荐:使用官方镜像
FROM node:14-alpine
# 不推荐:使用未知来源的镜像
FROM someuser/node:14
2. 避免以 root 用户运行应用
# 创建非 root 用户
RUN addgroup -g 1001 -S appuser && \
adduser -S appuser -u 1001
# 切换到非 root 用户
USER appuser
3. 定期更新基础镜像
# 使用特定版本的基础镜像,并定期更新
FROM node:14.17.0-alpine
# 而不是使用 latest 标签
FROM node:latest
4. 避免在镜像中存储敏感信息
# 不推荐:硬编码敏感信息
ENV API_KEY=secret123
# 推荐:使用环境变量或 Docker secrets
ENV API_KEY=${API_KEY}
5. 扫描镜像中的安全漏洞
使用工具如 Trivy、Clair 等扫描镜像中的安全漏洞:
# 使用 Trivy 扫描镜像
docker pull aquasec/trivy
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image my-app
实战:为常见应用编写 Dockerfile
1. Node.js 应用
# 使用多阶段构建
FROM node:14-alpine as deps
WORKDIR /app
COPY package*.json ./
RUN npm install --production
FROM node:14-alpine as builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN npm run build
FROM node:14-alpine as runner
WORKDIR /app
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S appuser -u 1001
# 复制构建结果和依赖
COPY --from=builder /app/build ./build
COPY --from=deps /app/node_modules ./node_modules
COPY package*.json ./
# 切换到非 root 用户
USER appuser
# 暴露端口
EXPOSE 3000
# 启动应用
CMD ["npm", "start"]
2. Python 应用
FROM python:3.9-alpine
WORKDIR /app
# 创建非 root 用户
RUN addgroup -g 1001 -S appuser && \
adduser -S appuser -u 1001
# 安装依赖
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 切换到非 root 用户
USER appuser
# 暴露端口
EXPOSE 8000
# 启动应用
CMD ["python", "app.py"]
3. Go 应用
FROM golang:1.16-alpine as builder
WORKDIR /app
COPY . .
RUN go mod download && \
go build -o app .
FROM alpine:3.14
WORKDIR /app
# 创建非 root 用户
RUN addgroup -g 1001 -S appuser && \
adduser -S appuser -u 1001
# 安装运行时依赖
RUN apk add --no-cache ca-certificates
# 复制可执行文件
COPY --from=builder /app/app ./
# 切换到非 root 用户
USER appuser
# 暴露端口
EXPOSE 8080
# 启动应用
CMD ["./app"]
4. Java 应用
FROM maven:3.8.4-openjdk-11-slim as builder
WORKDIR /app
COPY . .
RUN mvn clean package -DskipTests
FROM adoptopenjdk:11-jre-hotspot
WORKDIR /app
# 创建非 root 用户
RUN groupadd -g 1001 appuser && \
useradd -u 1001 -g appuser appuser
# 复制构建结果
COPY --from=builder /app/target/app.jar ./
# 切换到非 root 用户
USER appuser
# 暴露端口
EXPOSE 8080
# 启动应用
CMD ["java", "-jar", "app.jar"]
常见问题与解决方案
1. 构建速度慢
问题:docker build 命令执行缓慢。
解决方案:
- 使用 Docker 构建缓存:合理安排指令顺序,将不常变化的指令放在前面
- 使用多阶段构建:减少中间层的大小
- 配置镜像加速器:提高基础镜像的拉取速度
- 优化依赖安装:使用
--no-cache或类似选项减少缓存
2. 镜像大小过大
问题:构建的镜像体积过大,占用过多存储空间。
解决方案:
- 使用轻量级基础镜像:如 Alpine
- 最小化镜像层:合并多个 RUN 指令
- 清理不需要的文件:删除缓存、源码等
- 使用多阶段构建:只包含运行时需要的文件
3. 容器启动失败
问题:根据 Dockerfile 构建的镜像运行时失败。
解决方案:
- 检查 CMD/ENTRYPOINT 指令:确保命令正确
- 检查文件权限:确保应用文件有正确的执行权限
- 检查依赖项:确保所有运行时依赖都已安装
- 检查端口映射:确保容器端口与主机端口正确映射
4. 安全漏洞
问题:镜像扫描工具发现安全漏洞。
解决方案:
- 更新基础镜像:使用最新版本的基础镜像
- 更新依赖项:定期更新应用依赖
- 移除不必要的软件:减少攻击面
- 遵循安全最佳实践:如非 root 用户运行
最佳实践总结
1. 基础镜像选择
- 使用官方或经过验证的基础镜像
- 使用特定版本的镜像,避免使用 latest 标签
- 优先选择轻量级基础镜像,如 Alpine
2. 指令使用
- 合理安排指令顺序,利用 Docker 构建缓存
- 使用 COPY 而非 ADD(除非需要解压缩或下载功能)
- 使用 exec 格式的 CMD 和 ENTRYPOINT 指令
- 每个 RUN 指令只做一件事,并清理不需要的文件
3. 镜像优化
- 使用多阶段构建减少最终镜像大小
- 合理使用 .dockerignore 文件排除不需要的文件
- 最小化镜像层,合并多个 RUN 指令
- 清理包管理器缓存和构建文件
4. 安全性
- 避免以 root 用户运行应用
- 不存储敏感信息在镜像中
- 定期扫描镜像中的安全漏洞
- 遵循最小权限原则
5. 可维护性
- 添加注释说明 Dockerfile 的各个部分
- 使用环境变量管理配置
- 为镜像添加合理的标签
- 保持 Dockerfile 简洁明了
总结
编写高效、安全的 Dockerfile 是容器化应用的基础。通过本文的学习,你应该已经掌握了 Dockerfile 的核心指令、多阶段构建、镜像优化和安全最佳实践等知识,并能够为常见的应用类型编写专业的 Dockerfile。
作为一名运维工程师,掌握 Dockerfile 编写技巧不仅可以提高镜像构建效率,还可以确保应用的安全性和可靠性。在实际工作中,我们需要根据具体的应用场景和需求,灵活运用这些最佳实践,不断优化 Dockerfile,才能构建出高质量的容器镜像。