Deploy to Railway
Railway builds with Railpack by default, and it handles
Cedar's Yarn 4 workspaces and engines.node correctly with no configuration.
Cedar needs nothing beyond the conventions described in
any container host to deploy there.
Two services
When you create a new Railway project from a GitHub repo with Yarn workspaces,
Railway's import detects api and web as separate deployable services,
matching Cedar's
recommended topology for a
production Cedar app. Before your first deploy, follow the steps below.
-
Create a new Railway project from your Cedar repo. Railway creates an
apiand awebservice. -
Add a Postgres database from Railway's plugin catalog.
-
On the api service:
- Settings tab, Start Command:
yarn start:api - Settings tab, Healthcheck Path:
/graphql/health. - Settings tab, Pre-Deploy Command:
yarn cedar prisma migrate deploy. Railway runs this once per deploy, before traffic is switched over. - Variables tab:
PORT=8911 - Variables tab: reference the database, usually
DATABASE_URL=${{Postgres.DATABASE_URL}}. Possibly alsoDIRECT_DATABASE_URL
- Settings tab, Start Command:
-
On the web service:
-
Variables tab: same database URL(s) as the api service above.
-
Variables tab:
API_PROXY_TARGET=${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}. Define it as a Variable first, since Railway's${{Service.VAR}}syntax doesn't resolve directly in the Start Command field. -
Settings tab, Start Command:
yarn start:web --api-proxy-target="http://$API_PROXY_TARGET"This requires
apiUrlincedar.tomlto stay relative — see relative vs. absolute apiUrl. -
Settings tab → Networking → Public Networking → Generate Domain. Railway doesn't expose a public URL by default; this is what gives you one (a free
*.up.railway.appsubdomain, or add your own custom domain here instead).
-
-
Deploy. Railpack finds each service's
build/startscripts and runs them — no further build configuration needed.
It might seem strange to have to add DATABASE_URL to the web service. But
it's used for prerendering. During build Cedar will execute Cell queries against
your database to generate the static HTML it serves when prerendering.
This means a schema-changing deploy has an ordering risk: the web service's
build (and its prerender step) isn't gated on the api service's Pre-Deploy
Command finishing, only on it being started, so it can run before or during a
migration. If you use <Set prerender> on routes with Cell data and are
shipping a breaking schema change, run the migration yourself against
production before pushing (yarn cedar prisma migrate deploy), or keep
changes backward-compatible (expand/contract) so prerendering succeeds
against both the old and new shape.
Migrations
Set on the api service's Pre-Deploy Command — see step 3 under Two services above. For the single-container topology, set the same Pre-Deploy Command on the one remaining service.
Enabling Railway's CDN
Railway's CDN is per-service, free on all plans, and caches static assets by
Content-Type. Enable it on the web service's Settings tab under Networking
— yarn start:web still serves web/dist from a Node process, so the CDN is
what gets static assets edge-cached instead of round-tripping through that
process on every request.
Single-container (optional)
If you'd rather run one service instead of two — fewer moving parts, lower cost, no proxy wiring — you can consolidate down to Cedar's single-container topology. This isn't Railway's default: its GitHub import creates the two services described above, so getting to one means undoing that.
- Delete one of the two auto-created services, keeping the other.
- On the remaining service's Settings tab, clear Railway's auto-configured
Root Directory, Build Command, Start Command, and Watch Paths. Railway's
monorepo import sets these per-workspace (e.g. Start Command
yarn start:api), and single-container needs the root-levelbuild/startscripts instead — leaving a workspace-specific Start Command in place just keeps running that one side, not both. - Add Postgres and reference the same database variable(s) as the api
service above —
DATABASE_URL, plusDIRECT_DATABASE_URLif you used the Neon Postgres setup. - If you kept the api service (deleted web), it never had a public
domain generated — Settings → Networking → Public Networking →
Generate Domain, same as the web service's step above. Also remove its
PORT=8911Variable from the two-service setup: the combined server always runs its api side internally on8911(Cedar's defaultapi.port), so a leftoverPORT=8911makes the public web side try to bind that same port and fail to start. - Push. Railpack runs the root
build/startscripts, which serve both sides from one process.
Single-container doesn't support a custom server file — see the note on custom server files. Railway's CDN still applies the same way — enable it on the one remaining service.
Config as code
Railway supports config-as-code via railway.json or railway.toml in your
repo. There's no JSONC support, so if you want comments in your config, use
railway.toml.
Railway also has an experimental Infrastructure as Code mode that goes beyond
per-service build/deploy config: a .railway/railway.ts file, written against
their TypeScript SDK, can describe project-level resources — services,
databases, variables, domains — and be applied with railway config plan /
railway config apply. TypeScript is the only supported language so far.