将 Spring Boot 应用容器化是现代部署的基本功。但默认的 Dockerfile 打包出来的镜像体积大、启动慢、安全性差。本文介绍如何构建生产级的 Spring Boot Docker 镜像。
基础 Dockerfile 编写
项目结构
1 2 3 4
| spring-boot-app/ ├── src/ ├── pom.xml └── Dockerfile
|
基础版本
1 2 3 4 5 6 7 8 9 10
| FROM openjdk:17
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
|
问题:
- 镜像体积大(openjdk:17 约 500MB)
- 没有指定 JRE/JDK 区别
- 每次代码变更都需要 COPY 全部依赖
多阶段构建原理
多阶段构建使用多个 FROM 语句,前一阶段的产物可以复制到后一阶段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml . RUN mvn dependency:go-offline
COPY src ./src RUN mvn package -DskipTests
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
|
好处:
- 最终镜像只包含运行时需要的内容
- 不包含 Maven、源代码等
- 镜像体积大幅减小
镜像大小优化技巧
1. 使用 Alpine 基础镜像
1 2 3 4 5 6 7
| FROM eclipse-temurin:17-jre-alpine
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai
|
2. 使用 distroless 镜像(Google)
1 2 3 4 5 6
| FROM gcr.io/distroless/java17-debian11
COPY target/*.jar app.jar
ENTRYPOINT ["app.jar"]
|
distroless 镜像特点:
- 只包含运行时需要的内容
- 没有 shell、没有包管理器
- 更安全(攻击面小)
3. 只复制必要文件
1 2 3 4 5
| COPY . .
COPY target/*.jar app.jar
|
4. 利用层缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
|
完整优化版本
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 39 40 41 42 43 44 45 46 47 48
|
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml . RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
FROM eclipse-temurin:17-jre-alpine
RUN apk add --no-cache tzdata
RUN addgroup -g 1001 -S appgroup && \ adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
|
.dockerignore 配置
.dockerignore 防止不需要的文件进入镜像:
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 39 40 41 42 43
|
.git .gitignore
.idea/ *.iml .vscode/
target/ build/ *.class *.jar
src/test/ *.test *.spec
*.md docs/
Dockerfile docker-compose*.yml .docker/
*.log logs/
.env .env.* *.env
.DS_Store Thumbs.db
|
注意:.dockerignore 放在项目根目录,不是 Dockerfile 同级。
Jib 无需 Dockerfile
Google Jib 是更现代的构建方案,不需要写 Dockerfile:
Maven 插件
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
| <plugin> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-maven-plugin</artifactId> <version>3.4.0</version> <configuration> <from> <image>eclipse-temurin:17-jre-alpine</image> </from> <to> <image>registry.example.com/myapp:${project.version}</image> <tags> <tag>latest</tag> <tag>${maven.build.timestamp}</tag> </tags> </to> <container> <jvmFlags> <jvmFlag>-XX:+UseContainerSupport</jvmFlag> <jvmFlag>-XX:MaxRAMPercentage=75.0</jvmFlag> </jvmFlags> <ports> <port>8080</port> </port> <creationTime>USE_CURRENT_TIMESTAMP</creationTime> </container> </configuration> </plugin>
|
使用
1 2 3 4 5 6 7 8
| mvn compile jib:build
mvn compile jib:dockerBuild
mvn jib:log -Djib.container.command=sleep,600
|
Jib 的优势:
- 快:利用层缓存,只上传变化的层
- 简单:不需要 Dockerfile
- 安全:不需要 Docker daemon 权限
实战:生产级镜像构建
完整项目结构
1 2 3 4 5 6 7
| spring-boot-app/ ├── src/ ├── pom.xml ├── Dockerfile ├── .dockerignore └── docker/ └── entrypoint.sh
|
entrypoint.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #!/bin/sh
JAVA_OPTS="-XX:+UseContainerSupport" JAVA_OPTS="$JAVA_OPTS -XX:MaxRAMPercentage=75.0" JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC" JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError"
JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:file=/app/logs/gc.log"
SPRING_OPTS="$SPRING_OPTS --server.tomcat.threads.max=200" SPRING_OPTS="$SPRING_OPTS --server.tomcat.threads.min-spare=10"
echo "Starting application..." echo "Java options: $JAVA_OPTS" echo "Spring options: $SPRING_OPTS"
exec java $JAVA_OPTS $SPRING_OPTS -jar /app/app.jar "$@"
|
Dockerfile(生产级)
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml . RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B && \ cd target/dependency && \ mv ../*.jar app.jar
FROM eclipse-temurin:17-jre-alpine
RUN apk upgrade --no-cache && \ apk add --no-cache wget ca-certificates
RUN mkdir -p /app/logs /app/config && \ addgroup -g 1001 -S appgroup && \ adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /build/target/dependency/*.jar app.jar
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["sh", "/app/entrypoint.sh"]
|
docker-compose.yml
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
| version: '3.8'
services: app: build: context: . dockerfile: Dockerfile image: myapp:latest container_name: myapp ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod - JAVA_OPTS=-Xmx512m volumes: - ./logs:/app/logs - ./config:/app/config:ro restart: unless-stopped healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/actuator/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s deploy: resources: limits: memory: 1G reservations: memory: 512M
|
总结
Spring Boot Docker 镜像优化要点:
| 优化项 |
方案 |
效果 |
| 基础镜像 |
eclipse-temurin:17-jre-alpine |
~180MB |
| 多阶段构建 |
builder + runtime |
去掉 Maven、源码 |
| 层缓存 |
pom.xml 优先 COPY |
构建加速 |
| 非 root 用户 |
adduser |
安全性 |
| 健康检查 |
actuator + HEALTHCHECK |
可观测性 |
| JVM 容器支持 |
-XX:+UseContainerSupport |
合理使用内存 |
推荐构建方案:
- 简单项目:使用 Jib Maven 插件
- 复杂项目:手写优化 Dockerfile
- 生产环境:多阶段构建 + 非 root + 健康检查