Back to Blog
Tutorial10 min read2026-07-01

How to Deploy a Spring Boot App to Production

Deploy Spring Boot to production with a layered Docker image, JVM container settings that actually respect memory limits, Actuator health probes, externalized config, and a managed database.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Spring Boot produces a self-contained executable JAR, which makes deployment conceptually simple — but the JVM and container memory limits don't always get along, and Spring's config layering catches people off guard. This guide covers a production-ready containerized deployment.

Build an efficient image with layered JARs

A naive COPY app.jar works but rebuilds the whole layer on every code change. Spring Boot supports layered JARs that separate dependencies (rarely change) from your code (changes constantly), giving you far better Docker layer caching.

# --- build stage ---
FROM eclipse-temurin:21-jdk AS build
WORKDIR /app
COPY . .
RUN ./mvnw -DskipTests clean package
RUN java -Djarmode=layertools -jar target/*.jar extract

# --- runtime stage ---
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/dependencies/ ./
COPY --from=build /app/spring-boot-loader/ ./
COPY --from=build /app/snapshot-dependencies/ ./
COPY --from=build /app/application/ ./
EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

Use a JRE (not JDK) base for the runtime to keep the image lean.

The JVM-in-a-container memory trap

For years the JVM ignored cgroup memory limits and sized its heap based on the host's total RAM — leading to OOM kills inside containers. Modern JVMs (11+) are container-aware, but you should still set heap limits explicitly:

JAVA_OPTS=-XX:MaxRAMPercentage=75.0

MaxRAMPercentage tells the JVM to use up to 75% of the container's memory limit for the heap, leaving headroom for metaspace, thread stacks, and off-heap buffers. Setting a fixed -Xmx works too, but the percentage adapts when you resize the container.

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"]

Externalized configuration

Spring Boot reads config from many sources with a defined precedence — environment variables override application.properties. Use that to keep secrets out of your JAR:

spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.hibernate.ddl-auto=validate

Set ddl-auto to validate (or none) in production — never update or create, which mutate your schema unpredictably. Use Flyway or Liquibase for migrations instead.

Health probes with Actuator

Spring Boot Actuator gives you liveness and readiness endpoints for free:

management.endpoint.health.probes.enabled=true
management.endpoints.web.exposure.include=health,info

This exposes /actuator/health/liveness and /actuator/health/readiness. Wire readiness into your platform's probe so traffic only routes once Spring has fully started and the database is reachable.

Migrations with Flyway

Drop SQL files in src/main/resources/db/migration named V1__init.sql, V2__add_users.sql, etc. Flyway runs them on startup and tracks applied versions. For larger teams, run Flyway as a discrete release step so app replicas don't race.

Deploying on PandaStack

  1. 1Create a PostgreSQL or MySQL database; DATABASE_URL (and credentials) are injected automatically.
  2. 2Connect your repo as a container app. PandaStack detects the Dockerfile and builds via rootless BuildKit; or buildpacks detect Maven/Gradle and Java if you skip the Dockerfile.
  3. 3Set JAVA_OPTS, SPRING_PROFILES_ACTIVE=production, and DB env vars in the dashboard.
  4. 4Push — live build logs stream and you get automatic SSL.
ConcernProduction setting
ImageLayered JAR, JRE runtime
Heap-XX:MaxRAMPercentage=75.0
Schemaddl-auto=validate + Flyway
ProbesActuator liveness/readiness
ProfileSPRING_PROFILES_ACTIVE=production

Common pitfalls

  • ddl-auto=update in production — silent, unpredictable schema drift.
  • No heap limit — OOM kills under container memory pressure.
  • Exposing all Actuator endpoints/actuator/env and /actuator/heapdump leak data; expose only what you need.
  • JDK in the runtime image — bloated image; use a JRE.

References

  • Spring Boot container deployment: https://docs.spring.io/spring-boot/reference/packaging/container-images/dockerfiles.html
  • Spring Boot Actuator: https://docs.spring.io/spring-boot/reference/actuator/index.html
  • JVM container awareness (OpenJDK): https://www.eclipse.org/openj9/docs/xxusecontainersupport/
  • Flyway documentation: https://documentation.red-gate.com/flyway
  • Eclipse Temurin images: https://hub.docker.com/_/eclipse-temurin

---

PandaStack's free tier includes container apps and a managed database with DATABASE_URL injected automatically — push your Spring Boot repo and it builds, deploys, and connects. Get started at https://dashboard.pandastack.io

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also