Overview
Four deployables, three platforms, and which of them ship on a push.
Four deployables, three platforms, and only one of them ships when you push. That asymmetry is the single most common way to deploy nothing while believing you deployed something.
What goes where#
| You changed | Deploys to | Automatic on push to main |
|---|---|---|
web/ | Vercel | yes |
server/ | Railway | no — manual, always |
src/ (control plane) | Railway, its own service | no — manual |
infra/ (media plane) | a rented VM | no — a runbook |
Order#
- The media plane first. Everything else needs
a
LIVEKIT_URLto point at, and until one exists the control plane mints tokens nothing can use. - The control plane. Give it the key pair from step 1 and
UDK_SECRET. - The API and the web app. They need the control plane's URL, and they need
to agree with it about
UDK_SECRET. - The worker, beside the media plane.
The one Railway setting that is not optional#
Every service carries a healthcheck, and a repository without a deploy block
is worse than one with a slow probe — with no healthcheck, the platform goes
green the instant the container starts, which means traffic reaches an app that
cannot answer yet.
"deploy": {
"healthcheckPath": "/health",
"healthcheckTimeout": 300,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10,
"overlapSeconds": 20,
"drainingSeconds": 15
}
healthcheckTimeout must clear the worst-case cold boot, not the warm one.
120 seconds was tried and failed a perfectly correct deploy.
A probe hardcoded to 200 is not a shortcut past this. There is one probe and it is a traffic gate: an always-healthy endpoint tells the platform to cut the edge over to a container that cannot answer, and removes the only signal that would ever restart a wedged process. Keep the probe honest and make readiness fast.
A deploy reported SUCCESS is not yet reachable#
Zero-downtime rollover holds the old container until the new one drains, and the window is minutes — measured at 5m15s and 5m55s from SUCCESS to the first 200. During it the domain accepts TCP and returns nothing, which reads exactly like an outage and is not one.
Do not roll back, do not redeploy, and do not go hunting. drainingSeconds and
overlapSeconds above are what shorten it.
Verifying, without waiting blind#
/health is not a readiness signal for a new deploy: the old instance keeps
answering it perfectly while the new one builds, so a health poll reports success
about code that is not running yet.
Confirm a field only the new code emits, or ask the platform which revision is live. Health and status