SpringBoot 项目标准 Dockerfile 实战教程
在当下云原生、容器化部署的开发场景中,Docker 容器部署已经成为 SpringBoot 项目上线的主流方式。相比于传统的服务器打包部署,Docker 部署可以实现环境统一、部署快速、迁移便捷、版本可控,彻底解决"本地能跑,线上报错"的环境问题。
很多开发者在编写 SpringBoot Dockerfile 时,经常出现镜像臃肿、构建缓慢、环境冗余、启动异常等问题。今天这篇博客,给大家分享一套生产级、可直接复用、极致优化的 SpringBoot Dockerfile 模板,同时拆解核心语法、构建逻辑、优化技巧,新手也能直接看懂、上手即用。
一、Dockerfile 核心全面介绍
Dockerfile 是一个文本脚本文件 ,内置一系列专属构建指令,核心作用是自动化构建 Docker 镜像。Docker 引擎会逐行读取 Dockerfile 中的指令,逐条执行并逐层生成镜像层,最终编译产出可独立运行、环境完整的 Docker 镜像。
核心思想 :Docker 镜像采用分层存储结构,每一条 Dockerfile 指令都会生成一个全新镜像层,且构建完成后层级会自动缓存。再次构建时,仅执行变更指令,未改动层级直接复用缓存,大幅提升镜像构建速度。
1. 基础语法格式
基础语法规则极简,固定格式如下:
dockerfile
# 注释内容
INSTRUCTION arguments
- 指令大小写不敏感,行业统一习惯大写,用于和参数清晰区分,提升可读性;
- 文件默认命名为 Dockerfile,无后缀,Docker 会默认优先识别该文件。
2. 十大核心常用指令详解
(1)FROM(必备首指令)
用于指定基础镜像,所有自定义镜像构建都基于该基础镜像,是 Dockerfile 第一条有效指令,不可省略(空镜像 scratch 除外)。支持多阶段构建命名。
dockerfile
FROM openjdk:17-jdk-slim
# 多阶段构建命名示例
FROM maven:3.8 AS builder
(2)WORKDIR
设置容器内部工作目录,等效于容器内的 cd 命令,后续 RUN、COPY、CMD 等所有指令均在该目录下执行。目录不存在会自动创建,生产环境优先使用 WORKDIR,不建议频繁用 RUN 执行 mkdir 创建目录。
dockerfile
WORKDIR /app
(3)COPY / ADD
核心作用:将宿主机文件/目录复制到镜像内部,. 代表当前 WORKDIR 工作目录。
- COPY(推荐):纯文件复制功能,无额外逻辑,安全轻量,生产环境优先使用;
- ADD:在复制基础上,支持自动解压 tar 压缩包、拉取远程 URL 文件(不推荐使用 URL 下载,建议用 RUN curl 替代)。
dockerfile
# 复制配置文件和源码
COPY pom.xml .
COPY src ./src
# 解压压缩包到指定目录
ADD test.tar.gz /opt/
(4)RUN
构建镜像阶段执行的命令,仅在 build 构建过程运行,执行后会固化为新的镜像层,主要用于安装依赖、编译代码、下载资源。支持两种写法,建议合并多条命令,减少镜像层数。
dockerfile
# shell 写法(默认 /bin/sh 执行)
RUN apt-get update && apt-get install -y curl
# exec 写法(不启用shell,更安全)
RUN ["apt-get","install","-y","curl"]
(5)CMD
容器启动阶段执行的默认命令,仅在容器 run 运行时生效,构建阶段不执行。一个 Dockerfile 仅最后一个 CMD 生效,且可被 docker run 启动参数覆盖。
核心区分:RUN 构建时运行,CMD 启动时运行。
dockerfile
# shell 形式
CMD java -jar app.jar
# exec 形式(生产推荐)
CMD ["java","-jar","app.jar"]
(6)ENTRYPOINT
容器核心入口程序,用于固定容器启动主命令,优先级高于 CMD,无法被 docker run 普通参数直接覆盖。日常搭配 CMD 使用,CMD 仅作为 ENTRYPOINT 的默认参数。
dockerfile
ENTRYPOINT ["java"]
CMD ["-jar","app.jar"]
运行示例:执行 docker run xxx -Xmx512m 时,CMD 默认参数会被替换,最终执行 java -Xmx512m。
(7)EXPOSE
仅用于文档声明 容器对外开放的端口,不会自动开放端口、不占用端口资源。容器真正端口映射,需要通过 docker run -p 手动配置。
dockerfile
EXPOSE 8080
(8)ENV
定义全局环境变量,构建阶段、容器运行阶段均可生效,常用于配置项目参数、JVM 启动参数。
dockerfile
ENV APP_NAME=demo
ENV JAVA_OPTS="-Xms256m -Xmx512m"
(9)ARG
构建阶段临时参数,仅镜像 build 过程有效,容器启动运行后失效,常用于动态传入版本号、环境参数。
dockerfile
# 定义默认参数
ARG VERSION=1.0
构建时动态传参:
bash
docker build --build-arg VERSION=2.0 -t myapp .
(10)VOLUME
定义数据卷挂载目录,容器运行时会自动挂载匿名卷,用于实现数据持久化,避免容器删除后数据丢失。
dockerfile
VOLUME /data
3. 镜像构建核心命令
docker build 是构建自定义镜像的核心命令,. 代表当前目录为构建上下文,Docker 会将上下文目录文件上传至 Docker 引擎完成构建。
bash
# 默认 Dockerfile 构建,打标签
docker build -t my-demo:1.0 .
# 指定自定义 Dockerfile 文件构建
docker build -f Dockerfile.prod -t my-demo:1.0 .
⚠️ 构建上下文重点注意:切勿将 .git、日志文件、编译产物、大体积冗余文件放入构建上下文,必须配置 .dockerignore 过滤,减小构建体积、提升构建速度。
4. 多阶段构建核心原理
多阶段构建是解决镜像臃肿、环境冗余的核心方案,核心逻辑:拆分编译环境和运行环境,第一阶段用完整编译镜像完成打包,第二阶段仅拷贝运行所需产物,舍弃全部编译冗余环境,极大压缩镜像体积。
dockerfile
# 第一阶段:编译打包(临时构建环境)
FROM maven:3.9 AS builder
WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# 第二阶段:运行环境(最终镜像)
FROM openjdk:17-jdk-slim
WORKDIR /app
# 从构建阶段拷贝产物
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
CMD ["java","-jar","app.jar"]
最终镜像仅保留 JDK + 项目 Jar 包,无任何编译工具冗余,体积大幅精简。
5. 生产最佳实践
结合官方规范与实战经验,整理一套可直接落地的生产级 Dockerfile 编写规范,规避镜像臃肿、构建缓慢、运行异常等问题:
- 缓存优化:变动频率低的指令前置,优先复制 pom 依赖文件、下载依赖,再复制源码,锁住依赖缓存,大幅提升重复构建速度;
- 精简层数:合并多个 RUN 命令,减少镜像分层数量,降低镜像体积;
- 裁剪上下文:强制配置 .dockerignore,过滤日志、编译产物、版本控制文件等冗余文件;
- 选用轻量镜像:优先使用官方 slim、alpine 轻量化基础镜像,摒弃完整版镜像;
- 隔离环境:统一采用多阶段构建,分离编译环境与运行环境,只保留运行必需依赖;
- 清理冗余缓存:RUN 安装依赖后,及时清理 apt/yum 缓存,避免垃圾文件残留;
- 规范指令格式:优先使用 exec 格式编写 CMD / ENTRYPOINT,安全性、兼容性更强。
- 规范指令格式:优先使用 exec 格式编写 CMD / ENTRYPOINT,兼容性、安全性更强。
6. RUN / CMD / ENTRYPOINT 核心区别对照表
三大核心指令极易混淆,下表清晰区分执行时机、核心作用、使用场景,解决90%启动命令问题:
| 指令 | 执行时机 | 核心作用 |
|---|---|---|
| RUN | 镜像构建 build 阶段 | 执行编译、安装、下载命令,操作固化为镜像层 |
| CMD | 容器启动 run 阶段 | 配置容器默认启动命令,可被 docker run 参数覆盖 |
| ENTRYPOINT | 容器启动 run 阶段 | 配置容器核心主程序,固定启动入口,不易被覆盖 |
二、为什么要规范编写 Dockerfile?
不规范的 Dockerfile 会出现两大核心问题:
- 镜像体积过大:将 Maven、JDK 编译环境、冗余依赖全部打包进运行镜像,镜像动辄几百 M,部署、拉取、更新效率极低。
- 构建速度缓慢:不会利用 Docker 镜像缓存,每次修改代码都要重新下载所有依赖,浪费大量时间。
- 生产环境不安全:镜像包含编译工具、多余命令,增加漏洞攻击面。
因此,生产环境必须使用多阶段构建方案,分离编译环境和运行环境,打造轻量、高效、安全的项目镜像。
三、生产级 SpringBoot Dockerfile(通用完整版)
该模板适配 JDK8 / JDK11 / JDK17 主流 SpringBoot 版本,支持所有 SpringBoot 单体项目,开箱即用,无需大幅修改。
dockerfile
# 第一阶段:编译构建阶段(构建镜像,临时环境,最终不保留)
# 指定Maven基础镜像,用于编译打包项目
FROM maven:3.8.8-openjdk-17 AS builder
# 设置工作目录
WORKDIR /build
# 优先复制依赖配置文件,利用docker缓存(核心优化点)
# 先复制pom.xml,单独下载依赖,代码变更不会重复下载依赖
COPY pom.xml .
# 下载项目依赖,缓存依赖包
RUN mvn dependency:go-offline
# 复制项目源码
COPY src ./src
# 执行打包命令,跳过测试,加快打包速度
RUN mvn clean package -DskipTests
# 第二阶段:运行阶段(最终上线镜像,只保留运行所需环境)
# 采用轻量级JDK镜像,减小最终镜像体积
FROM openjdk:17-jdk-slim
# 设置容器工作目录
WORKDIR /app
# 从构建阶段拷贝打包好的jar包到当前运行环境
COPY --from=builder /build/target/*.jar app.jar
# 声明项目暴露端口(仅文档声明,不主动开放端口)
EXPOSE 8080
# 设置JVM启动参数,优化容器运行内存
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC"
# 容器启动命令,运行SpringBoot项目
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
四、配套 .dockerignore 文件(必配)
.dockerignore 用于过滤构建上下文冗余文件,避免无关文件参与构建,大幅提升构建速度、减小镜像体积,必须和 Dockerfile 放在项目根目录。
plain
# Java 项目编译冗余文件
target/
*.log
*.tmp
*.bak
# Git 版本控制文件
.git/
.gitignore
# IDEA 编辑器配置
.idea/
*.iml
*.iws
*.ipr
# 系统冗余文件
.DS_Store
Thumbs.db
# 文档、测试文件
doc/
test/
五、逐行核心语法详解
1. 多阶段构建核心逻辑
整个 Dockerfile 分为两个阶段,这是生产最优方案:
- builder 构建阶段:使用完整 Maven 镜像,完成项目依赖下载、源码编译、Jar 包打包,该阶段镜像构建完成后会被舍弃。
- 运行阶段:使用轻量化 JDK 镜像,仅拷贝编译好的 Jar 包,无任何冗余编译环境,最终生成的镜像体积极小。
2. 缓存优化核心:先拷依赖,后拷代码
Docker 镜像采用分层缓存机制,每层指令都会缓存。
我们先复制 pom.xml 下载依赖,再复制源码:依赖文件不变,无论修改多少次业务代码,都不会重新下载依赖,构建速度提升数倍。
3. JVM 参数优化
容器环境不建议使用默认 JVM 参数,容易出现内存溢出。模板中预设了轻量化内存配置:
- -Xms256m:初始内存256M
- -Xmx512m:最大内存512M
- -XX:+UseG1GC:使用G1垃圾回收器,适配容器低内存场景
六、Docker 将 SpringBoot 项目打包为镜像完整流程
很多新手只会粘贴 Dockerfile,却不清楚 SpringBoot 项目从源码 → Jar包 → Docker镜像 的完整打包流程。本节手把手讲解标准打包流程,包含前置准备、文件配置、镜像构建、镜像查看全流程,适配本地、服务器所有环境。
1. 前置准备工作
打包镜像前,必须保证项目可正常编译打包,提前完成两项准备:
- 本地/服务器安装 Docker 环境,且 Docker 服务正常启动;
- SpringBoot 项目可正常编译,支持 Maven/Gradle 打包(本文以 Maven 为例)。
2. 项目文件准备
在 SpringBoot 项目根目录,新建两个核心文件(必须和 pom.xml 同级):
- Dockerfile:镜像构建脚本(使用上文生产级模板)
- .dockerignore:构建冗余文件过滤文件
文件结构示例:
plain
springboot-demo-project/
├── pom.xml
├── src/
├── Dockerfile
└── .dockerignore
3. 两种打包方式(推荐 Docker 内置打包)
方式一:Docker 多阶段自动打包(生产推荐,无需手动打Jar)
依托前文多阶段构建 Dockerfile,内置 Maven 编译流程,无需本地执行打包命令,Docker 会自动拉取 Maven 镜像、编译源码、打包 Jar、构建运行镜像,全程自动化。
核心优势:无需本地配置 Maven、JDK 环境,环境统一、零污染。
在项目根目录直接执行构建命令:
bash
docker build -t springboot-demo:1.0 .
方式二:手动打Jar + Docker打包(传统方式)
先本地/服务器手动编译出 Jar 包,再通过 Dockerfile 基于 Jar 包构建镜像,适合需要提前测试 Jar 包的场景。
第一步:手动打包(Maven)
bash
# 跳过测试打包
mvn clean package -DskipTests
打包完成后,target 目录下会生成:项目名.jar 可执行文件。
第二步:简化版 Dockerfile(仅运行阶段)
dockerfile
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
第三步:构建镜像
bash
docker build -t springboot-demo:1.0 .
4. 验证镜像打包结果
构建完成后,通过以下命令查看本地镜像,确认打包成功:
bash
docker images
如果列表中出现 springboot-demo 镜像,说明 SpringBoot 项目打包镜像完成。
5. 打包核心注意事项
- 上下文目录必须正确 :构建命令最后的
.代表当前根目录,必须在 Dockerfile 所在目录执行构建; - 版本统一:Dockerfile 内 JDK 版本与项目编译 JDK 版本保持一致,避免版本不兼容启动报错;
- 禁止冗余构建:必须配置 .dockerignore,防止 target、日志、IDE 配置文件参与构建;
- 镜像标签规范 :正式环境建议使用
项目名:版本号命名,方便版本管理与回滚。
七、完整构建、启动、停止命令
1. 构建镜像
在项目根目录执行,构建镜像并命名打标签:
bash
docker build -t springboot-demo:1.0 .
参数说明:. 代表当前目录为构建上下文,必须携带。
2. 启动容器
bash
docker run -d --name springboot-app -p 8080:8080 springboot-demo:1.0
参数说明:
- -d:后台守护进程运行
- --name:指定容器名称
- -p 宿主机端口:容器端口:端口映射,对外提供访问
3. 常用运维命令
bash
# 查看运行容器
docker ps
# 查看项目日志
docker logs -f springboot-app
# 停止容器
docker stop springboot-app
# 删除容器
docker rm springboot-app
# 删除镜像
docker rmi springboot-demo:1.0
八、生产环境最佳实践总结
- 强制使用多阶段构建:杜绝编译环境残留,镜像体积从几百M压缩至100M以内。
- 合理利用缓存:pom.xml、依赖下载指令前置,源码复制后置。
- 使用轻量基础镜像:优先使用 openjdk-slim 镜像,舍弃完整版 JDK 镜像。
- 配置 .dockerignore:过滤冗余文件,精简构建上下文。
- 自定义 JVM 参数:适配容器内存限制,避免内存溢出、资源浪费。
- 精简镜像层数:合理合并命令、剔除无用指令,严控镜像分层数量,保证镜像轻量化。
九、常见问题答疑
1. EXPOSE 端口为什么不生效?
EXPOSE 只是声明端口用途,仅作为文档备注,不会主动开放端口,真正的端口映射需要通过 -p 8080:8080 实现。
2. 为什么构建镜像速度很慢?
大概率是没有配置 .dockerignore,或者没有拆分 pom.xml 和源码复制,导致每次构建都重新下载全部依赖。
3. 容器启动后立即退出?
大概率是启动命令格式错误,推荐使用模板中的ENTRYPOINT + sh -c 格式,支持读取环境变量 JAVA_OPTS 参数。
结尾
本文提供的 Dockerfile 模板适配 99% 的 SpringBoot 单体项目,开箱即用、极致优化、符合生产规范。大家可以直接复制到项目中使用,彻底解决容器部署的各类问题,同时掌握 Dockerfile 的核心编写逻辑和优化思路。