Railway
Deploy long-running BullMQ worker services on Railway with the monorepo build and Railway CLI.
This guide covers orchestrator worker deployments on Railway: always-on processes that consume BullMQ queues while your API may live on Vercel (or another host). The pattern matches Railway’s API + Redis + worker model described in Deploy an AI Agent with Async Workers—here the queue is BullMQ instead of a custom Redis list.
Railway treats these as persistent services (containers that keep running). See also Orchestrator workers for env vars and Orchestrator workflows for when to use bullmq transport.
Prerequisites
- Railway account and Railway CLI on your
PATH. This guide uses the globalrailwaycommand. - Worker environment variables set on each Railway service (applies to dashboard deploys and CLI deploys):
- REDIS_* (shared with your API). See Redis cache.
- Supabase keys (at minimum PUBLIC_SUPABASE_URL and PUBLIC_SUPABASE_ANON_KEY; in production also SUPABASE_SERVICE_ROLE_KEY). See Orchestrator workers.
RAILPACK_CONFIG_FILEset per worker service (see “Set variables per worker service” below).
Build and start (summary)
| Phase | Command / setting |
|---|---|
| Build (repo root) | pnpm install && pnpm railway:orchestrator:build |
| Start — integration refresh | pnpm railway:orchestrator:start:integration-refresh |
| Start — notification email | pnpm railway:orchestrator:start:notification-email |
The repo includes railway.toml at the monorepo root with buildCommand and a restart policy; start command is not fixed in that file so two Railway services can use the same image/build with different start lines (config as code).
Dashboard setup
Create project resources
Add Redis (and reference its connection into worker variables as REDIS_*). Add one Railway service per worker (integration refresh vs notification email), or split later—each needs its own Start Command.
Name the services after the worker they run (recommended), but the exact service names are up to you. The CLI bootstrap uses the linked service (see “Railway CLI” below).
Connect the GitHub repo
Use the same repository as the monorepo. Configure monorepo settings so install/build run from the repository root.
Set variables per worker service
Set NODE_ENV=production, Redis, Supabase, and any provider/email keys from Orchestrator workers.
Railpack config: set <code>RAILPACK_CONFIG_FILE</code>
RAILPACK_CONFIG_FILE (a Railway service variable) to select which worker starts. - Integration refresh worker:
RAILPACK_CONFIG_FILE=railpack.integration-refresh.json - Notification email worker:
RAILPACK_CONFIG_FILE=railpack.notification-email.json
You can set this in the Railway dashboard (per service) or with the CLI after linking the service:
railway service
railway variables set RAILPACK_CONFIG_FILE=railpack.integration-refresh.json Switch to the other service, then:
railway service
railway variables set RAILPACK_CONFIG_FILE=railpack.notification-email.jsonSet Start Command
In the service Settings → Deploy → Start Command, set exactly one of the following (from the monorepo root).
Integration refresh worker —
pnpm railway:orchestrator:start:integration-refresh Notification email worker —
pnpm railway:orchestrator:start:notification-email Avoid scale-to-zero if it would stop a process during long integration-refresh sleeps.
Railway CLI
Prerequisite: install the Railway CLI
Install the CLI so the railway command is available globally (or on your PATH). Follow Installing the Railway CLI — for example:
npm i -g @railway/cli You can also deploy each worker with the Railway CLI instead of (or in addition to) GitHub-triggered deploys from the dashboard. To avoid deploying the wrong worker, prefer the repo scripts that set the correct Railpack config for you:
pnpm railway:setup:integration-refresh(one-time: create service + set variables via CLI)pnpm railway:setup:notification-email(one-time: create service + set variables via CLI)pnpm railway:deploy:integration-refreshpnpm railway:deploy:notification-email
Deploy a worker from your machine
Authenticate and link
From the repository root:
railway login
railway init
railway link
railway service Choose your Railway project and the worker service (integration refresh or notification email). Run railway link again only if you need a fresh link. Use railway unlink to clear the link for this directory.
If you see No service linked, run railway service to link a service for this directory.
“Available options can not be empty”
railway link fails with Available options can not be empty, the CLI could not find any projects to link in the selected workspace. - Create a project in the Railway dashboard (empty project is fine), then rerun
railway link. - Run
railway initto create a new project, then rerunrailway link.
Create services and set variables (recommended once)
If this is the first time setting up a worker service, create the services and set variables via the setup scripts:
pnpm railway:setup:integration-refresh
pnpm railway:setup:notification-email These scripts create empty services (if missing) and call railway variable set with —skip-deploys so you can safely configure variables before deploying.
Deploy
Before deploying, confirm the linked service is configured for the integration-refresh worker. It must have RAILPACK_CONFIG_FILE set to railpack.integration-refresh.json (see above), otherwise Railpack will report No start command detected.
pnpm railway:deploy:integration-refresh This ships the current directory to the linked service and deploys the integration-refresh worker.
To deploy the other worker, switch the linked service (railway service or railway unlink + railway link), ensure it has RAILPACK_CONFIG_FILE=railpack.notification-email.json, then run:
pnpm railway:deploy:notification-email Full CLI reference: Railway CLI.
Two workers from one clone
railway up, then railway service (or railway unlink + railway link) to target the second service before deploying again.