Deploy spring-boot-railway-template
The Java starter that deploys: Spring Boot 4.1 + Postgres in one click
Just deployed
/var/lib/postgresql/data
Just deployed
Spring Boot on Railway — the Java starter that actually deploys
One click gives you a production-shaped Spring Boot 4.1 (Java 25 LTS) application wired to a PostgreSQL 18 database — with schema migrations, a real REST API, and a healthcheck that goes green on the first deploy. No port errors, no out-of-memory crashes, no version surprises: every classic Railway-Java failure mode is already handled in this template.
What's inside
| Service | What it runs |
|---|---|
app | Spring Boot 4.1.1 on Java 25 (Eclipse Temurin), built by a multi-stage Dockerfile (maven:3.9-eclipse-temurin-25 → eclipse-temurin:25-jre) |
Postgres | Railway's official postgres-ssl:18 image with a persistent volume |
The app service ships a working reference application, not a hello-world stub:
- REST API for a todo list (
GET/POST/PATCH/DELETE /api/todos) on Spring Data JPA - Flyway migration (
V1__init.sql) that creates the schema on first boot - Spring Boot Actuator with
/actuator/healthwired as Railway's healthcheck - Graceful shutdown so redeploys don't drop in-flight requests
Everything is pinned: Java 25 LTS in both Docker stages, Spring Boot 4.1.1 in pom.xml, the
Postgres 18 image tag. Nothing floats, so a deploy that works today works next month.
Railway wiring (done for you)
SERVER_PORT = ${{PORT}}binds the JVM to Railway's injected port; the app falls back gracefully (server.port=${SERVER_PORT:${PORT:8080}}inapplication.properties).- JVM memory flags in the Dockerfile entrypoint:
-XX:MaxRAMPercentage=75.0 -XX:+ExitOnOutOfMemoryError— the heap scales with the container and never fights the OOM killer. - Healthcheck:
/actuator/health, 300 s timeout, restart-on-failure (×10). - Database credentials are auto-generated and injected via variable references —
SPRING_DATASOURCE_URL=jdbc:postgresql://${{Postgres.PGHOST}}:${{Postgres.PGPORT}}/${{Postgres.PGDATABASE}},SPRING_DATASOURCE_USERNAME=${{Postgres.PGUSER}},SPRING_DATASOURCE_PASSWORD=${{Postgres.PGPASSWORD}}. Zero hardcoded secrets.
Deploy and Host
About Hosting
Deploying provisions two services: the Spring Boot app (built from the linked GitHub repo with
Docker) and a Postgres database with persistent storage. The first build runs a full Maven
build, so expect roughly 3–5 minutes before the healthcheck goes green; subsequent deploys are
faster thanks to Docker layer caching. Hosting cost scales with usage — a typical small instance
runs the JVM at 0.5–1 GB of RAM, which lands around $5–10/month including the Postgres
service. After deploy, Railway assigns a public domain; open it to see the app metadata JSON,
then try curl https://YOUR-DOMAIN/api/todos.
Why Deploy
Because every other path to Java on Railway tends to fail in one of four ways, and this template fixes all four before you click:
- Port mismatch — Spring defaults to 8080 while Railway expects the app to listen on its
injected
PORT. This template bindsSERVER_PORTto${{PORT}}and the domain targets it. - OOM kills — a JVM with default heap settings on a small container gets killed by the
kernel.
MaxRAMPercentage=75keeps the heap inside the container. - Failed healthchecks — Railway needs an endpoint that actually reports readiness.
Actuator's
/actuator/healthis wired as the deploy gate. - Stale versions — the old Spring Boot templates on Railway pin Boot 3.3.x and a single-stage alpine build. This one pins the current GA line (Boot 4.1.1) and the current LTS (Java 25) in a cached multi-stage build.
You also get a database that's actually wired: Flyway migrates on boot, and data survives redeploys because it lives in Postgres, not in the container.
Common Use Cases
- A real starting point for a Spring Boot service on Railway — swap the
todopackage for your own entities and controllers, keep the wiring, push to deploy. - REST API backends for web and mobile apps, with validated inputs and a migrated schema.
- Enterprise-style reference architecture — JPA + Flyway + actuator is the layout most Java teams already know; use it as a template for internal services.
- Learning Spring Boot — the repo has a README with local-dev instructions, a test suite that runs without a database, and CI (GitHub Actions) building on every push.
Dependencies for
Deployment Dependencies
- A Railway account (the deploy button handles project creation).
- The GitHub repository lNamelessl/spring-boot-railway-template — public, MIT-licensed; the
appservice builds from it. - The
Postgresservice is provisioned by the template itself (officialpostgres-ssl:18image); no external database is needed. - No deploy-form inputs: all variables are pre-wired references, database credentials are generated per deployment, and there is nothing to type.
Troubleshooting
- Healthcheck fails on first deploy — the first build is the slow one; check the deploy logs
for
Started …Application. If the app started but the check still fails, confirm the domain's target port matchesSERVER_PORT(8080 by default). Connection refusedto Postgres at boot — the app can start before Postgres finishes booting on the very first deploy. The template sets a 60 s connection-patient Hikari timeout and restarts on failure, so it self-heals; press Redeploy once if you're impatient.- Killed / exit 137 — the JVM outgrew the container. Lower the heap via a
JAVA_OPTSservice variable or add memory in the service settings. - Build timeout — free-plan builds can time out during the first dependency download. Redeploy: Docker layer caching resumes where the build left off.
- Flyway errors after you change the schema — never edit an applied migration; add a new
V{n}__*.sqlfile instead.
Bring your own app
Fork the repo, replace the todo package with your own code, add your migrations as
V2__…, V3__…, and keep four things untouched: the port line in application.properties, the
healthcheck lines, the Dockerfile, and railway.json. Push to main — Railway redeploys
automatically.
Template Content
PGDATA
PGPORT
POSTGRES_DB
POSTGRES_USER
SSL_CERT_DAYS
RAILWAY_DEPLOYMENT_DRAINING_SECONDS
