Railway
`mandu deploy --target=railway` emits a `railway.json` + `nixpacks.toml` pair tuned for Bun + Mandu. With `--execute` and `railway ≥ 3.0.0` installed, Mandu triggers a deploy.
On this page
Railway
mandu deploy --target=railway emits a railway.json and a
nixpacks.toml — the two files Railway reads to understand your
project. Uses Nixpacks (Railway's default builder) for a zero-config
build.
# Emit artifacts only
mandu deploy --target=railway
# Set up the API token
mandu deploy --target=railway --set-secret RAILWAY_TOKEN=rw_...
# First-time link
railway link
# Full deploy (requires railway CLI + token)
mandu deploy --target=railway --execute
Prerequisites
# Install Railway CLI
bun add -g @railway/cli
# Log in (once)
railway login
Minimum version: railway 3.0.0. Older versions exit with CLI_E206.
Emitted files
.
├── railway.json
└── nixpacks.toml
Both are idempotent. Re-running mandu deploy --target=railway
produces byte-identical output.
The railway.json
{
"$schema": "https://railway.app/railway.schema.json",
"build": {
"builder": "NIXPACKS"
},
"deploy": {
"startCommand": "bun run start",
"healthcheckPath": "/healthz",
"healthcheckTimeout": 30,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 3
}
}
The nixpacks.toml
# nixpacks.toml
[phases.setup]
nixPkgs = ["bun"]
[phases.install]
cmds = ["bun install --frozen-lockfile"]
[phases.build]
cmds = ["bun run build"]
[start]
cmd = "bun run start"
[variables]
PORT = "3333"
NODE_ENV = "production"
Railway picks up nixpacks.toml at the project root and uses it to
drive its build. The [phases.setup] entry pulls in Bun from Nix
packages — no Docker image dance required.
First deploy
Railway expects a one-time railway link to bind this directory to a
project:
# Link to a project (or create one)
railway link
# Deploy via Mandu
mandu deploy --target=railway --execute
Alternatively run railway up directly — railway.json and
nixpacks.toml are standard Railway files.
Required secrets
| Name | Purpose | Store |
|---|---|---|
RAILWAY_TOKEN |
railway CLI auth for non-interactive deploys | Bun.secrets → OS keychain |
Set app-runtime env vars through the Railway CLI or dashboard, NOT
--set-secret:
railway variables set DATABASE_URL="postgres://..."
railway variables set SESSION_SECRET="$(openssl rand -hex 32)"
railway variables set JWT_SECRET="$(openssl rand -hex 32)"
These are stored encrypted in Railway's control plane and injected at runtime.
Database addon
Railway's Postgres addon auto-injects DATABASE_URL into the service
environment. From the Railway dashboard:
- Click New → Database → Add PostgreSQL.
- Attach it to your Mandu service (drag the reference).
- Redeploy —
DATABASE_URLflows in automatically.
Then inside your Mandu app:
// src/server/db.ts
import { sql } from "bun";
export const db = sql(process.env.DATABASE_URL!);
Environment variables (convention)
| Variable | Source | Purpose |
|---|---|---|
PORT |
nixpacks.toml / Railway | Listener port (defaults to 3333) |
NODE_ENV |
nixpacks.toml | production (cookie secure flag, etc.) |
DATABASE_URL |
Railway addon | Primary DB |
SESSION_SECRET |
railway variables set |
Session middleware secret |
JWT_SECRET |
railway variables set |
Auth middleware secret |
Cold start
Railway doesn't scale to zero by default — services run continuously while the project is active. Typical cold start after a deploy:
- Build: ~60–120s (Nixpacks cache helps subsequent deploys)
- Warm start: ~800ms (Bun cold boot)
- Per-request: ~30–80ms (SSR + DB)
Common errors
CLI_E205: required provider CLI railway is missing — install
with bun add -g @railway/cli.
CLI_E206: railway 2.x is older than required minimum 3.0.0 —
run bun add -g @railway/cli@latest.
CLI_E207: required secret RAILWAY_TOKEN is not present — run
mandu deploy --target=railway --set-secret RAILWAY_TOKEN=rw_....
Build fails on bun: command not found — check nixpacks.toml:
[phases.setup] must include nixPkgs = ["bun"]. The emitted file
does this correctly.
Deploy succeeds but healthcheck fails — confirm /healthz is
reachable. Mandu exposes it by default; override the path in
railway.json if your app mounts health on a different route.
🤖 Agent Prompt
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/railway to my project.
Summary of the page:
Railway adapter emits railway.json (metadata) + nixpacks.toml (build recipe). Nixpacks installs Bun, runs `bun install --frozen-lockfile`, then `bun run build`. Start command is `bun run start`. Secret: `RAILWAY_TOKEN`.
Required invariants — must hold after your changes:
- Uses Nixpacks (Railway's default builder) — not a custom Dockerfile
- Nixpacks installs Bun during the build step — no pre-installed runtime assumed
- Required secret: `RAILWAY_TOKEN` — stored in Bun.secrets
- Minimum railway CLI version: 3.0.0
- App env vars are set via `railway variables set`, not `--set-secret`
Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.
Related
- Deploy index — full adapter matrix.
- CLI —
db seed— seeding fixtures on Railway's Postgres addon.
For Agents
{
"schema": "mandu.deploy.railway/v0.24",
"command": "mandu deploy --target=railway",
"artifacts": ["railway.json", "nixpacks.toml"],
"builder": "NIXPACKS",
"provider_cli": { "binary": "railway", "min_version": "3.0.0" },
"nix_packages": ["bun"],
"start_command": "bun run start",
"healthcheck_path_default": "/healthz",
"secrets_mandu_side": ["RAILWAY_TOKEN"],
"secrets_app_side": "via `railway variables set`, NOT `--set-secret`",
"rules": [
"Run `railway link` once before first `--execute`",
"Attach DB addon from dashboard — Railway injects DATABASE_URL automatically",
"App-runtime env vars go via `railway variables set`"
]
}For Agents
Railway adapter emits railway.json (metadata) + nixpacks.toml (build recipe). Nixpacks installs Bun, runs `bun install --frozen-lockfile`, then `bun run build`. Start command is `bun run start`. Secret: `RAILWAY_TOKEN`.
- Uses Nixpacks (Railway's default builder) — not a custom Dockerfile
- Nixpacks installs Bun during the build step — no pre-installed runtime assumed
- Required secret: `RAILWAY_TOKEN` — stored in Bun.secrets
- Minimum railway CLI version: 3.0.0
- App env vars are set via `railway variables set`, not `--set-secret`