The two things that define Spring Boot hosting
Spring Boot is productive and battle-tested, but two characteristics dominate the hosting conversation:
- 1Memory. The JVM plus the Spring context is heavier than a Go binary or a Node process. You provision more RAM, and you tune the heap.
- 2Startup time. A traditional Spring Boot JAR can take several seconds to boot. That matters a lot for scale-to-zero and autoscaling, and it's why GraalVM native images exist.
Get those two right and Spring Boot hosts cleanly as a standard container.
JAR vs. native image
| Executable JAR | GraalVM native image | |
|---|---|---|
| Startup | Seconds | Tens of milliseconds |
| Memory | Higher | Much lower |
| Build time | Fast | Slow (AOT compilation) |
| Reflection/dynamic | Full support | Needs config/hints |
| Best for | Most apps | Serverless, scale-to-zero, dense packing |
Spring Boot 3.x supports GraalVM native images well via AOT processing. If you're chasing fast cold starts (serverless or scale-to-zero), native is compelling. If you rely on heavy reflection or libraries without native hints, the JAR is the safer, simpler path.
The container build
For JARs, layered images cache dependencies separately from your code:
FROM eclipse-temurin:21-jre AS run
WORKDIR /app
COPY target/app.jar app.jar
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75 -XX:+UseG1GC"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]The single most important JVM-in-container flag is -XX:MaxRAMPercentage — let the JVM size its heap as a percentage of the container's memory limit rather than guessing a fixed -Xmx. Modern JVMs are container-aware, but setting this explicitly avoids surprises.
The platforms
AWS / Azure / Google Cloud
The enterprise default for Spring Boot. Elastic Beanstalk, App Runner, Azure App Service, and Cloud Run all run Spring Boot containers, and the big clouds have the managed databases, secrets, and networking enterprises need. The trade-off is operational complexity and cost management.
Railway and Render
Both deploy Spring Boot containers on push with managed Postgres/MySQL alongside. Much simpler than the hyperscalers for small-to-medium apps. Watch memory tiers — Spring Boot needs more headroom than a typical Node app on the same plan.
Heroku
Mature JVM support with a Java buildpack and add-on databases. Comfortable and well-documented, if not the cheapest.
PandaStack
Disclosure: my platform. PandaStack runs Spring Boot as a container app. Bring a Dockerfile (recommended so you control JVM flags) and connect the repo — the build runs with rootless BuildKit in an ephemeral K8s Job and deploys via Helm. Attach a managed PostgreSQL or MySQL and the connection details are injected as env vars, which Spring reads via SPRING_DATASOURCE_URL (map the injected DATABASE_URL in your config).
For cold-start-sensitive deployments, a GraalVM native image pairs nicely with PandaStack's scale-to-zero free tier — native startup makes idle-down/idle-up far less painful. You also get cronjobs (for @Scheduled-style work you'd rather externalize), live logs, and automatic SSL.
Honest limits: Spring Boot's JVM memory means the smallest free tier (0.25 CPU / 512MB) is tight for a full JAR — you'll likely want a memory-optimized tier (m1/m2) for comfortable production. PandaStack is a newer platform with a growing ecosystem, and free-tier apps cold-start on preemptible nodes (less painful with native images).
Memory and tuning notes
- Use
-XX:MaxRAMPercentage=75rather than fixed-Xmxin containers. - Prefer memory-optimized tiers for JAR-based Spring Boot; CPU is rarely the bottleneck, RAM is.
- Externalize config via env vars and Spring profiles; never bake secrets into the image.
- Use the Spring Boot Actuator
/actuator/healthendpoint for liveness/readiness probes. - Run DB migrations (Flyway/Liquibase) as a controlled step; Spring can run them on startup, but coordinate across replicas.
When to reach for native
Go native if: you want sub-second cold starts, you're scaling to zero, or you're packing many small services densely. Stay on the JAR if: you depend on reflection-heavy libraries, your build pipeline can't absorb slow AOT builds, or startup time simply isn't on your critical path.
References
- [Spring Boot deployment docs](https://docs.spring.io/spring-boot/reference/deployment/index.html)
- [Spring Boot GraalVM native images](https://docs.spring.io/spring-boot/reference/packaging/native-image/index.html)
- [Spring Boot container images](https://docs.spring.io/spring-boot/reference/packaging/container-images/index.html)
- [JVM container awareness (MaxRAMPercentage)](https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html)
- [Spring Boot Actuator](https://docs.spring.io/spring-boot/reference/actuator/index.html)
---
Running Spring Boot with a managed Postgres or MySQL wired in automatically? PandaStack's free tier is a quick way to try it — bump to a memory tier when you go to production. Start at [dashboard.pandastack.io](https://dashboard.pandastack.io).