Docker 镜像构建最佳实践
本章将深入讲解 Docker 镜像构建的最佳实践,包括 Dockerfile 优化、构建性能提升、安全配置等关键技巧。
镜像构建原则
核心设计原则
- 最小化原则:只包含必要的组件和文件
- 单一职责:每个容器只运行一个主要服务
- 不可变性:镜像构建后不应修改
- 可重现性:相同的构建环境产生相同的镜像
- 安全性:遵循安全最佳实践
镜像分层策略
dockerfile
# 良好的分层示例
FROM node:16-alpine AS base
# 基础层:很少变化
WORKDIR /app
# 工作目录层:很少变化
COPY package*.json ./
# 依赖文件层:偶尔变化
RUN npm ci --only=production
# 依赖安装层:偶尔变化
COPY . .
# 应用代码层:经常变化
RUN npm run build
# 构建层:经常变化Dockerfile 优化技巧
基础镜像选择
dockerfile
# ❌ 避免使用过大的基础镜像
FROM ubuntu:20.04
# ✅ 优先使用 Alpine 版本
FROM node:16-alpine
# ✅ 使用 distroless 镜像(生产环境)
FROM gcr.io/distroless/nodejs:16
# ✅ 使用 scratch(静态编译程序)
FROM scratch
COPY myapp /
ENTRYPOINT ["/myapp"]指令优化
dockerfile
# ❌ 不好的做法 - 多个 RUN 指令
FROM alpine:3.16
RUN apk update
RUN apk add curl
RUN apk add vim
RUN rm -rf /var/cache/apk/*
# ✅ 好的做法 - 合并 RUN 指令
FROM alpine:3.16
RUN apk update && \
apk add --no-cache curl vim && \
rm -rf /var/cache/apk/*
# ✅ 更好的做法 - 使用 heredoc
FROM alpine:3.16
RUN <<EOF
apk update
apk add --no-cache curl vim
rm -rf /var/cache/apk/*
EOF缓存优化
dockerfile
# ✅ 优化构建缓存的 Dockerfile
FROM node:16-alpine
WORKDIR /app
# 1. 先复制依赖文件(变化频率低)
COPY package*.json ./
# 2. 安装依赖(利用缓存)
RUN npm ci --only=production && npm cache clean --force
# 3. 后复制源代码(变化频率高)
COPY . .
# 4. 构建应用
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]文件复制优化
dockerfile
# ❌ 复制不必要的文件
COPY . /app
# ✅ 使用 .dockerignore 排除文件
# .dockerignore 内容:
# node_modules
# .git
# *.log
# .env
# ✅ 精确复制需要的文件
COPY package*.json ./
COPY src/ ./src/
COPY public/ ./public/
# ✅ 使用通配符
COPY *.json ./
COPY src/ ./src/多阶段构建
基本多阶段构建
dockerfile
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM node:16-alpine AS production
WORKDIR /app
# 只复制必要文件
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]复杂多阶段构建
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 builder AS tester
RUN npm run test
RUN npm run lint
# 生产阶段
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
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
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]构建特定阶段
bash
# 只构建到测试阶段
docker build --target tester -t myapp:test .
# 构建生产镜像
docker build --target runner -t myapp:prod .
# 并行构建多个阶段
docker build --target deps -t myapp:deps . &
docker build --target builder -t myapp:builder . &
wait安全最佳实践
用户和权限
dockerfile
# ✅ 创建和使用非 root 用户
FROM alpine:3.16
# 创建用户
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
# 设置工作目录权限
WORKDIR /app
RUN chown appuser:appgroup /app
# 复制文件并设置权限
COPY --chown=appuser:appgroup . .
# 切换到非 root 用户
USER appuser
# 设置只读根文件系统
# 运行时使用: docker run --read-only myapp最小权限原则
dockerfile
FROM alpine:3.16
# 只安装必要的包
RUN apk add --no-cache \
ca-certificates \
&& rm -rf /var/cache/apk/*
# 删除不必要的工具
RUN apk del --purge \
wget \
curl
# 设置安全的文件权限
COPY --chmod=755 app /usr/local/bin/app
COPY --chmod=644 config.json /etc/app/config.json
USER 1001:1001
ENTRYPOINT ["/usr/local/bin/app"]秘密信息处理
dockerfile
# ❌ 不要在镜像中硬编码秘密
ENV API_KEY=secret123
COPY secret.key /app/
# ✅ 使用构建时参数
ARG API_URL
ENV API_URL=${API_URL}
# ✅ 使用多阶段构建处理秘密
FROM alpine AS secrets
ARG SECRET_KEY
RUN echo "$SECRET_KEY" > /tmp/secret
FROM alpine AS final
RUN --mount=from=secrets,source=/tmp/secret,target=/secret \
process_secret < /secret性能优化
镜像大小优化
dockerfile
# Python 应用优化示例
FROM python:3.9-slim AS builder
# 安装构建依赖
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
gcc \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# 生产阶段
FROM python:3.9-slim
# 只复制必要的 Python 包
COPY --from=builder /root/.local /root/.local
# 确保脚本在 PATH 中
ENV PATH=/root/.local/bin:$PATH
WORKDIR /app
COPY . .
# 创建非 root 用户
RUN useradd --create-home --shell /bin/bash app && \
chown -R app:app /app
USER app
CMD ["python", "app.py"]构建缓存优化
dockerfile
# Go 应用构建优化
FROM golang:1.19-alpine AS builder
WORKDIR /app
# 利用 Go modules 缓存
COPY go.mod go.sum ./
RUN go mod download
# 复制源代码
COPY . .
# 构建应用
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# 最小运行时镜像
FROM scratch
# 复制 CA 证书
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# 复制二进制文件
COPY --from=builder /app/main /main
EXPOSE 8080
ENTRYPOINT ["/main"]BuildKit 优化
dockerfile
# syntax=docker/dockerfile:1
FROM node:16-alpine
WORKDIR /app
# 使用缓存挂载
RUN --mount=type=cache,target=/root/.npm \
npm install -g npm@latest
# 使用绑定挂载
RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \
npm ci --only=production
COPY . .
CMD ["npm", "start"]特定语言优化
Node.js 应用
dockerfile
FROM node:16-alpine AS base
# 安装 dumb-init
RUN apk add --no-cache dumb-init
# 创建用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
FROM base AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM base AS runtime
WORKDIR /app
ENV NODE_ENV production
COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=build --chown=nextjs:nodejs /app/dist ./dist
COPY --from=build --chown=nextjs:nodejs /app/package.json ./package.json
USER nextjs
EXPOSE 3000
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/index.js"]Python 应用
dockerfile
FROM python:3.9-slim AS base
# 设置 Python 环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
FROM base AS builder
# 安装构建依赖
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
FROM base AS runtime
# 复制 Python 包
COPY --from=builder /root/.local /root/.local
# 确保用户安装的包在 PATH 中
ENV PATH=/root/.local/bin:$PATH
# 创建用户
RUN useradd --create-home --shell /bin/bash app
WORKDIR /app
COPY --chown=app:app . .
USER app
EXPOSE 8000
CMD ["python", "app.py"]Java 应用
dockerfile
FROM openjdk:11-jdk-slim AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
# 构建应用
RUN ./mvnw clean package -DskipTests
FROM openjdk:11-jre-slim AS runtime
# 创建用户
RUN useradd --create-home --shell /bin/bash app
WORKDIR /app
# 复制 JAR 文件
COPY --from=builder --chown=app:app /app/target/*.jar app.jar
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]构建工具和技巧
使用 BuildKit
bash
# 启用 BuildKit
export DOCKER_BUILDKIT=1
# 或在构建时启用
DOCKER_BUILDKIT=1 docker build -t myapp .
# 使用 buildx
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 -t myapp .构建参数和变量
dockerfile
# 定义构建参数
ARG NODE_VERSION=16
ARG BUILD_DATE
ARG VERSION
FROM node:${NODE_VERSION}-alpine
# 设置标签
LABEL build-date=${BUILD_DATE}
LABEL version=${VERSION}
# 使用构建参数
ARG API_URL=https://api.example.com
ENV API_URL=${API_URL}
# 条件构建
ARG ENVIRONMENT=production
RUN if [ "$ENVIRONMENT" = "development" ]; then \
npm install; \
else \
npm ci --only=production; \
fibash
# 传递构建参数
docker build \
--build-arg NODE_VERSION=18 \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg VERSION=1.0.0 \
-t myapp:1.0.0 .构建脚本
bash
#!/bin/bash
# build.sh
set -e
# 配置变量
IMAGE_NAME="myapp"
VERSION=${1:-latest}
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
GIT_COMMIT=$(git rev-parse --short HEAD)
echo "构建镜像: $IMAGE_NAME:$VERSION"
# 构建镜像
docker build \
--build-arg VERSION=$VERSION \
--build-arg BUILD_DATE=$BUILD_DATE \
--build-arg GIT_COMMIT=$GIT_COMMIT \
--tag $IMAGE_NAME:$VERSION \
--tag $IMAGE_NAME:latest \
.
# 运行测试
echo "运行测试..."
docker run --rm $IMAGE_NAME:$VERSION npm test
# 安全扫描
echo "执行安全扫描..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image $IMAGE_NAME:$VERSION
echo "构建完成: $IMAGE_NAME:$VERSION"镜像标签策略
语义化版本
bash
# 版本标签策略
docker build -t myapp:1.2.3 . # 具体版本
docker build -t myapp:1.2 . # 次版本
docker build -t myapp:1 . # 主版本
docker build -t myapp:latest . # 最新版本
# Git 标签策略
docker build -t myapp:$(git describe --tags) .
docker build -t myapp:$(git rev-parse --short HEAD) .环境标签
bash
# 环境相关标签
docker build -t myapp:dev .
docker build -t myapp:staging .
docker build -t myapp:prod .
# 带时间戳的标签
docker build -t myapp:$(date +%Y%m%d-%H%M%S) .质量检查
Dockerfile Linting
bash
# 使用 hadolint 检查 Dockerfile
docker run --rm -i hadolint/hadolint < Dockerfile
# 或安装本地版本
brew install hadolint
hadolint Dockerfile镜像扫描
bash
# 使用 Trivy 扫描漏洞
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image myapp:latest
# 使用 Clair 扫描
docker run -d --name clair-db postgres:latest
docker run -d --name clair --link clair-db:postgres \
quay.io/coreos/clair:latest
# 使用 Docker Bench Security
git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
sudo sh docker-bench-security.sh自动化测试
yaml
# .github/workflows/docker.yml
name: Docker Build and Test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t myapp:test .
- name: Run tests
run: docker run --rm myapp:test npm test
- name: Security scan
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image myapp:test
- name: Push to registry
if: github.ref == 'refs/heads/main'
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker tag myapp:test myapp:latest
docker push myapp:latest本章小结
本章详细介绍了 Docker 镜像构建的最佳实践:
关键要点:
- 优化原则:最小化、单一职责、安全性
- Dockerfile 优化:合并指令、缓存利用、文件复制
- 多阶段构建:分离构建和运行环境
- 安全实践:非 root 用户、最小权限、秘密管理
- 性能优化:镜像大小、构建速度、缓存策略
- 质量保证:代码检查、安全扫描、自动化测试
最佳实践总结:
- 选择合适的基础镜像
- 优化 Dockerfile 指令顺序
- 使用多阶段构建
- 实施安全配置
- 建立质量检查流程
- 自动化构建和测试
掌握这些最佳实践将帮助你构建更小、更安全、更高效的 Docker 镜像。