Dockerfile实战教程

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 会出现两大核心问题:

  1. 镜像体积过大:将 Maven、JDK 编译环境、冗余依赖全部打包进运行镜像,镜像动辄几百 M,部署、拉取、更新效率极低。
  2. 构建速度缓慢:不会利用 Docker 镜像缓存,每次修改代码都要重新下载所有依赖,浪费大量时间。
  3. 生产环境不安全:镜像包含编译工具、多余命令,增加漏洞攻击面。

因此,生产环境必须使用多阶段构建方案,分离编译环境和运行环境,打造轻量、高效、安全的项目镜像。

三、生产级 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

八、生产环境最佳实践总结

  1. 强制使用多阶段构建:杜绝编译环境残留,镜像体积从几百M压缩至100M以内。
  2. 合理利用缓存:pom.xml、依赖下载指令前置,源码复制后置。
  3. 使用轻量基础镜像:优先使用 openjdk-slim 镜像,舍弃完整版 JDK 镜像。
  4. 配置 .dockerignore:过滤冗余文件,精简构建上下文。
  5. 自定义 JVM 参数:适配容器内存限制,避免内存溢出、资源浪费。
  6. 精简镜像层数:合理合并命令、剔除无用指令,严控镜像分层数量,保证镜像轻量化。

九、常见问题答疑

1. EXPOSE 端口为什么不生效?

EXPOSE 只是声明端口用途,仅作为文档备注,不会主动开放端口,真正的端口映射需要通过 -p 8080:8080 实现。

2. 为什么构建镜像速度很慢?

大概率是没有配置 .dockerignore,或者没有拆分 pom.xml 和源码复制,导致每次构建都重新下载全部依赖。

3. 容器启动后立即退出?

大概率是启动命令格式错误,推荐使用模板中的ENTRYPOINT + sh -c 格式,支持读取环境变量 JAVA_OPTS 参数。

结尾

本文提供的 Dockerfile 模板适配 99% 的 SpringBoot 单体项目,开箱即用、极致优化、符合生产规范。大家可以直接复制到项目中使用,彻底解决容器部署的各类问题,同时掌握 Dockerfile 的核心编写逻辑和优化思路。

相关推荐
新时代牛马1 小时前
Linux 驱动调试完整篇:从printk/dev_dbg、动态调试到 debugfs/ftrace 排障
java·linux·服务器
FfHUCisI1 小时前
Golang 网络轮询器 netpoll:把阻塞 IO 变成事件通知
开发语言·网络·golang
XiaoQiao6669991 小时前
C 语言常用头文件及里面核心函数
c语言·开发语言
statistican_ABin1 小时前
中国宠物经济发展分析报告——基于R语言的2018-2024年宠物市场多维度分析
开发语言·r语言·宠物
计算机毕设定制辅导-无忧学长2 小时前
《基于Spring Boot传承之光非遗陶瓷烧造产品交易平台的设计与实现》
java·vue.js·spring boot·后端·毕业设计
Zane19942 小时前
内存都要回收,为什么JVM偏要把堆分成新生代和老年代
java·后端
matlab代码2 小时前
基于matlab PCA人脸识别系统【源码63期】
开发语言·matlab
Terra.K2 小时前
后端开发阶段性总结
java·开发语言·后端
学逆向的2 小时前
PE——合并节
开发语言·网络安全·pe