首页 / Spring Boot 入门教程 / Docker 容器化

Spring Boot 入门教程

Docker 容器化

本教程共 48 篇 · 第 43 篇 · 更新于 2026-08-13 · 约 4 分钟阅读

Spring BootDockerDockerfile容器化分层镜像多阶段构建部署

本节目标:会写 Spring Boot 的 Dockerfile,理解分层镜像的原理,掌握多阶段构建和镜像优化方法。

为什么容器化

java -jar 能跑,但换个环境就未必。服务器上没有 JDK 21?应用起不来。JDK 版本不对?行为诡异。

Docker 把应用和运行环境打包成一个镜像,到处运行结果一致。开发、测试、生产,同一份镜像,消灭「在我机器上是好的」。

第一个 Dockerfile

在项目根目录创建 Dockerfile

# 运行阶段的基础镜像:带 JRE 21 的精简 Linux
FROM eclipse-temurin:21-jre

# 容器内的工作目录
WORKDIR /app

# 把本地打好的 Jar 复制进镜像,改名为 app.jar
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar

# 容器启动时执行的命令
ENTRYPOINT ["java", "-jar", "app.jar"]

构建并运行:

# 先打出 Jar
mvn clean package

# 构建镜像,demo:1.0 是镜像名和标签
docker build -t demo:1.0 .

# 运行:宿主 8080 端口映射到容器 8080
docker run -d -p 8080:8080 demo:1.0

访问 http://localhost:8080,应用在容器里跑起来了。

Note

eclipse-temurin 是 Eclipse 基金会维护的开源 JDK 镜像,社区主流选择。老教程里的 openjdk:8-jdk-alpine 已停止维护,别用。基础镜像用 21 LTS 完全没问题,官方示例基于 25。

问题:镜像太大、构建太慢

上面的 Dockerfile 能跑,但不合格。两个痛点:

  • 镜像大:基础镜像带一整套 JRE,应用本身才几十 MB,镜像却几百 MB。
  • 缓存差COPY 的是整个胖 Jar。改一行代码重新打包,COPY 层缓存全失效,整个 Jar 重新复制进镜像。

这两个问题,靠分层解决。

分层镜像:只变该变的层

Spring Boot 支持把 Jar 按「变化频率」拆成多个层。打包时加配置:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <layers>
                    <enabled>true</enabled>
                </layers>
            </configuration>
        </plugin>
    </plugins>
</build>

重新打包后,Jar 里多了一个 layers.idx 索引文件,把内容分成四层:

dependencies          # 正式发布的第三方依赖,很少变
spring-boot-loader    # Spring Boot 启动器
snapshot-dependencies # SNAPSHOT 版本依赖
application           # 你自己的代码和资源,最常变

查看层列表:

java -Djarmode=tools -jar target/demo-0.0.1-SNAPSHOT.jar list-layers

分层 Dockerfile:多阶段构建

先解压分层,再按层 COPY,让 Docker 的缓存按层复用:

# ---- 第一阶段:构建层 ----
FROM eclipse-temurin:21-jre AS builder
WORKDIR /builder

ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar

# 按 layers.idx 把 Jar 解压成目录
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted

# ---- 第二阶段:运行层 ----
FROM eclipse-temurin:21-jre
WORKDIR /application

# 按层依次 COPY:依赖层几乎不变,Docker 直接命中缓存
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./

ENTRYPOINT ["java", "-jar", "application.jar"]

这个 Dockerfile 用了多阶段构建:第一阶段负责解压,产物只留需要的目录;第二阶段从零组装运行镜像。基础镜像只用一次,COPY 变成四行,每层独立缓存。

效果:改自己的代码,只有最后一层 application 重新 COPY,前三层全部命中 Docker 缓存,构建秒级完成。镜像体积也更小,因为层与层之间共享基础。

Tip

extract --layers 会把解压后的 Jar 放在 extracted/ 下,四个子目录分别对应 layers.idx 的四层。--destination 指定解压位置。用 java -Djarmode=tools -jar application.jar help extract 可以查看全部参数。

构建与运行命令回顾

# 构建镜像(Jar 已在 target/ 下)
docker build -t demo:1.0 .

# 查看镜像
docker images

# 后台运行并映射端口
docker run -d --name demo-app -p 8080:8080 demo:1.0

# 查看日志
docker logs -f demo-app

# 进入容器排查
docker exec -it demo-app sh

# 停止并删除容器
docker stop demo-app && docker rm demo-app
Warning

容器里的应用默认以 root 用户运行,有安全隐患。生产环境建议在 Dockerfile 里创建普通用户:RUN useradd -m appuserUSER appuser。涉及文件权限时注意挂载卷的属主问题。

非 root 用户运行(生产建议)

FROM eclipse-temurin:21-jre
WORKDIR /application

COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./

# 创建普通用户并切换
RUN useradd -m appuser
USER appuser

ENTRYPOINT ["java", "-jar", "application.jar"]

内存参数:容器里的 JVM

容器有内存上限(比如 docker run -m 512m),但 JVM 默认按宿主机内存算堆大小,可能超出限制被 OOM 杀掉。加两个参数让 JVM 感知容器限制:

ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "application.jar"]

-XX:MaxRAMPercentage=75 表示堆最多占容器可用内存的 75%,留一部分给 JVM 自身和线程栈。容器化部署标配,第 45 章还会细讲资源优化。

进阶:官方推荐的基础镜像

Spring Boot 官方文档示例用的是 bellsoft/liberica-openjre-debian 系列,配合 CDS / AOT 缓存做启动优化:

FROM bellsoft/liberica-openjre-debian:25-cds AS builder
# ... 解压分层 ...
RUN java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar

ENTRYPOINT ["java", "-XX:AOTCache=app.aot", "-jar", "application.jar"]

思路是:构建时先跑一次训练,把类加载和编译结果存成缓存文件,运行时直接加载,启动更快。Java 25+ 用 AOT 缓存,Java 24 及以下用 CDS(-XX:ArchiveClassesAtExit / -XX:SharedArchiveFile)。入门阶段了解即可,需要极致启动速度时再上。

Tip

不想手写 Dockerfile?Spring Boot 支持直接用 Maven 插件构建镜像(Buildpacks 方案),一条 mvn spring-boot:build-image 搞定,下一章详细讲。

小结

  • Dockerfile 三步:选基础镜像、COPY Jar、写 ENTRYPOINT。
  • 开启分层打包(layers.idx),用 jarmode extract --layers 按层 COPY,缓存复用、镜像更小。
  • 多阶段构建让构建阶段和运行阶段分离。
  • 生产环境用非 root 用户、加 -XX:MaxRAMPercentage 内存参数。