Skip to content

Railway

Railway is part of the intended deployment story for the merchant dashboard, but the current codebase shows a mixed reality with strong Cloudflare Worker paths. This page documents both the intended architecture and the current state, so future contributors understand why both platforms are mentioned.


Railway's Role in the Deployment Story

Railway is positioned in the strategy docs and STACK.md as the heavier compute option for the merchant dashboard — specifically for running TanStack Start's SSR server. The intended architecture was:

  • Railway: Runs the full-stack app (TanStack Start SSR + Hono API). Docker deployment with Bun. Accesses Cloudflare D1 via the HTTP API for database operations.
  • Cloudflare: Everything else — static sites, workers, edge services, DNS.

This split made sense because:

  • TanStack Start's SSR requires a persistent Node/Bun process — something Workers don't provide
  • Railway provides Docker deployment with automatic scaling and health checks
  • An official Railway deployment template exists for TanStack Start
  • Railway can access Cloudflare D1 via the HTTP API (external access), so the database layer stays on Cloudflare even when compute is on Railway
  • The "Run TanStack Start on Railway" template from the Railway marketplace was a known, tested deployment path

Why Both Railway and Cloudflare Exist

No single platform handles both SSR and edge deployment optimally. The two-platform strategy splits responsibilities by compute profile:

NeedRailwayCloudflare Workers
SSR renderingFull Node/Bun process with server-side renderingLimited CPU (10ms wall clock on free, 30ms on paid), no persistent process
Long-running tasksCan run background jobs, scheduled tasks, report generationCron triggers limited to 15 min, no persistent state between invocations
Database accessD1 via HTTP API (external, ~50ms latency)D1 via binding (internal, <10ms latency)
WebSocket supportNative supportLimited (requires Durable Objects, additional complexity)
Static assetsServed but not optimized for edge deliveryFree, edge-cached, 300+ locations, zero-config
Edge deploymentSingle region (or multi-region with extra cost and config)300+ edge locations by default
Cost at low scaleUsage-based (~$5-20/month)Free tier covers most needs
Cost at scalePredictable, grows linearly with computePer-request, can spike unpredictably
Docker supportNative — build from DockerfileNot applicable (Workers use their own runtime)
Build pipelineDocker build on Railwaywrangler deploy or @cloudflare/vite-plugin

The Mixed Reality in the Codebase

The checked-in code tells a different story than the strategy docs. Understanding this gap is important for anyone reading the planning artifacts.

What the strategy docs say (STACK.md, CLAUDE.md, planning docs):

  • TanStack Start + Railway for the merchant dashboard
  • Full SSR with server functions
  • Docker deployment on Railway
  • D1 access via HTTP API

What's actually checked in (vela-dashboard/):

  • Vite + React 19 + TanStack Router + TanStack Query + Hono — not TanStack Start
  • Cloudflare Worker–compatible backend — Hono runs on Workers natively
  • D1 accessed via binding (env.DB), not HTTP API — the faster internal path
  • wrangler.toml configured for direct Worker deployment
  • No Dockerfile or Railway configuration in the repo

What this means in practice:

The dashboard can run entirely on Cloudflare Workers today. The Vite build produces static assets that Workers serve, and the Hono API routes handle all dynamic requests. This is a simpler, cheaper, and faster deployment than Railway would provide for this architecture.

The Railway path remains architecturally valid if TanStack Start SSR is implemented in the future, but it is not the current default.


The Docker Compose Staging Setup

The workspace root contains docker-compose.staging.yml, which suggests Railway (or Docker-based) deployment was explored for staging:

yaml
# docker-compose.staging.yml exists at workspace root

This file provides a local staging environment that mimics a Railway-like Docker deployment. It's used for integration testing and staging validation before deploying to production Workers.


When to Use Which Platform

Use Cloudflare Workers When:

  • The app is a Vite-built SPA + Hono API (current dashboard state)
  • Static assets are served from edge
  • API routes are stateless or use D1/KV bindings
  • No SSR is required (client-side rendering via TanStack Query handles data loading)
  • Deployment simplicity matters (single wrangler deploy)
  • Service bindings are needed for inter-worker communication (admin → dashboard, checkout → dashboard)

Use Railway When:

  • TanStack Start SSR is implemented and requires a persistent process
  • Server functions need more than Workers' CPU time limits (30ms wall clock on paid tier)
  • Background jobs or scheduled tasks exceed Workers' cron limitations (15 min max, no state)
  • WebSocket connections are needed for real-time features (e.g., live billing dashboards)
  • Long-running report generation or batch processing is required (e.g., monthly invoice batch)
  • Complex Docker builds with native dependencies are needed

Current Default

The current default is Cloudflare Workers for everything. All 11 repos deploy to Workers:

RepoDeploymentWrangler Config
vela-dashboardWorkersYes
vela-adminWorkersYes
vela-webWorkersYes
vela-docsWorkersYes
vela-widgetWorkersYes
vela-checkoutWorkersYes
vela-portalWorkersYes
vela-webhookWorkersYes
vela-syntheticWorkers (cron)Yes

Railway is available as an upgrade path when SSR or heavier compute becomes necessary.


Migration Path: Workers → Railway

If the dashboard ever needs to move to Railway (e.g., for TanStack Start SSR), the migration path is:

  1. Add Dockerfile to vela-dashboard — Bun-based Docker image
  2. Swap D1 access from binding (env.DB) to HTTP API (fetch('https://api.cloudflare.com/client/v4/accounts/{id}/d1/database/{id}/query'))
  3. Replace service bindings with internal API calls — admin, checkout, and portal would call the dashboard's Railway URL instead of using env.DASHBOARD.fetch()
  4. Update DNSapp.velapay.com would point to Railway instead of Workers
  5. Keep static assets on Workers — the Vite build output can still be served from Workers for free

The Drizzle ORM schema and queries don't change — only the database connection method changes.


Practical Guidance for Contributors

  1. Don't assume Railway is running. Check wrangler.toml in each repo — if it exists, the service deploys to Workers.
  2. Don't add a Dockerfile unless needed. The current Worker deployment is simpler and cheaper.
  3. If adding SSR via TanStack Start, evaluate whether Workers' CPU limits are sufficient before defaulting to Railway. Workers support SSR via @cloudflare/vite-plugin.
  4. D1 access via binding is faster than HTTP API. If running on Workers, use env.DB binding. If running on Railway, use D1 HTTP API with authentication.
  5. Service bindings only work Worker-to-Worker. If the dashboard moves to Railway, the admin/checkout/portal service bindings would need to be replaced with internal API calls authenticated via DASHBOARD_PROXY_AUTH_TOKEN.
  6. Internal wiki docs should preserve the nuance. Don't flatten the Railway/Workers distinction. The strategy docs say Railway; the code says Workers. Both are correct for different time horizons.

Cost Comparison at Current Scale

Cost FactorCloudflare WorkersRailway
Dashboard hostingFree tier (100K req/day)~$5/month (developer plan)
D1 databaseFree tier (5M reads/day)Same D1, accessed via HTTP API
Static assetsFree (edge-cached)Not optimized for static
API computeFree tier sufficientBilled per CPU-second
SSL/HTTPSFree (Cloudflare-managed)Free (Railway-managed)
Custom domainFree (DNS on Cloudflare)Free

At current pre-PMF scale, Cloudflare Workers is effectively free for all workloads. Railway becomes cost-competitive only when SSR or heavy compute is required.

Internal knowledge base for the Vela Labs workspace.