引言

编写高质量的Dockerfile是容器化的关键。本文介绍Dockerfile的最佳实践。

一、基础优化

1.1 选择合适的基础镜像

1
2
3
4
5
6
7
8
# ✗ 不好:使用完整镜像
FROM ubuntu:latest

# ✓ 好:使用精简镜像
FROM alpine:3.18

# ✓ 更好:使用专用镜像
FROM node:18-alpine

1.2 合并RUN指令

1
2
3
4
5
6
7
8
9
10
# ✗ 不好:多个RUN层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

# ✓ 好:合并为一个RUN
RUN apt-get update && \
apt-get install -y curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

1.3 使用.dockerignore

1
2
3
4
5
6
7
node_modules
npm-debug.log
.git
.gitignore
.env
*.md
.DS_Store

二、多阶段构建

2.1 Java应用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 构建阶段
FROM maven:3.8-jdk-11 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

# 运行阶段
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=builder /build/target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

2.2 Node.js应用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /build
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# 运行阶段
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /build/dist ./dist
COPY --from=builder /build/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/main.js"]

三、安全加固

3.1 使用非root用户

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM node:18-alpine

# 创建应用用户
RUN addgroup -g 1001 appgroup && \
adduser -D -u 1001 -G appgroup appuser

WORKDIR /app
COPY --chown=appuser:appgroup . .

# 切换到非root用户
USER appuser

CMD ["node", "index.js"]

3.2 最小化暴露

1
2
3
4
5
6
7
8
# 只暴露必要的端口
EXPOSE 8080

# 不要安装不必要的包
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl && \
apt-get clean

四、性能优化

4.1 利用构建缓存

1
2
3
4
5
6
# ✓ 好:先复制依赖文件
COPY package*.json ./
RUN npm install

# 再复制源代码
COPY . .

4.2 优化层顺序

1
2
3
4
5
6
7
8
9
10
11
12
# 不常变的层放在前面
FROM node:18-alpine
WORKDIR /app

# 依赖(不常变)
COPY package*.json ./
RUN npm install

# 源代码(经常变)
COPY . .

CMD ["node", "index.js"]

五、健康检查

1
2
3
4
5
6
7
# HTTP服务
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1

# TCP服务
HEALTHCHECK --interval=30s --timeout=3s \
CMD nc -z localhost 8080 || exit 1

六、完整示例

6.1 Spring Boot应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 构建阶段
FROM maven:3.8-jdk-11-slim AS builder
WORKDIR /build

# 缓存依赖
COPY pom.xml .
RUN mvn dependency:go-offline -B

# 构建应用
COPY src ./src
RUN mvn package -DskipTests -B

# 运行阶段
FROM openjdk:11-jre-slim
LABEL maintainer="your@email.com"

# 创建非root用户
RUN groupadd -r spring && useradd -r -g spring spring

WORKDIR /app

# 复制jar包
COPY --from=builder --chown=spring:spring \
/build/target/*.jar app.jar

# 切换用户
USER spring

# 环境变量
ENV JAVA_OPTS="-Xms256m -Xmx512m"

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

EXPOSE 8080

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

6.2 前端应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /build

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# 运行阶段
FROM nginx:alpine

# 复制nginx配置
COPY nginx.conf /etc/nginx/nginx.conf

# 复制构建产物
COPY --from=builder /build/dist /usr/share/nginx/html

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

七、常用技巧

7.1 ARG与ENV

1
2
3
4
5
6
7
8
9
10
# 构建时变量
ARG VERSION=1.0.0
ARG BUILD_DATE

# 运行时环境变量
ENV APP_VERSION=${VERSION}
ENV BUILD_DATE=${BUILD_DATE}

LABEL version="${VERSION}" \
build-date="${BUILD_DATE}"

7.2 ENTRYPOINT vs CMD

1
2
3
4
5
6
7
# ENTRYPOINT:固定的执行命令
ENTRYPOINT ["java", "-jar"]

# CMD:可被覆盖的参数
CMD ["app.jar"]

# 运行:docker run myapp app.jar --spring.profiles.active=prod

总结

Dockerfile最佳实践:

  1. 选择合适的基础镜像:轻量级优先
  2. 多阶段构建:减小镜像体积
  3. 安全加固:非root用户、最小化暴露
  4. 利用缓存:优化构建速度
  5. 健康检查:保证容器可用性

参考资料