使用Maven将Java项目打包为Docker镜像的实战方法
引言
在云原生时代,容器化(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镜像的资料请关注脚本之家其它相关文章!
相关文章
JavaWeb开发使用Cookie创建-获取-持久化、自动登录、购物记录、作用路径
这篇文章主要介绍了JavaWeb开发使用Cookie创建-获取-持久化、自动登录、购物记录、作用路径的相关知识,非常不错,对cookie持久化知识感兴趣的朋友一起学习吧2016-08-08
Springboot @Value注入boolean设置默认值方式
这篇文章主要介绍了Springboot @Value注入boolean设置默认值方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2022-03-03


最新评论