Railway
Railway auto-detects Node projects from package.json. A config file is optional; add one only when you want to pin the start command, set a health check, run pre-deploy migrations, or switch to a Dockerfile-based build.
When to choose Railway
- You want the lowest-config push-and-it-runs experience on Node.
- You want managed Postgres, Redis, or MySQL in the same project.
- You like environment-per-PR with usage-based billing.
Server entrypoint
The scaffolded create-daloy templates already ship this as src/index.ts (the only file that opens a port). It builds the app from the pure buildApp() factory and starts the Node adapter:
railway.json
Or, equivalently, railway.toml:
Deploy
Trust Railway's edge proxy
Railway terminates TLS and proxies every request, adding X-Forwarded-For / X-Forwarded-Proto headers. In production DaloyJS refuses to trust forwarded headers until you declare the proxy posture: an app with no posture set returns 500 https://daloyjs.dev/errors/internal on the first request that carries an X-Forwarded-* header (which, behind Railway, is every request). This is deliberate, a misconfigured proxy chain must not silently feed spoofable client IPs to rate-limiting, request-id propagation, and audit logs.
Railway is exactly one proxy hop, so declare it. The scaffolded templates read TRUST_PROXY_HOPS into behindProxy: { hops: 1 }, so set the service variable:
- 01inboundClient requestto Railway edge
- 02Railway edge proxyadds X-Forwarded-For / -Proto
- 03Posture undeclared500 errors/internal
- 04TRUST_PROXY_HOPS=1behindProxy: { hops: 1 }
- 05Real client IP resolvedright-most XFF entry
Now DaloyJS reads the real client IP from the right-most X-Forwarded-For entry and rejects spoofed extra hops, the guard is satisfied rather than disabled. If you put Cloudflare (or another proxy) in front of Railway, that is two hops, set TRUST_PROXY_HOPS=2. See the deployment overview for the full posture matrix.
Public API URL
The templates leave the OpenAPI servers list unset by default, so the Scalar Try it panel calls the origin the docs are served from (your Railway domain in production, localhost in dev). That keeps Try it within the connect-src 'self' CSP automatically, with no env var to set. Set PUBLIC_URL only if you want to pin an absolute base URL in the spec (for example, to generate a typed client against a fixed environment):
Gotchas
- Don't override
PORT. Railway injects it and the load balancer targets that port. - Set
TRUST_PROXY_HOPS=1or every route returns500in production (see above). It is the single most common first-deploy surprise. healthcheckTimeoutis in seconds. Make it longer than your slowest legitimate startup.- Use
preDeployCommandfor migrations so schema changes run before traffic shifts.