website/src/content/docs/self-hosting/railway.mdx
import { TutorialVideo } from '@/components/TutorialVideo';
Choose the template that best fits your needs:
You can also use the Rivet Railway template as a starting point for your application.
<Note> After deploying either template, you can find the `RIVET__AUTH__ADMIN_TOKEN` under the **Variables** tab in the Railway dashboard. This token is required to access the Rivet Inspector. </Note># Using Railway CLI
railway init
# Or create via dashboard
# https://railway.app/new
rivetdev/engine:latestRIVET__POSTGRES__URL=${{Postgres.DATABASE_URL}}Follow the Railway Quick Start guide to deploy your repository:
RIVET_ENDPOINT=${{Rivet.RAILWAY_PRIVATE_DOMAIN}} - Points to the Rivet Engine service's private domainRivet uses long-lived WebSocket connections for both client traffic (browsers, SDKs) and envoy traffic (actor hosts connecting back to the engine). Railway's HTTP proxy supports WebSockets, but you must make sure no app-side timeout cuts them off.
If you front the engine with your own reverse proxy (NGINX, Caddy, etc.) inside the Railway service, raise its idle / read timeout to at least 1 hour (3600 seconds). The same guidance applies to the Railway service hosting your RivetKit app, since envoys connect to it over WebSocket.
By default, Railway kills the old deploy 0 seconds after sending SIGTERM (see Railway's docs), so in-flight requests are dropped and state flushes can be interrupted on every deploy. Rivet ships with a SIGTERM handler that drains cleanly, but it only gets to run if Railway is configured to give it time.
Configure the following under Settings → Deploy on your service:
SIGTERM and SIGKILL. This is how long Rivet has to finish in-flight work and flush state. See Railway's deployment teardown docs.Set draining seconds in the dashboard, via drainingSeconds in config-as-code, or via the RAILWAY_DEPLOYMENT_DRAINING_SECONDS service variable. A reasonable value is 60 seconds.
If your start command is a wrapper process (for example npm start, yarn start, pnpm start, or a shell script), the wrapper becomes PID 1 and swallows the signal — your app never drains and Railway force-kills it at the end of the window.
node dist/index.js, not npm start).dumb-init / tini as PID 1 in your Dockerfile so signals forward to your process.