Deploy Spring Boot to Production: A Practical Guide

Khimananda Oli 6 min read Programming and Languages
Deploy Spring Boot to Production: A Practical Guide

By Khimananda Oli | Last reviewed: August 2026

Shipping Java applications requires more than just copying a JAR file to a server; you need a repeatable, secure, and observable pipeline. This article on how to deploy Spring Boot to production covers the essential layers from optimized container images to runtime configuration and zero-downtime strategies. Whether you are running on AWS EKS, Azure AKS, or bare metal, these patterns ensure your application survives real-world traffic and passes compliance audits.

Source CodeGit + TestsCI PipelineBuild & ScanRegistryImmutable ImageProductionK8s / VMSecrets Vault
Secure pipeline architecture to deploy Spring Boot to production with immutable artifacts and externalized secrets.

How do you optimize Docker images when you deploy Spring Boot to production?

The most common mistake I see teams make is shipping bloated Java containers that waste memory and increase attack surface. When you reduce Docker image size with multi-stage builds, you directly improve startup time and security posture. For Spring Boot 3.x and Java 21+, always separate the build environment from the runtime environment.

Multi-stage Dockerfile for Spring Boot

This pattern uses Eclipse Temurin for both stages to ensure binary compatibility while keeping the final image under 250MB. We also extract layers to leverage Docker caching effectively.

# Stage 1: Build
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
COPY . .
RUN ./gradlew bootJar --no-daemon

# Stage 2: Runtime
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar

# Extract layers for better caching
RUN java -Djarmode=layertools -jar app.jar extract

ENTRYPOINT ["java", \
  "-XX:+UseContainerSupport", \
  "-XX:MaxRAMPercentage=75.0", \
  "org.springframework.boot.loader.launch.JarLauncher"]
  • Alpine Base: Reduces OS-level vulnerabilities and image size by ~60% compared to Debian.
  • Layer Extraction: Spring Boot’s layer tools separate dependencies from application code, meaning code-only changes don’t invalidate the dependency cache.
  • No Root User: Always add a non-root user in production containers to limit blast radius if compromised.

What JVM settings are critical when you deploy Spring Boot to production?

Java inside a container behaves differently than on bare metal. Without explicit tuning, the JVM may misinterpret cgroup limits, leading to OOM kills even when heap usage appears low. Modern JVMs (Java 17+) handle container awareness automatically, but you must still configure memory ceilings explicitly.

Container Limit: 2GB RAMJVM Heap (MaxRAMPercentage=75%)Used HeapFree HeapNon-HeapMetaspaceThreadsDirect BuffersGC Overhead⚠ Never set Xmx = Container Limit
JVM memory layout showing why MaxRAMPercentage is safer than fixed Xmx values in containerized deployments.

Avoid hardcoded -Xmx values. If your pod limit changes but the flag doesn't, you risk immediate crashes. Use percentage-based allocation instead.

JAVA_OPTS="\
  -XX:+UseContainerSupport \
  -XX:MaxRAMPercentage=75.0 \
  -XX:InitialRAMPercentage=50.0 \
  -XX:+ExitOnOutOfMemoryError \
  -Djava.security.egd=file:/dev/./urandom"

The ExitOnOutOfMemoryError flag is non-negotiable in Kubernetes. Without it, a JVM might stay alive in a broken state after an OOM event, passing liveness checks while failing every request. Let the orchestrator restart the pod cleanly. For deeper performance analysis, refer to the four golden signals of monitoring to track saturation and errors effectively.

How do you manage configuration and secrets securely?

Never bake credentials into your Docker image or commit application-prod.yml to Git. When you deploy Spring Boot to production, configuration should be injected at runtime. This separates the artifact from the environment, allowing the same verified image to move through dev, staging, and prod.

MethodBest ForSecurity LevelComplexity
Environment VariablesSimple configs, feature flagsModerateLow
Kubernetes ConfigMapsNon-sensitive app propertiesModerateMedium
Kubernetes SecretsDB passwords, API keysHigh (with encryption)Medium
HashiCorp Vault / AWS Secrets ManagerDynamic secrets, rotation, auditHighestHigh

Spring Cloud Kubernetes Integration

For Kubernetes-native deployments, use property sources that map directly to ConfigMaps and Secrets without extra sidecars.

# application.yml
spring:
  config:
    import:
      - optional:kubernetes:
  cloud:
    kubernetes:
      config:
        enabled: true
        sources:
          - name: my-app-config
      secrets:
        enabled: true
        sources:
          - name: my-app-secrets

This approach keeps your Spring Boot application decoupled from infrastructure specifics while maintaining proper Kubernetes secrets hygiene. Always enable RBAC restrictions so pods can only read their own namespace's secrets.

How do you implement health checks and graceful shutdown?

Load balancers and orchestrators need accurate signals to route traffic safely. Spring Boot Actuator provides standardized endpoints, but default configurations often cause issues during rolling deployments. You must distinguish between "alive" and "ready to serve traffic."

Actuator Configuration for Production

management:
  endpoint:
    health:
      probes:
        enabled: true
      show-details: never
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  server:
    port: 8081

Separating the management port (8081) from the application port (8080) prevents external users from accessing internal metrics. The probes.enabled=true setting exposes /actuator/health/liveness and /actuator/health/readiness specifically for Kubernetes.

Graceful Shutdown Settings

Without graceful shutdown, in-flight requests fail during deployments. Enable this to allow active connections to complete before the JVM terminates.

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

Pair this with a preStop hook in Kubernetes that sleeps for 5–10 seconds. This accounts for the delay between the pod being marked terminating and the load balancer actually stopping traffic. Skipping this step causes intermittent 502 errors during every deployment.

