Dockerfile 最佳实践
基础结构
基本 Dockerfile
# 使用官方基础镜像
FROM node:18-alpine
# 设置工作目录
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["node", "server.js"]
选择基础镜像
镜像大小对比
- 完整镜像
- Slim 镜像
- Alpine 镜像
- Distroless
# Ubuntu 完整镜像 (~77MB)
FROM ubuntu:22.04
RUN apt-get update && \
apt-get install -y nodejs npm && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Debian Slim 镜像 (~124MB)
FROM node:18-slim
# 包含基本的 Node.js 运行时
# 适合大多数应用
# Alpine 镜像 (~40MB)
FROM node:18-alpine
# 最小化的镜像
# 适合生产环境
# Distroless 镜像
FROM gcr.io/distroless/nodejs18
# 只包含应用和运行时依赖
# 没有 shell,最安全
COPY --from=builder /app .
常用基础镜像推荐
# Node.js
FROM node:18-alpine
# Python
FROM python:3.11-slim
# .NET
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
# Go
FROM golang:1.21-alpine AS builder
FROM alpine:3.18
# Java
FROM eclipse-temurin:17-jre-alpine
多阶段构建
.NET 应用示例
# 构建阶段
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# 复制项目文件
COPY ["MyApp/MyApp.csproj", "MyApp/"]
RUN dotnet restore "MyApp/MyApp.csproj"
# 复制所有文件并构建
COPY . .
WORKDIR "/src/MyApp"
RUN dotnet build "MyApp.csproj" -c Release -o /app/build
# 发布阶段
FROM build AS publish
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
# 运行阶段
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
COPY --from=publish /app/publish .
# 创建非 root 用户
RUN addgroup -g 1001 -S appuser && \
adduser -S appuser -u 1001
USER appuser
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"]
Node.js 应用示例
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
# 安装依赖
COPY package*.json ./
RUN npm ci
# 构建应用
COPY . .
RUN npm run build && \
npm prune --production
# 生产阶段
FROM node:18-alpine
WORKDIR /app
# 复制必要文件
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
# 非 root 用户
RUN addgroup -g 1001 nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs /app
USER nodejs
EXPOSE 3000
CMD ["node", "dist/main.js"]
Go 应用示例
# 构建阶段
FROM golang:1.21-alpine AS builder
WORKDIR /build
# 下载依赖
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.18
WORKDIR /app
# 安装必要的证书
RUN apk --no-cache add ca-certificates
# 复制编译后的二进制文件
COPY --from=builder /build/main .
# 非 root 用户
RUN addgroup -g 1001 appuser && \
adduser -D -u 1001 -G appuser appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
CMD ["./main"]
层缓存优化
合理安排 COPY 顺序
# ❌ 不好的做法 - 代码改变会重新安装依赖
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
# ✅ 好的做法 - 依赖不变时使用缓存
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
合并 RUN 命令
# ❌ 不好的做法 - 创建多个层
FROM alpine:3.18
RUN apk update
RUN apk add curl
RUN apk add git
RUN apk add vim
# ✅ 好的做法 - 减少层数
FROM alpine:3.18
RUN apk update && \
apk add --no-cache \
curl \
git \
vim && \
rm -rf /var/cache/apk/*
利用 BuildKit 缓存挂载
# syntax=docker/dockerfile:1
FROM node:18-alpine AS builder
WORKDIR /app
# 使用缓存挂载加速 npm install
RUN --mount=type=cache,target=/root/.npm \
npm ci
COPY . .
RUN npm run build
减小镜像体积
1. 清理缓存和临时文件
FROM node:18-alpine
WORKDIR /app
# 单个 RUN 命令中完成所有操作
RUN apk add --no-cache python3 make g++ && \
npm ci && \
npm run build && \
npm prune --production && \
apk del python3 make g++ && \
rm -rf /root/.npm /tmp/*
2. 使用 .dockerignore
# .dockerignore
node_modules
npm-debug.log
.git
.github
.gitignore
README.md
.env
.env.local
*.md
coverage
.vscode
.idea
dist
build
3. 只复制必要文件
# ❌ 不好的做法
COPY . .
# ✅ 好的做法
COPY package*.json ./
RUN npm ci
COPY src ./src
COPY public ./public
4. 使用多阶段构建
# 完整示例
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 只保留生产文件
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
CMD ["node", "dist/main.js"]
安全最佳实践
1. 使用非 root 用户
FROM node:18-alpine
WORKDIR /app
# 安装依赖(需要 root)
COPY package*.json ./
RUN npm ci
# 复制应用文件
COPY . .
# 创建用户并切换
RUN addgroup -g 1001 nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs /app
USER nodejs
EXPOSE 3000
CMD ["node", "server.js"]
2. 不在镜像中存储敏感信息
# ❌ 不好的做法
FROM node:18-alpine
COPY .env .
RUN echo "API_KEY=secret123" > .env
# ✅ 好的做法 - 使用环境变量
FROM node:18-alpine
ENV NODE_ENV=production
# 在运行时通过 -e 或 docker-compose 传递敏感信息
3. 使用固定版本标签
# ❌ 不好的做法
FROM node:latest
# ✅ 好的做法
FROM node:18.17.1-alpine3.18
4. 扫描漏洞
# 使用 Docker Scout
docker scout quickview myapp:latest
# 使用 Trivy
trivy image myapp:latest
# 使用 Snyk
snyk container test myapp:latest
5. 最小化攻击面
FROM node:18-alpine
# 只安装必要的包
RUN apk add --no-cache tini
# 使用 tini 作为 init 进程
ENTRYPOINT ["/sbin/tini", "--"]
# 只读文件系统
# docker run --read-only --tmpfs /tmp myapp
USER nodejs
CMD ["node", "server.js"]
健康检查
基本健康检查
FROM node:18-alpine
WORKDIR /app
COPY . .
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
CMD ["node", "server.js"]
HTTP 健康检查
FROM node:18-alpine
# 安装 curl
RUN apk add --no-cache curl
WORKDIR /app
COPY . .
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["node", "server.js"]
自定义健康检查脚本
FROM node:18-alpine
WORKDIR /app
COPY . .
COPY healthcheck.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/healthcheck.sh
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD ["/usr/local/bin/healthcheck.sh"]
CMD ["node", "server.js"]
# healthcheck.sh
#!/bin/sh
response=$(wget --no-verbose --tries=1 --spider http://localhost:3000/health 2>&1)
if [ $? -eq 0 ]; then
exit 0
else
exit 1
fi
环境变量和参数
构建参数 (ARG)
# 定义构建参数
ARG NODE_VERSION=18
ARG APP_VERSION=1.0.0
FROM node:${NODE_VERSION}-alpine
WORKDIR /app
# ARG 可以在构建时传递
LABEL version="${APP_VERSION}"
COPY . .
RUN npm ci
CMD ["node", "server.js"]
# 构建时传递参数
docker build --build-arg NODE_VERSION=20 --build-arg APP_VERSION=2.0.0 -t myapp .
环境变量 (ENV)
FROM node:18-alpine
# 设置环境变量
ENV NODE_ENV=production \
PORT=3000 \
LOG_LEVEL=info
WORKDIR /app
COPY . .
EXPOSE ${PORT}
CMD ["node", "server.js"]
运行时环境变量
# docker run 传递
docker run -e NODE_ENV=development -e PORT=8080 myapp
# docker-compose.yml
version: '3.8'
services:
app:
image: myapp
environment:
NODE_ENV: production
PORT: 3000
env_file:
- .env
实用技巧
1. 条件复制文件
# 使用通配符复制可选文件
COPY package*.json ./
COPY *.config.js ./
2. 使用 ONBUILD 指令
# 基础镜像 Dockerfile
FROM node:18-alpine
WORKDIR /app
ONBUILD COPY package*.json ./
ONBUILD RUN npm ci
ONBUILD COPY . .
# 使用基础镜像
FROM myorg/node-base
EXPOSE 3000
CMD ["node", "server.js"]
3. 多平台构建
# 创建构建器
docker buildx create --name mybuilder --use
# 构建多平台镜像
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .
# 构建并推送
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .
4. 使用 heredoc
# syntax=docker/dockerfile:1
FROM alpine:3.18
# 使用 heredoc 创建文件
RUN <<EOF
apk update
apk add nginx
mkdir -p /run/nginx
EOF
# 创建配置文件
COPY <<EOF /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
events {
worker_connections 1024;
}
http {
include mime.types;
server {
listen 80;
root /var/www/html;
}
}
EOF
CMD ["nginx", "-g", "daemon off;"]
5. 使用缓存挂载
# syntax=docker/dockerfile:1
FROM golang:1.21-alpine AS builder
WORKDIR /build
# 使用缓存挂载加速构建
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -o app .
FROM alpine:3.18
COPY --from=builder /build/app /app
CMD ["/app"]
常见场景示例
静态网站 (Nginx)
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Python Flask 应用
FROM python:3.11-slim
WORKDIR /app
# 安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用
COPY . .
# 非 root 用户
RUN useradd -m -u 1001 appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 5000
# 使用 gunicorn 运行
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]
Java Spring Boot 应用
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /app
# 复制 Maven 文件
COPY pom.xml .
COPY src ./src
# 构建
RUN ./mvnw clean package -DskipTests
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 复制 jar 文件
COPY --from=builder /app/target/*.jar app.jar
# 非 root 用户
RUN addgroup -g 1001 spring && \
adduser -D -u 1001 -G spring spring
USER spring
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
调试技巧
1. 在特定阶段停止
# 在构建阶段停止以检查
FROM node:18-alpine AS debug
WORKDIR /app
COPY package*.json ./
RUN npm ci
# 构建时: docker build --target debug -t myapp:debug .
2. 保留构建缓存
# 不使用缓存
docker build --no-cache -t myapp .
# 使用特定缓存
docker build --cache-from myapp:latest -t myapp .
3. 查看镜像层
# 查看镜像历史
docker history myapp:latest
# 查看详细信息
docker inspect myapp:latest
4. 导出文件系统
# 导出镜像文件系统
docker save myapp:latest -o myapp.tar
tar -xvf myapp.tar
# 或使用 dive 工具
dive myapp:latest
性能优化
1. 并行构建
# syntax=docker/dockerfile:1
FROM node:18-alpine
WORKDIR /app
# 并行复制和安装
COPY package*.json ./
RUN --network=none npm ci
# 使用 BuildKit 并行
RUN --mount=type=cache,target=/root/.npm \
npm ci
2. 减少上下文大小
# 检查构建上下文大小
du -sh .
# 使用 .dockerignore
# 只构建必要文件
docker build -f Dockerfile -t myapp:latest .
3. 使用本地缓存
# 启用 BuildKit
export DOCKER_BUILDKIT=1
# 使用内联缓存
docker build --build-arg BUILDKIT_INLINE_CACHE=1 -t myapp .
最佳实践清单
Dockerfile 最佳实践