Pantheon Infrastructure · Decision-Grade Spec

Railway — Infra Audit & Deployment-Layer Spec

Owner proteus Board Arsenal #7 Issue #191 Tier T-deep Status Review 2026-06-22

What is Railway's role in the Pantheon stack, what runs on it vs N5/VPS, and which projects do we stand up? Railway facts deep-read from official docs; footprint from fleet_services. Grounds the durable MCP/workflow deployment-layer decision.

The call

Railway is the sanctioned home for stateless / agent app-layer services — Mastra agents & hosted MCP servers. N5 stays the self-hosted substrate (vault, fabric, obs, media). n8n stays on the Hostinger VPS — Railway's volume/replica limits make it a poor n8n host. The vault never moves to PaaS. Split by workload class — not "everything to Railway."

Hard rule from the research: hosted MCP transport must be WebSocket or streamable-HTTP-with-heartbeatnever bare SSE. Railway caps SSE at 15 min; WebSocket is exempt. deep-read

The split — by workload class

RAILWAY · PaaS

App-layer services

always-on · stable URL · git-deploy
  • Mastra agents (mimir live)
  • Hosted MCP servers (WS / streamable-HTTP)
  • Client-facing APIs (SSE w/ heartbeat)
  • Per-PR preview / staging envs
HOSTINGER VPS

Stateful runtimes

flat cost · full control
  • n8n prod + dev (stays here)
  • Volume-backed / single-replica apps
  • Long-lived connections (no 15-min cap)
N5 · SELF-HOST

Trust & substrate

control anchor · fixed-cost
  • OpenBao vault (never PaaS)
  • Tailscale fabric
  • Obs stack (Grafana/VM/Loki)
  • Media

1 · Use cases (Pantheon-specific)

Use caseFitNotes
Always-on Mastra agentstrongmimir proven; metis next
Hosted MCP server (remote, OAuth)w/ constraintWebSocket / streamable-HTTP+heartbeat — not SSE
Client-facing API (SSE chat)provenmimir-chat-api; heartbeat ≤5min, reconnect <15min
Per-PR preview / stagingstrong DXpreview env inherits shared vars
Scheduled jobs (cron)yessupported (light-coverage this pass)
n8n workflow runtimeweaksingle-replica forced → keep on VPS (§5)
Stateful DB of recordavoidDB "templates" self-run on volume, not managed/SLA'd

2 · Capabilities deep-read 2026-06-22

AreaWhat
Compute / plansPer-service caps: Hobby 48GB/48vCPU·6 replicas; Pro 1TB/1000vCPU·42 replicas. No scale-to-zero — always-on bills continuously.
DeployGitHub repo / Dockerfile / Nixpacks. Config-as-code railway.toml (code overrides dashboard), per-env overrides.
PersistenceEphemeral FS wiped on redeploy; persistence needs a Volume. Single-attach: one volume/service, not shareable, not with replicas. Redeploy of volume-backed svc = downtime. DBs are self-run templates, not managed.
NetworkingPrivate SERVICE.railway.internal (WireGuard, IPv6, same project+env). Public .railway.app + auto-TLS, custom domains, TCP proxy. WebSocket = no timeout; SSE/HTTP = 15-min max, 5-min idle close. Static outbound IPv4 (Pro+); SMTP Pro+ only.
Envs / secretsPreview env per PR. Shared vars (per-env), Sealed vars (write-only), Reference vars ${{ shared.X }} — point, don't copy.
ObservabilityLogs 7d/30d/90d by plan; metrics CPU/mem/disk/net 30d, no app-level metrics. Healthcheck = deploy-gate only, NOT liveness (default 300s).
Regions / HA4 Metal regions (US-W, US-E, EU-W, SEA). Multi-region replicas, nearest-route, no sticky sessions. No published uptime SLA on self-serve — real SLA = Enterprise-only, negotiated.
CLI / APICLI up/run/logs/ssh/connect; CI via RAILWAY_TOKEN. GraphQL backboard.railway.com/graphql/v2 — token types Account/Workspace/Project/OAuth.

3 · Anti-patterns & gotchas

Bare SSE for long streams

15-min hard cap + 5-min idle close. MCP must heartbeat+reconnect, or use WebSocket (exempt). deep-read

n8n MCP-trigger on multi-replica