KubernetesSpring BootLoad BalancerSIGTERM SignalReadiness Probe FailsRemove from PoolComplete Active RequestsProcess Exits Cleanly
Graceful shutdown sequence preventing request loss when you deploy Spring Boot to production with rolling updates.

How do you ensure observability after deployment?

You cannot fix what you cannot see. Production Spring Boot applications require structured logging, metrics, and distributed tracing from day one. Relying solely on console logs makes debugging impossible at scale.

OpenTelemetry Integration

Modern Spring Boot versions support OpenTelemetry natively. This gives you vendor-neutral instrumentation that works with Jaeger, Tempo, or Datadog.

# build.gradle.kts
implementation("io.micrometer:micrometer-tracing-bridge-otel")
implementation("io.opentelemetry:opentelemetry-exporter-otlp")

# application.yml
management:
  otlp:
    metrics:
      export:
        url: http://otel-collector:4318/v1/metrics
    tracing:
      export:
        url: http://otel-collector:4318/v1/traces
  tracing:
    sampling:
      probability: 0.1

Start with 10% sampling in production to control costs, then adjust based on traffic volume. Combine this with structured logging best practices to correlate log entries with trace IDs automatically. Every log line should include trace_id and span_id fields for seamless debugging across microservices.

Deploy Spring Boot to Production With Confidence

Successfully running Spring Boot in production demands attention to container optimization, memory management, secure configuration, and observability. By following these patterns—multi-stage builds, percentage-based JVM tuning, externalized secrets, proper health probes, and native OpenTelemetry—you build systems that are resilient, auditable, and maintainable. Start with the Dockerfile and JVM flags today; they deliver immediate stability gains. If your team needs help designing a compliant, production-grade Java platform, reach out to discuss your architecture.

Frequently Asked Questions

Use executable JARs with layered dependencies for faster container builds. Avoid WAR files unless deploying to legacy servlet containers. The spring-boot-maven-plugin generates optimized artifacts that include embedded Tomcat, reducing external configuration and improving startup consistency across environments.

Externalize settings using Spring Profiles and environment variables. Mount config maps or secrets at runtime instead of baking values into the JAR. This allows identical binaries across dev, staging, and production while keeping sensitive data out of version control and build artifacts.

Enable container awareness with -XX:+UseContainerSupport and set memory limits via -XX:MaxRAMPercentage=75.0. These flags prevent OOMKilled errors by respecting cgroup limits. In 2026, JDK 21+ handles this automatically, but explicit tuning ensures predictable garbage collection behavior under load.

Yes, for serverless or high-density deployments. GraalVM native images reduce startup time to milliseconds and cut memory usage by sixty percent. However, build times increase significantly and reflection-heavy libraries require extra configuration. Evaluate based on your specific scaling and cost requirements.

Expose /actuator/health/liveness and /actuator/health/readiness endpoints. Configure liveness probes to detect deadlocks and readiness probes to verify database connectivity. Set appropriate initial delays to prevent premature restarts during slow startups. Never use the same endpoint for both probe types in production clusters.

Output structured JSON logs to stdout and let the platform handle aggregation. Avoid file-based logging in containers. Include trace IDs and correlation fields for request tracking. Tools like Fluent Bit or Vector can enrich and route logs to Elasticsearch or cloud-native observability backends efficiently.

Use HashiCorp Vault, AWS Secrets Manager, or Kubernetes Secrets with encryption at rest. Inject credentials as environment variables or mounted files at runtime. Never store plaintext secrets in application properties or Git repositories. Rotate credentials automatically and audit access patterns regularly.

Component scanning overhead and eager bean initialization are common culprits. Use lazy initialization for non-critical beans, enable class data sharing, or adopt native compilation. Profile startup with Spring Startup Actuator to identify bottlenecks. Caching bean definitions and reducing auto-configuration also helps significantly.

Embedded servers are preferred for microservices and cloud-native deployments. They simplify packaging, reduce configuration drift, and align with twelve-factor principles. External Tomcat only makes sense for legacy infrastructure or shared hosting environments where operational standards mandate traditional servlet container management.

Enable server.shutdown=graceful and set spring.lifecycle.timeout-per-shutdown-phase appropriately. This allows in-flight requests to complete before termination. Combine with Kubernetes preStop hooks and proper SIGTERM handling to prevent dropped connections during rolling updates or autoscaling events.

Track HTTP request latency percentiles, error rates, JVM heap usage, and connection pool saturation. Enable Micrometer with Prometheus registry. Alert on SLO violations rather than raw thresholds. Business metrics like order processing rate often provide earlier warning signals than infrastructure metrics alone.

Yes. Systemd services with executable JARs work well for single-server deployments. Use Ansible or Terraform for configuration management. This approach reduces operational complexity for small teams while maintaining production-grade reliability through proper process supervision and log rotation.

Size HikariCP pools based on actual concurrency needs, not CPU cores. Monitor active versus idle connections and adjust maximum-pool-size accordingly. Enable connection validation and leak detection. Over-provisioning wastes resources while under-provisioning causes request queuing and timeout cascades during peak loads.

Terminate TLS at the ingress controller when possible. If application-level TLS is required, use PEM certificates with automatic rotation via cert-manager. Disable legacy protocols and weak cipher suites. Prefer mutual TLS for service-to-service communication within zero-trust network architectures.

Capture heap dumps during high-memory periods using jcmd or async-profiler. Analyze with Eclipse MAT to identify retained object graphs. Check for unclosed resources, cache misconfigurations, and listener accumulation. Enable NativeMemoryTracking for off-heap issues. Reproduce locally with production-like data volumes before applying fixes.