引言
在云原生时代,容器化(containerization) 已成为现代应用部署的标准范式。docker 凭借其轻量、可移植、一致性的优势,彻底改变了软件交付流程。而作为 java 生态中最主流的构建工具,maven 如何与 docker 无缝集成,高效地将 spring boot 或普通 java 应用打包为 docker 镜像,是每一位 java 开发者必须掌握的核心技能。
本文将带你从零开始,深入实战 使用 maven 构建 java 项目并生成 docker 镜像的完整流程。我们将覆盖:
- 传统手动构建镜像的痛点
- 使用 jib 插件 实现“零 dockerfile”构建
- 使用 dockerfile + maven resources 插件 精细控制镜像内容
- 多阶段构建(multi-stage build)优化镜像大小
- 镜像标签策略、推送私有仓库、ci/cd 集成
- 安全最佳实践与性能调优
全文包含大量可运行代码示例、mermaid 架构图、真实外链资源(均经验证可访问),助你打造生产级 java 容器化流水线。
为什么需要将 java 项目容器化?
尽管 java 应用“一次编写,到处运行”,但在实际部署中仍面临诸多挑战:
- 环境差异:开发机(macos)、测试服务器(ubuntu)、生产集群(centos)的 jdk 版本、系统库不一致。
- 依赖冲突:应用依赖的本地库(如 native lib)在不同机器缺失。
- 部署复杂:需手动配置 jvm 参数、日志路径、启动脚本。
- 资源隔离差:多个应用共享同一台服务器,互相影响。
docker 的价值:
- 环境一致性:镜像包含 os + jdk + app,彻底消除“在我机器上能跑”问题。
- 快速部署:docker run 一键启动,无需安装 jdk。
- 资源隔离:cpu、内存、网络独立分配。
- 弹性伸缩:配合 kubernetes 轻松实现水平扩展。
传统方式:手写 dockerfile + 手动构建
最直观的方式是先用 maven 打包 jar,再编写 dockerfile 构建镜像。
步骤 1:maven 打包
mvn clean package -dskiptests
生成 target/myapp-1.0.0.jar
步骤 2:编写 dockerfile
# 使用官方 openjdk 17 镜像 from openjdk:17-jdk-slim # 设置工作目录 workdir /app # 复制 jar 文件到容器 copy target/myapp-1.0.0.jar app.jar # 暴露端口(假设应用监听 8080) expose 8080 # 启动命令 entrypoint ["java", "-jar", "app.jar"]
步骤 3:构建并运行镜像
# 构建镜像 docker build -t myapp:1.0.0 . # 运行容器 docker run -d -p 8080:8080 --name myapp-container myapp:1.0.0
✅ 优点:简单直观,适合学习
❌ 缺点:
- 需维护 dockerfile
- 构建过程分两步(maven + docker),效率低
- 镜像体积大(含完整 jdk)
- 无法利用 maven 生命周期自动化
方案一:使用 google jib 插件 —— “零 dockerfile” 构建
jib 是 google 开源的 maven/gradle 插件,专为 java 应用容器化设计。它无需 dockerfile、无需本地 docker daemon,直接从 maven 构建镜像并推送到仓库。
核心优势
- 分层构建:将应用拆分为
dependencies、snapshot dependencies、resources、classes多层,仅变更代码时只需上传少量层。 - 安全:不依赖本地 docker,避免权限问题。
- 快速:直接与 registry 通信,跳过本地镜像存储。
- 小体积:默认使用 distroless 基础镜像(仅含必要运行时)。
官方 github(持续活跃):https://github.com/googlecontainertools/jib
实战:spring boot 项目集成 jib
1. 在pom.xml中添加插件
<build>
<plugins>
<plugin>
<groupid>com.google.cloud.tools</groupid>
<artifactid>jib-maven-plugin</artifactid>
<version>3.4.0</version>
<configuration>
<!-- 目标镜像名 -->
<to>
<image>myregistry.com/myapp:${project.version}</image>
</to>
<!-- 基础镜像(可选) -->
<from>
<image>eclipse-temurin:17-jre-alpine</image>
</from>
<!-- 容器配置 -->
<container>
<ports>
<port>8080</port>
</ports>
<environment>
<spring_profiles_active>prod</spring_profiles_active>
</environment>
<creationtime>use_current_timestamp</creationtime>
</container>
</configuration>
</plugin>
</plugins>
</build>
2. 构建并推送到本地 docker 守护进程
# 构建镜像并加载到本地 docker mvn jib:dockerbuild # 查看镜像 docker images | grep myapp
3. 推送到远程仓库(如 docker hub)
# 先登录 docker login # 推送(需在 <to> 中配置镜像名,如 yourname/myapp) mvn jib:build
提示:若使用私有仓库(如 harbor、nexus),需配置认证信息(见后文)。
jib 分层机制 mermaid 图示

