使用Docker运行Spring Boot

本指南介绍如何构建用于运行Spring Boot应用的Docker镜像。我们先从基本的Dockerfile开始,再做几项调整,随后展示使用Maven和Gradle构建插件来替代docker命令的两种选择。作为入门指南,本文只覆盖少量基础需求。如果为生产环境构建容器镜像,需要考虑的事情很多,短篇指南无法全部介绍。

另有Docker专题指南,涵盖比本文更广的选择,并有更详细的介绍。

你将构建什么

Docker是一套Linux容器管理工具,也具有“社区分享”的特点:用户可以发布容器镜像,并使用其他人发布的镜像。Docker镜像是一份运行容器化进程的配方。本指南将为一个简单的Spring Boot应用构建镜像。

你需要什么

还需要Docker;在你的机器上安装Docker的详细说明见Docker安装文档。继续之前,确认可以从命令行运行docker命令。

从Spring Initializr开始

可以使用这个预初始化项目,点击Generate下载ZIP文件。项目配置与本教程示例相匹配。

手动初始化项目:

  1. 打开https://start.spring.io。该服务会引入应用需要的依赖,并完成大部分设置。

  2. 选择Gradle或Maven,以及希望使用的语言。

  3. 点击Dependencies,选择Spring Web。

  4. 点击Generate。

  5. 下载生成的ZIP文件。它是按你的选择配置好的Web应用压缩包。

如果IDE集成了Spring Initializr,可以直接在IDE内完成上述过程。
也可以从GitHub fork该项目,并在IDE或其他编辑器中打开。

设置Spring Boot应用

现在可以创建一个简单应用:Java文件为src/main/java/hello/Application.java,Kotlin文件为src/main/kotlin/hello/Application.kt。

Java
package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Application {

	@RequestMapping("/")
	public String home() {
		return "Hello Docker World";
	}

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

}

Kotlin
package hello

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@SpringBootApplication
@RestController
class Application {

    @RequestMapping("/")
    fun home() = "Hello Docker World"

}

fun main(args: Array<String>) {
    runApplication<Application>(*args)
}

类标注了@SpringBootApplication和@RestController,表示已经可以由Spring MVC处理Web请求。@RequestMapping把/映射到home()方法,该方法返回Hello World响应。main()使用Spring Boot的SpringApplication.run()启动应用;Kotlin则使用runApplication。

现在可以先在宿主操作系统中运行应用,不使用Docker容器:

Gradle
./gradlew build && java -jar build/libs/spring-boot-docker-complete-0.0.1-SNAPSHOT.jar

Maven
./mvnw package && java -jar target/spring-boot-docker-complete-0.0.1-SNAPSHOT.jar

然后访问localhost:8080,查看“Hello Docker World”消息。

容器化应用

Docker使用一种简单的Dockerfile格式指定镜像的“层”。在Spring Boot项目中创建以下Dockerfile:

示例1:Dockerfile
FROM eclipse-temurin:17
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

运行以下命令:

Gradle
docker build --build-arg 'JAR_FILE=build/libs/*-SNAPSHOT.jar' -t springio/gs-spring-boot-docker .

Maven
docker build -t springio/gs-spring-boot-docker .

该命令构建镜像,并将其标记为springio/gs-spring-boot-docker。

这个Dockerfile很简单,但已足以运行基础Spring Boot应用:只需要Java和JAR文件。它通过COPY命令将项目JAR复制到容器,命名为app.jar,再由ENTRYPOINT运行。ENTRYPOINT采用数组形式,因此Java进程不会再包在shell中。

以前,为缩短Tomcat启动时间,我们会添加一个系统属性,指定/dev/urandom作为熵源。使用JDK 8或更高版本时,已经无需这样做。

使用普通用户权限运行应用有助于降低部分风险,参见StackExchange上的讨论。因此,Dockerfile的一项重要改进是以非root用户运行应用:

示例2:Dockerfile
FROM eclipse-temurin:17
RUN addgroup --system spring && adduser --system --ingroup spring spring
USER spring:spring
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

使用以下命令构建并运行应用,可以在启动日志中看到用户名:

Gradle
./gradlew build
docker build --build-arg 'JAR_FILE=build/libs/*-SNAPSHOT.jar' -t springio/gs-spring-boot-docker .
docker run -p 8080:8080 springio/gs-spring-boot-docker

Maven
./mvnw package
docker build -t springio/gs-spring-boot-docker .
docker run -p 8080:8080 springio/gs-spring-boot-docker

注意第一条INFO日志中的started by:

 :: Spring Boot ::                (v4.1.1)

2026-09-30T12:47:53.827Z  INFO 1 --- [           main] hello.Application                        : Starting Application v0.0.1-SNAPSHOT using Java 17.0.19 with PID 1 (/app.jar started by spring in /)
...

Spring Boot胖JAR中的依赖与应用资源分离清楚,可以利用这一点改善性能。关键是在容器文件系统中创建不同层。构建时以及多数运行时会缓存这些层,因此变化最频繁的资源,通常是应用自身的类和静态资源,应放在变化较慢的资源之后。为此,我们采用稍有不同的Dockerfile:

Java
FROM eclipse-temurin:17
RUN addgroup --system spring && adduser --system --ingroup spring spring
USER spring:spring
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

Kotlin
FROM eclipse-temurin:17
RUN addgroup --system spring && adduser --system --ingroup spring spring
USER spring:spring
ARG DEPENDENCY=build/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.ApplicationKt"]

这个Dockerfile有一个DEPENDENCY参数,指向已解压胖JAR的目录。使用该参数前,运行以下命令:

