Deploy Spring Boot | Java 25 API in a Two-Stage Container
Spring Boot 4 on Java 25, two-stage image, bound to the platform port
Just deployed
Deploy and Host a Spring Boot API on Railway
Spring Boot 4 on Java 25, in a two-stage container, listening on the port the platform assigns.
About Hosting Spring Boot
The Spring Boot template on Railway builds from a repository last updated in October 2024, and about four deployments in ten never come up. Two things in it are worth fixing.
The port is never configured. Its application.properties contains a single line - the application name. Spring then listens on its own default rather than on the port the platform hands the container, and whether that works is left to autodetection. Here the properties file says server.port=${PORT:8080}.
The container is a single stage. Its Dockerfile builds with a full JDK and then runs that same image, so the deployed container carries the JDK, the Maven cache and the sources. This one builds in a Maven image and copies only the jar into a JRE image - 347 MB, with nothing in it that only the build needed.
Common Use Cases
- A JSON API with the Spring ecosystem behind it - data, security, messaging
- A service that has to run on the JVM because the rest of the stack does
- A starting point where the container, port and health check are already right
Dependencies for Spring Boot Hosting
- Nothing. One service, no database. Add spring-boot-starter-data-jpa and a Postgres service when you need persistence.
Deployment Dependencies
- Spring Boot: https://docs.spring.io/spring-boot/index.html
- Source: https://github.com/ak40u/spring-boot-railway-starter
Implementation Details
| Method | Path | Does |
|---|---|---|
| GET | / | Lists the endpoints |
| GET | /actuator/health | Reports UP once the application context is ready |
| GET | /greet?name=world | Greets, 400 when the parameter is missing |
Four decisions worth keeping if you rewrite this:
- Dependencies resolve before the sources are copied. pom.xml goes into the image first and
dependency:go-offlineruns against it alone, so editing a .java file rebuilds in seconds rather than re-downloading every dependency. -XX:MaxRAMPercentage=75. Left alone the JVM sizes its heap from the host machine memory, not the container limit, and gets killed for allocating past it.- Graceful shutdown is on. The platform sends SIGTERM before replacing a container; without
server.shutdown=gracefulin-flight requests are dropped on every deploy. - Actuator exposes only health, without details. The detailed view reports disk paths and component internals to anyone who can reach the domain.
Why Deploy Spring Boot on Railway?
A JVM service with a domain, TLS and a health check from one deploy, and an image small enough that a redeploy is not a coffee break.
Template Content