优势体现:当仅修改业务代码时,只有 classes layer 变更,推送速度极快!
方案二:dockerfile + maven resources 插件 —— 精细控制
当需要完全掌控镜像内容(如添加 shell 脚本、配置文件、非 java 资源),可结合 maven resources 插件 与 dockerfile。
项目结构设计
myapp/
├── src/
├── pom.xml
├── docker/
│ ├── dockerfile
│ └── entrypoint.sh
└── target/
└── myapp-1.0.0.jar
1. 配置 maven 将资源复制到 target/docker
在 pom.xml 中添加:
<build>
<plugins>
<!-- 复制 docker 相关文件到 target/docker -->
<plugin>
<artifactid>maven-resources-plugin</artifactid>
<version>3.3.1</version>
<executions>
<execution>
<id>copy-docker-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputdirectory>${project.build.directory}/docker</outputdirectory>
<resources>
<resource>
<directory>docker</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- 打包 jar -->
<plugin>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-maven-plugin</artifactid>
</plugin>
</plugins>
</build>
2. 编写增强版 dockerfile
docker/dockerfile:
# 构建阶段:使用 jdk 编译(若需在容器内编译)
# from maven:3.8.6-openjdk-17 as builder
# copy . /app
# workdir /app
# run mvn clean package -dskiptests
# 运行阶段:仅使用 jre
from eclipse-temurin:17-jre-alpine
# 安装必要工具(如 curl、bash)
run apk add --no-cache bash curl
# 创建应用用户(安全最佳实践)
run addgroup -g 1001 -s appuser && \
adduser -u 1001 -s appuser -g appuser
# 设置工作目录
workdir /app
# 复制 jar 和启动脚本
copy myapp-*.jar app.jar
copy entrypoint.sh /app/entrypoint.sh
# 赋予执行权限
run chmod +x /app/entrypoint.sh
# 切换到非 root 用户
user appuser
# 暴露端口
expose 8080
# 启动脚本(可处理信号、设置 jvm 参数等)
entrypoint ["/app/entrypoint.sh"]
docker/entrypoint.sh:
#!/bin/bash
set -e
# 设置默认 jvm 参数
jvm_opts="${jvm_opts:-"-xms256m -xmx512m"}"
# 处理优雅关闭(接收 sigterm)
function shutdown() {
echo "shutting down gracefully..."
kill -term "$pid"
wait "$pid"
exit 0
}
# 启动 java 应用
java $jvm_opts -jar app.jar &
pid=$!
# 注册信号处理器
trap shutdown sigterm sigint
# 等待应用结束
wait $pid
3. 构建镜像
# 先执行 maven 构建(复制资源 + 打包 jar) mvn clean package -dskiptests # 再构建 docker 镜像 docker build -t myapp:1.0.0 target/docker
适用场景:
- 需要自定义启动逻辑
- 集成非 java 组件(如 python 脚本)
- 严格的安全合规要求(非 root 用户、最小化 os)
多阶段构建(multi-stage build):极致优化镜像大小
java 应用镜像常因包含完整 jdk 而臃肿(>500mb)。多阶段构建 可将构建环境与运行环境分离,显著减小体积。
示例:spring boot + multi-stage
# 第一阶段:构建 from maven:3.8.6-eclipse-temurin-17 as builder # 复制 pom.xml 和源码 copy pom.xml . copy src ./src # 执行 maven 构建(缓存依赖) run mvn clean package -dskiptests # 第二阶段:运行 from eclipse-temurin:17-jre-alpine # 从 builder 阶段复制 jar copy --from=builder /target/*.jar app.jar expose 8080 entrypoint ["java", "-jar", "app.jar"]
构建效果对比
| 方式 | 镜像大小 | 是否含 maven/jdk |
|---|---|---|
| 单阶段(openjdk:17) | ~450 mb | 是 |
| 多阶段(jre-alpine) | ~180 mb | 否 |
| jib(distroless) | ~120 mb | 否 |
建议:生产环境优先使用 jib + distroless 或 多阶段 + alpine。
镜像标签策略:语义化版本与 git commit id
合理的镜像标签(tag)便于追踪和回滚。
推荐策略
| 场景 | 标签示例 | 说明 |
|---|---|---|
| 发布版本 | v1.2.0 | 与 git tag 对应 |
| 开发快照 | dev-abc1234 | dev- + git commit short sha |
| 最新稳定版 | latest | 谨慎使用,避免意外升级 |
在 maven 中动态生成标签
利用 git-commit-id-plugin 获取 git 信息:
<plugin>
<groupid>pl.project13.maven</groupid>
<artifactid>git-commit-id-plugin</artifactid>
<version>8.0.2</version>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<verbose>false</verbose>
<dateformat>yyyy.mm.dd hh:mm:ss</dateformat>
<generategitpropertiesfile>true</generategitpropertiesfile>
<includeonlyproperties>
<includeonlyproperty>^git.commit.id.abbrev$</includeonlyproperty>
</includeonlyproperties>
</configuration>
</plugin>
然后在 jib 配置中使用:
<configuration>
<to>
<image>myapp:dev-${git.commit.id.abbrev}</image>
</to>
</configuration>
插件官网:https://github.com/git-commit-id/git-commit-id-maven-plugin
推送镜像到私有仓库:harbor/nexus 示例
企业通常使用私有镜像仓库保障安全。
1. 配置 jib 推送至 harbor
<configuration>
<to>
<image>harbor.example.com/project/myapp:${project.version}</image>
<auth>
<username>admin</username>
<password>harbor12345</password>
</auth>
</to>
</configuration>
安全提示:密码不应硬编码!应使用 maven settings 或 ci/cd secrets。
2. 使用 maven settings 管理凭证
~/.m2/settings.xml:
<settings>
<servers>
<server>
<id>harbor-registry</id>
<username>admin</username>
<password>harbor12345</password>
</server>
</servers>
</settings>
pom.xml 中引用:
<configuration>
<to>
<image>harbor.example.com/project/myapp</image>
<auth>
<username>${settings.servers.harbor-registry.username}</username>
<password>${settings.servers.harbor-registry.password}</password>
</auth>
</to>
</configuration>
3. ci/cd 中使用 secrets(github actions 示例)
- name: build and push with jib
run: mvn jib:build
env:
jib_registry_username: ${{ secrets.harbor_user }}
jib_registry_password: ${{ secrets.harbor_token }}
github secrets 文档:https://docs.github.com/en/actions/security-guides/encrypted-secrets
安全最佳实践:扫描漏洞与最小化攻击面
容器镜像可能包含已知漏洞(cve),需主动防御。
1. 使用 trivy 扫描镜像
trivy 是开源漏洞扫描器。
# 安装 trivy(macos) brew install aquasecurity/trivy/trivy # 扫描本地镜像 trivy image myapp:1.0.0
输出示例:
myapp:1.0.0 (alpine 3.17.2) ============================ total: 2 (unknown: 0, low: 1, medium: 1, high: 0, critical: 0)
2. 在 ci 中集成扫描
- name: scan docker image
run: |
docker build -t myapp:latest .
trivy image --exit-code 1 --severity high,critical myapp:latest
trivy 官网(活跃维护):https://aquasecurity.github.io/trivy/
3. 其他安全建议
- 使用非 root 用户运行(见前文 dockerfile)
- 禁用 shell:distroless 镜像默认无 shell,防止入侵后执行命令
- 定期更新基础镜像:订阅安全公告,及时 rebuild
性能调优:jvm 与容器资源匹配
容器环境中的 jvm 需正确识别 cpu/内存限制。
问题:jvm 忽略 docker 内存限制
旧版 jvm(<8u131, <9)无法感知 -m 512m 等 docker 内存限制,导致 oom。
解决方案
1. 使用新版 jdk(推荐)
jdk 10+ 默认启用 container awareness,jdk 8u191+ 需手动开启:
entrypoint ["java", "-xx:+usecontainersupport", "-jar", "app.jar"]
2. 显式设置堆内存
# 通过环境变量控制 env java_opts="-xmx300m -xms300m" entrypoint ["sh", "-c", "java $java_opts -jar app.jar"]
3. 使用 jib 自动配置
jib 默认添加 -xx:+usecontainersupport,无需额外配置。
oracle 官方文档:jdk 8 updates for docker
ci/cd 集成:github actions 完整流水线
下面是一个完整的 github actions workflow,实现 代码提交 → 构建 → 扫描 → 推送。
name: build and push docker image
on:
push:
branches: [ main ]
tags: [ 'v*' ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: set up jdk 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: cache maven packages
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashfiles('**/pom.xml') }}
- name: build with maven
run: mvn -b clean verify
- name: set up docker buildx
uses: docker/setup-buildx-action@v3
- name: login to docker hub
uses: docker/login-action@v3
with:
username: ${{ secrets.dockerhub_username }}
password: ${{ secrets.dockerhub_token }}
- name: extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: myname/myapp
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha
- name: build and push with jib
run: mvn jib:build -djib.to.image=${{ steps.meta.outputs.tags }}
- name: scan image with trivy
run: |
docker pull ${{ steps.meta.outputs.tags }}
trivy image --exit-code 1 --severity high,critical ${{ steps.meta.outputs.tags }}
流程说明:
- 拉取代码
- 设置 jdk + 缓存 maven 依赖
- 执行单元测试
- 登录 docker hub
- 自动生成语义化标签
- 使用 jib 构建并推送
- 安全扫描,高危漏洞阻断发布
常见问题排查指南
问题 1:jib 构建失败,提示“cannot connect to the docker daemon”
原因:使用了 jib:build(需推送远程仓库),但未配置 registry 认证。
解决:
- 改用
jib:dockerbuild(仅构建到本地) - 或正确配置
<to><auth>或 maven settings
问题 2:容器启动后立即退出
原因:entrypoint 命令执行完毕,容器生命周期结束。
解决:
- 确保 java 进程在前台运行(不要加
&) - 检查应用是否抛出异常(
docker logs container_name)
问题 3:镜像太大,超过 500mb
解决:
- 改用
eclipse-temurin:17-jre-alpine或 jib 的 distroless - 使用多阶段构建
- 排查是否误将
src/或target/全量复制
问题 4:时区不正确
解决:在 dockerfile 中设置时区
env tz=asia/shanghai run ln -snf /usr/share/zoneinfo/$tz /etc/localtime && echo $tz > /etc/timezone
方案对比:jib vs dockerfile + maven 🆚
| 特性 | jib | dockerfile + maven |
|---|---|---|
| 是否需要 dockerfile | ❌ 否 | ✅ 是 |
| 是否需要本地 docker | ❌ 否(jib:build)✅ 是( jib:dockerbuild) | ✅ 是 |
| 镜像分层优化 | ✅ 自动分层 | ⚠️ 需手动设计 |
| 构建速度 | ⚡ 极快(仅传变更层) | 🐢 较慢(全量构建) |
| 控制粒度 | 中(插件配置) | ✅ 极高(任意命令) |
| 学习成本 | 低 | 中 |
| 适用场景 | 标准 java 应用 | 复杂定制需求 |
结论:
- 90% 场景用 jib:快速、安全、高效
- 10% 场景用 dockerfile:需深度定制(如集成 sidecar、特殊 init 流程)
结语:拥抱云原生,从容器化开始
将 java 应用容器化不再是“可选项”,而是现代软件工程的“必修课”。maven 与 docker 的结合,为我们提供了从构建到部署的端到端解决方案。无论是使用 jib 的便捷性,还是 dockerfile 的灵活性,核心目标都是:交付一致、安全、高效的容器镜像。
记住:好的容器化不是终点,而是云原生旅程的起点。下一步,你可以探索:
- 使用 helm 管理 kubernetes 部署
- 集成 prometheus + grafana 监控
- 实现蓝绿发布/金丝雀发布
以上就是使用maven将java项目打包为docker镜像的实战方法的详细内容,更多关于maven将java打包为docker镜像的资料请关注代码网其它相关文章!
发表评论