SSE needs same instance; replicas break it (n8n #14761, closed "not planned"). Forces single-replica. deep-read

Stateful + HA

Volume single-attach + no-replicas-with-volumes + redeploy downtime = no zero-downtime HA for n8n / self-run DBs. deep-read

Ephemeral FS data loss

No volume → wiped on redeploy; build-phase writes don't persist. deep-read

DATABASE_URL shadowing

Railway Postgres plugin injects it; name external-DB vars explicitly (mimir uses VISKA_DB_URL). in-fleet

Assuming an uptime SLA

None on self-serve; healthcheck is deploy-gate, not liveness. Keep external monitoring (Talos). deep-read

24/7 always-on cost

Per-second RAM+CPU; a steady 0.5vCPU/1GB ≈ a full Pro credit/mo. Not cheaper than a flat VPS for steady-state. deep-read

Naming drift

mimir's prod env is mimir-staging-production. Name service+env deliberately. in-fleet

4 · Design playbook

1. Split by workload class — app/agents → Railway; stateful n8n → VPS; vault/fabric/obs → N5.
2. MCP transport — WebSocket or streamable-HTTP+heartbeat. Never bare SSE.
3. State — stateless agents multi-replica OK; stateful/volume = single-replica + accept redeploy downtime, or keep off Railway.
4. Secrets — Sealed vars sourced via Hades; Reference vars not copies; never literals in repo. Config-as-code committed (no click-ops drift).
5. External liveness — Railway healthcheck is deploy-gate only → Talos probe every Railway service.
6. Tenant isolation — separate Railway projects + project-scoped tokens + per-tenant auth: pantheon-* vs client-viska-*. A client never rides fleet auth.
7. Register reality — every Railway service → fleet_services with real host/url (retire stdio rows).

5 · n8n: VPS vs Railway — resolved → keep on VPS

n8n is stateful (volume) and its MCP Server-Trigger needs single-replica (#14761). On Railway: single-replica only, redeploy downtime on the volume, per-second cost on a 24/7 app. The Hostinger VPS already runs it at flat cost with full control. n8n stays on the VPS. Railway is for Mastra agents + new hosted MCP servers — not the n8n runtime.

6 · Scoped projects — current footprint (fleet_services, 2026-06-22)

Project / serviceTenantWhatStatus
viska-mimir-agent · svc mimir · prodclient (Viska)Mastra agent (Slack)LIVE
mimir-chat-api (/api/chat SSE)client (Viska)chat API → portalLIVE
Metis-as-Mastrapantheonfleet Mastra agentSCOPED, NOT SHIPPED
mastra-bridge MCPatlasMCP (deploy unconfirmed)registry
Verification gap (honest): live Railway-account enumeration is blockedrailway CLI returns Unauthorized this session. RAILWAY_TOKEN is healthy+wired in vault but not injected into the session (registry↔session drift). This table = fleet_services + operator statement, not a live account dump. Fix via token-in-session (Hades) before treating as exhaustive.

7 · Recommended projects

  1. pantheon-metis-agent (Railway, pantheon) — ship the scoped Metis Mastra agent as the Pantheon pilot + first hosted MCP under the new standard (WebSocket/streamable-HTTP).
  2. Tenant-isolation pattern codifiedpantheon-* vs client-viska-* projects + project-scoped tokens + per-tenant auth/secrets.
  3. MCP migration audit — of ~16 stdio-localhost MCP rows, decide which need always-on/remote hosting (→ Mastra MCPServer on Railway) vs stay stdio-local. Don't lift what doesn't need lifting.
  4. n8n stays on VPS (§5) — no migration.
  5. Unblock toolingRAILWAY_TOKEN session-injection (Hades) to restore railway CLI + Talos railway probe + live enumeration.
  6. Browser-debug (separate track) — first-party ride-local MCP (Playwright --extension / Chrome DevTools --autoConnect); keep DOMShell if it earns its place. Not a Railway concern.

8 · Cost shape

Railway turns a fixed VPS bill into a variable RAM-hours bill. 24/7 steady-state → usually pricier & less predictable than the VPS; bursty/preview/agent workloads → per-second metering can win. Rates deep-read: $20/vCPU·mo · $10/GB-RAM·mo · $0.15/GB-vol·mo · $0.05/GB egress · Pro $20/seat·mo.

9 · Source ledger

deep-read plans/caps, cost rates, volumes, DB-templates, private networking, TCP proxy, WS-no-timeout/SSE-15min (2 pages), logs/metrics/healthcheck, regions, no-self-serve-SLA, CLI/GraphQL, n8n #14761, secrets/reference/sealed vars, config-as-code.

in-fleet DATABASE_URL shadowing, naming drift, footprint.

verify before quoting object-storage $/GB, build-minute pricing, volume region-binding, usage-cap/suspension, cron mechanics, deploy-method internals, metrics sampling, default restart-policy, multi-region plan floor. (none load-bearing for §0)

blocked live Railway-account enumeration (RAILWAY_TOKEN not in session).