Spring Boot with Docker

This guide walks you through the process of building a Docker image for running a Spring Boot application. We start with a basic Dockerfile and make a few tweaks. Then we show a couple of options that use build plugins (for Maven and Gradle) instead of docker. This is a “getting started” guide, so the scope is limited to a few basic needs. If you are building container images for production use, there are many things to consider, and it is not possible to cover them all in a short guide.

There is also a Topical Guide on Docker, which covers a wider range of choices that we have here and in much more detail.

What You Will Build

Docker is a Linux container management toolkit with a “social” aspect, letting users publish container images and consume those published by others. A Docker image is a recipe for running a containerized process. In this guide, we build one for a simple Spring boot application.

What You Will Need

You also need Docker, see https://docs.docker.com/installation/#installation for details on setting Docker up for your machine. Before proceeding further, verify you can run docker commands from the shell.

Starting with Spring Initializr

You can use this pre-initialized project and click Generate to download a ZIP file. This project is configured to fit the examples in this tutorial.

To manually initialize the project:

  1. Navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.

  2. Choose either Gradle or Maven and the language you want to use.

  3. Click Dependencies and select Spring Web.

  4. Click Generate.

  5. Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.

If your IDE has the Spring Initializr integration, you can complete this process from your IDE.
You can also fork the project from GitHub and open it in your IDE or other editor.

Set up a Spring Boot Application

Now you can create a simple application (in src/main/java/hello/Application.java for Java or src/main/kotlin/hello/Application.kt for Kotlin):

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)
}

The class is flagged as a @SpringBootApplication and as a @RestController, meaning that it is ready for use by Spring MVC to handle web requests. @RequestMapping maps / to the home() method, which sends a Hello World response. The main() method uses Spring Boot’s SpringApplication.run() method to launch an application. In Kotlin, runApplication is used instead.

Now we can run the application without the Docker container (that is, in the host OS):

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

Then go to localhost:8080 to see your “Hello Docker World” message.

Containerize It

Docker has a simple "Dockerfile" file format that it uses to specify the “layers” of an image. Create the following Dockerfile in your Spring Boot project:

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

Run the following command:

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 .

This command builds an image and tags it as springio/gs-spring-boot-docker.

This Dockerfile is very simple, but it is all you need to run a Spring Boot app with no frills: just Java and a JAR file: it copies (using the COPY command) the project JAR file into the container as app.jar, which is run in the ENTRYPOINT. The array form of the Dockerfile ENTRYPOINT is used so that there is no shell wrapping the Java process.

To reduce Tomcat startup time, we used to add a system property pointing to /dev/urandom as a source of entropy. This is not necessary anymore with JDK 8 or later.

Running applications with user privileges helps to mitigate some risks (see, for example, a thread on StackExchange). So, an important improvement to the Dockerfile is to run the application as a non-root user:

Example 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"]

You can see the username in the application startup logs when you build and run the application with the following commands:

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

Note the started by in the first INFO log entry:

 :: 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 /)
...

Also, there is a clean separation between dependencies and application resources in a Spring Boot fat JAR file, and we can use that fact to improve performance. The key is to create layers in the container filesystem. The layers are cached both at build time and at runtime (in most runtimes), so we want the most frequently changing resources (usually the class and static resources in the application itself) to be layered after the more slowly changing resources. Thus, we use a slightly different implementation of the 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"]

This Dockerfile has a DEPENDENCY parameter pointing to a directory where we have unpacked the fat JAR. To use the DEPENDENCY parameter, run the following command:

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

If we get that right, it already contains a BOOT-INF/lib directory with the dependency JARs in it, and a BOOT-INF/classes directory with the application classes in it. Notice that we use the application’s own main class: hello.Application in Java, or hello.ApplicationKt in Kotlin, since the top-level main function is compiled to an ApplicationKt class. (This is faster than using the indirection provided by the fat JAR launcher.)

Exploding the JAR file can result in the classpath order being different at runtime. A well-behaved and well-written application should not care about this, but you may see behavior changes if the dependencies are not carefully managed.

To build the image, run the following command (a Gradle build needs explicit build arguments in the Docker command line):

Gradle
docker build --build-arg DEPENDENCY=build/dependency -t springio/gs-spring-boot-docker .
Maven
docker build -t springio/gs-spring-boot-docker .
If you use only Gradle, you could change the Dockerfile to make the default value of DEPENDENCY match the location of the unpacked archive. This is what the Kotlin sample project, which is built with Gradle only, does, so the --build-arg DEPENDENCY=build/dependency argument is not needed with it.

Instead of building with the Docker command line, you might want to use a build plugin. Spring Boot supports building a container from Maven or Gradle by using its own build plugin. Google also has an open source tool called Jib that has Maven and Gradle plugins. Probably the most interesting thing about this approach is that you do not need a Dockerfile. You can build the image by using the same standard container format as you get from docker build. Also, it can work in environments where docker is not installed (not uncommon in build servers).

By default, the images generated by the default buildpacks do not run your application as root. Check the configuration guide for Gradle or Maven for how to change the default settings.

Build a Docker Image with the Spring Boot Build Plugin

You can build a tagged docker image in one command, without even changing your build configuration (remember that the Dockerfile, if it is still there, is ignored):

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

To push to a Docker registry, you need to have permission to push, which you do not have by default. Change the image prefix to your own Dockerhub ID and docker login to make sure you are authenticated before you run Docker.

After the Push

A docker push in the example fails (unless you are part of the "springio" organization at Dockerhub). However, if you change the configuration to match your own docker ID, it should succeed. You then have a new tagged, deployed image.

You do NOT have to register with docker or publish anything to run a docker image that was built locally. If you built with Docker (from the command line or from Spring Boot), you still have a locally tagged image, and you can run it like this:

$ 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)
The buildpack uses a memory calculator at runtime to size the JVM to fit the container.

The application is then available on http://localhost:8080 (visit that and it says, “Hello Docker World”).

When it is running, you can see in the list of containers, similar to the following example:

$ 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

To shut it down again, you can run docker stop with the container ID or name from the previous listing (yours will be different):

docker stop goofy_brown

If you like, you can also delete the container (it is persisted in your filesystem somewhere under /var/lib/docker) when you are finished with it:

docker rm goofy_brown

Using Spring Profiles

Running your freshly minted Docker image with Spring profiles is as easy as passing an environment variable to the Docker run command (for the prod profile):

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

You can do the same for the dev profile:

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

Debugging the Application in a Docker Container

To debug the application, you can use JPDA Transport. We treat the container like a remote server. To enable this feature, pass Java agent settings in the JAVA_OPTS variable and map the agent’s port to localhost during a container run. With Docker for Mac, there is a limitation because we can’t access the container by IP without black magic usage.

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

Summary

Congratulations! You have created a Docker container for a Spring Boot application! By default, Spring Boot applications run on port 8080 inside the container, and we mapped that to the same port on the host by using -p on the command line.

See Also

The following guides may also be helpful:

Want to write a new guide or contribute to an existing one? Check out our contribution guidelines.

All guides are released with an ASLv2 license for the code, and an Attribution, NoDerivatives creative commons license for the writing.

Get the Code