Docker 容器化
本教程共 48 篇 · 第 43 篇 · 更新于 2026-08-13 · 约 4 分钟阅读
本节目标:会写 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 appuser加USER 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内存参数。