应直接使用 docker hub 官方 openjdk 镜像,优先选 openjdk:17-jdk-slim 或 21-jdk-slim;依赖提前分层缓存;必须用 exec java -jar 启动以支持优雅停机;务必配置时区、utf-8 编码和非 root 用户权限。

用 FROM openjdk 就够了,别自己编译
绝大多数 Java 应用根本不需要从源码构建 JDK 镜像。Docker Hub 官方 openjdk 镜像已预装 JRE/JDK、配置好 JAVA_HOME 和 PATH,支持多版本(8-jre-slim、17-jdk-slim、21-jdk-slim 等),底层是 Debian 或 Alpine,安全更新及时。
自己用 apt install openjdk-17-jdk 或下载 tar.gz 解压,不仅镜像体积大、构建慢,还容易漏掉 update-ca-certificates 或时区配置,导致 HTTPS 请求失败或日志时间错乱。
- 生产环境优先选
openjdk:17-jdk-slim(Debian 基础,兼容性好)或openjdk:21-jdk-slim(LTS 新版本) - 想更小体积可试
openjdk:17-jdk-slim-bullseye(明确 Debian 版本,避免未来基础镜像漂移) - Alpine 版(如
openjdk:17-jdk-alpine)慎用:glibc 依赖缺失会导致某些 JNI 库(如 Netty 的 native transport)直接报UnsatisfiedLinkError
COPY jar 包前先 ADD 依赖层做缓存优化
Docker 构建缓存机制对 COPY 敏感——只要 jar 文件变,后续所有层全失效。但应用代码改得勤,依赖(lib/ 下的 jar 或 BOOT-INF/lib/)其实变少。把依赖提前复制并单独分层,能显著提升 CI 构建速度。
以 Spring Boot fat jar 为例,它内部结构固定:BOOT-INF/lib/ 存依赖,BOOT-INF/classes/ 存业务代码。可以用 unzip -q your-app.jar 'BOOT-INF/lib/*' 提前解出依赖目录。
- 在
Dockerfile中先ADD解压出的BOOT-INF/lib/目录(注意用ADD不是COPY,支持自动解压 tar) - 再
COPY主 jar 包——这样改业务代码只触发最后一层重建 - 若用 Maven,可在打包阶段加
spring-boot-maven-plugin的layout=ZIP+requiresUnpack配置,生成可分层部署的结构
运行时别用 java -jar,改用 exec java 启动
直接写 java -jar app.jar 会让 JVM 进程成为 PID 1,无法正确响应 SIGTERM,容器 docker stop 会硬等 10 秒后发 SIGKILL,导致 Spring Boot 的优雅停机(context.close()、连接池关闭、Actuator shutdown endpoint)完全失效。
必须用 exec java -jar app.jar,让 JVM 替换当前 shell 进程,真正成为 PID 1 并接管信号。否则你会看到日志里没有 Shutting down ExecutorService,数据库连接泄漏、Kafka offset 未提交等问题随之而来。
- 检查是否生效:容器内执行
ps -o pid,ppid,comm,确认java进程的 PPID 是 0(即 PID 1) - Spring Boot 3.x 默认启用
systemd风格信号处理,但前提是 JVM 必须是 PID 1;否则需手动加-Dspring.lifecycle.timeout-per-shutdown-phase=30s - 别信网上“加
trap捕获信号再转发”的方案——PID 1 的 shell 对大多数信号默认忽略,不可靠
时区、编码、文件权限三个点不设就出事
Java 应用在容器里默认时区是 UTC,new Date()、SimpleDateFormat、数据库 timestamp 写入全错;LANG=C 导致中文日志乱码;非 root 用户启动时,/tmp 或挂载卷权限不足会卡在 java.io.tmpdir 初始化。
这些不是“高级配置”,是上线前必填项。漏一个,轻则日志看不懂,重则定时任务跑错时间、文件上传失败、JVM 启动直接抛 AccessDeniedException。
- 时区:在
Dockerfile加ENV TZ=Asia/Shanghai+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - 编码:加
ENV LANG=en_US.UTF-8(Debian)或ENV LANG=C.UTF-8(Alpine),避免file.encoding被识别为 ANSI_X3.4-1968 - 权限:用
useradd -r -u 1001 -g root javauser创建非 root 用户,chown -R javauser:root /app,最后USER javauser——别图省事用root
这些配置看着琐碎,但线上第一个告警往往就来自其中某一项没设。不是“可能出问题”,是“一定会”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











