Docs

Documentation versions (currently viewingVaadin 25)
Documentation translations (currently viewingEnglish)

Build a Container Image

Package a Vaadin application in a container image, and give the JVM enough memory.

A container image packages the JAR from Build for Production together with a Java runtime. The following Dockerfile builds an image from the JAR in the target directory:

Source code
Dockerfile
FROM eclipse-temurin:25-jre AS builder
WORKDIR /builder
COPY target/*.jar application.jar
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted

FROM eclipse-temurin:25-jre
RUN useradd --system app
USER app
WORKDIR /application
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "application.jar"]

Build the JAR first, then the image, and run it with a memory limit:

Source code
terminal
./mvnw clean package
docker build -t my-app .
docker run -p 8080:8080 -m 2g -e SPRING_PROFILES_ACTIVE=prod my-app

How the Dockerfile Works

The first stage splits the JAR into layers, as described in the Spring Boot documentation. The dependencies, which include Vaadin, end up in their own image layer. They change less often than your code, so rebuilding and pulling the image after a code change is faster.

The second stage runs the application as an unprivileged user, and raises the share of memory that the JVM may use for the heap, as explained in the next section. The ENTRYPOINT starts Java directly, without a shell in between, so that Java receives the stop signal from the container platform.

Give the JVM Enough Memory

Unless told otherwise, the JVM uses at most a quarter of the container’s memory for the heap. In a container with 2 GB of memory, the heap gets 512 MB, even when the application needs more, and the container has memory to spare.

A Vaadin application keeps the UI state of every user in the heap, so give the heap most of the memory. With -XX:MaxRAMPercentage=75, the same container gets a 1.5 GB heap, and the rest remains for the memory that the JVM needs outside the heap.

Always run the container with a memory limit, such as the -m option of docker run, or a memory limit in Kubernetes. Without a limit, the JVM sizes its heap according to the memory of the whole host.

How much memory your application needs depends on how many sessions it holds, and on what its views keep in memory. Measure Memory per Session shows how to find out.

To pass other JVM options without rebuilding the image, set the JAVA_TOOL_OPTIONS environment variable on the container. The JVM reads its options from it.

Build Inside Docker Instead

Projects from start.vaadin.com come with a Dockerfile that runs the Maven build inside Docker, so the machine that builds the image doesn’t need Java. If the application uses commercial features, the build needs a license key, as explained in When the Build Needs a License Key. As the comments in that Dockerfile describe, pass the key as a build secret, which isn’t stored in the image:

Source code
terminal
docker build --secret id=offlineKey,src=$HOME/.vaadin/offlineKey -t my-app .

This Dockerfile runs the application as the root user. Consider adding an unprivileged user, as in the example at the top of this page.

If you build the JAR on a CI server anyway, as described in Build on CI, building the image from that JAR avoids building the application twice.

Stop Containers Gracefully

When a container platform stops a container, it first sends the application a stop signal, and Spring Boot shuts down gracefully. Open server push connections don’t delay the shutdown. The users on the stopped instance lose their UI state, though. Test What Users See When a Server Stops shows what this looks like, and compares the ways to handle it: accepting the reload, draining the instance, and replicating sessions.

The Spring Boot documentation explains how to delay the shutdown on Kubernetes, so that no new requests are routed to a pod that’s already shutting down.

For more about running Vaadin applications in Docker, see Docker Deployment.

Next Steps

In production, a reverse proxy usually sits in front of the container, and terminates TLS. Deploy Behind a Proxy shows how to configure it for Vaadin.