LangENKO

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.

since v0.24
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:

  1. Click NewDatabaseAdd PostgreSQL.
  2. Attach it to your Mandu service (drag the reference).
  3. Redeploy — DATABASE_URL flows 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

🤖 Agent Prompt — Railway
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.

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

AI hint

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`.

Invariants
  • 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`
Guard scope
deploy