Gradle
mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*-SNAPSHOT.jar)

Maven
mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)

如果操作正确,目录中已包含存放依赖JAR的BOOT-INF/lib,以及存放应用类的BOOT-INF/classes。注意,我们使用应用自己的主类:Java中是hello.Application,Kotlin中是hello.ApplicationKt,因为顶层main函数会编译为ApplicationKt类。这比通过胖JAR启动器间接启动更快。

解压JAR可能使类路径在运行时采用不同顺序。行为正确且编写良好的应用不应依赖这一点,但如果依赖没有被仔细管理,可能出现行为变化。

运行以下命令构建镜像。Gradle构建需要在Docker命令行中显式传递构建参数:

Gradle
docker build --build-arg DEPENDENCY=build/dependency -t springio/gs-spring-boot-docker .

Maven
docker build -t springio/gs-spring-boot-docker .

如果只使用Gradle,可以修改Dockerfile,让DEPENDENCY的默认值对应解压目录。仅使用Gradle构建的Kotlin示例正是这样处理的,因此它无需--build-arg DEPENDENCY=build/dependency参数。

也可以使用构建插件。Spring Boot支持用自己的Maven或Gradle插件构建容器,Google的开源工具Jib也提供Maven和Gradle插件。这种方式最有吸引力的一点是无需Dockerfile:仍能构建与docker build相同标准格式的容器镜像,而且也能在没有安装Docker的环境中工作,构建服务器上常见这种情况。

默认buildpack生成的镜像默认不会以root运行应用。修改默认设置的方法见Gradle或Maven配置指南。

使用Spring Boot构建插件构建Docker镜像

无需修改构建配置,一条命令即可构建带标签的Docker镜像。注意:即使Dockerfile仍在目录中,也会被忽略。

Gradle
./gradlew bootBuildImage --imageName=springio/gs-spring-boot-docker

Maven
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=springio/gs-spring-boot-docker

向Docker镜像仓库推送需要相应权限,而你默认没有这些权限。将镜像前缀改为自己的Docker Hub ID,并在运行Docker前使用docker login确保已经登录。

推送之后

示例中的docker push会失败,除非你属于Docker Hub上的springio组织。但将配置改为自己的Docker ID后,应该可以成功,得到一个带标签且已部署到仓库的新镜像。

运行本地构建的镜像,不需要注册Docker账户,也无需发布任何内容。无论通过Docker命令行还是Spring Boot构建,本地都保留着带标签的镜像,可以这样运行:

$ docker run -p 8080:8080 -t springio/gs-spring-boot-docker
Calculating JVM memory based on 7373112K available memory
For more information on this calculation, see https://paketo.io/docs/reference/java-reference/#memory-calculator
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx6785158K -XX:MaxMetaspaceSize=75953K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 7373112K, Thread Count: 250, Loaded Class Count: 10996, Headroom: 0%)
...
2026-09-30T12:52:44.656Z  INFO 1 --- [           main] o.s.boot.tomcat.TomcatWebServer          : Tomcat started on port 8080 (http) with context path '/'
2026-09-30T12:52:44.660Z  INFO 1 --- [           main] hello.Application                        : Started Application in 0.678 seconds (process running for 0.843)

buildpack在运行时使用内存计算器,为JVM分配适合容器的内存大小。

应用随后可在http://localhost:8080访问,页面显示“Hello Docker World”。

运行期间,可以在容器列表中看到类似以下示例的内容:

$ docker ps
CONTAINER ID        IMAGE                                   COMMAND                  CREATED             STATUS              PORTS                    NAMES
81c723d22865        springio/gs-spring-boot-docker:latest   "/cnb/process/web"       34 seconds ago      Up 33 seconds       0.0.0.0:8080->8080/tcp   goofy_brown

停止应用时,对前面列表中的容器ID或名称运行docker stop。你的ID或名称会有所不同:

docker stop goofy_brown

使用结束后,也可以删除容器。它持久保存在文件系统中/var/lib/docker下的某个位置:

docker rm goofy_brown

使用Spring Profile

使用Spring Profile运行刚构建的Docker镜像,只需向Docker运行命令传递环境变量。以下使用prod Profile:

docker run -e "SPRING_PROFILES_ACTIVE=prod" -p 8080:8080 -t springio/gs-spring-boot-docker

dev Profile可以采用相同方式:

docker run -e "SPRING_PROFILES_ACTIVE=dev" -p 8080:8080 -t springio/gs-spring-boot-docker

调试Docker容器中的应用

调试应用可以使用JPDA Transport,将容器视为远程服务器。要启用调试,在JAVA_OPTS变量中传入Java agent设置,并在运行容器时将agent端口映射到localhost。原文指出,Docker for Mac存在限制:不使用特殊技巧就无法直接通过容器IP访问。(保留原文平台说明。)

docker run -e "JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n" -p 8080:8080 -p 5005:5005 -t springio/gs-spring-boot-docker

总结

完成以上步骤,就创建了运行Spring Boot应用的Docker容器。Spring Boot默认在容器内监听8080端口,我们通过命令行的-p将它映射到了宿主的同一端口。

另请参阅

以下指南也可能有所帮助:

想编写新指南或为现有指南作贡献?请查看贡献指南。

所有指南的代码按ASLv2(Apache License 2.0)发布,文字按Creative Commons署名—禁止演绎许可证发布。


原文:使用Docker运行Spring Boot;作者:Spring Guides贡献者;日期:未列出固定发布日期。原文及源码权利归原作者和相应权利人所有。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容