Deploy BullMQ | API and Worker, With Outcomes in Postgres
API and BullMQ worker wired up, with every outcome recorded in Postgres.
Just deployed
Redis
Just deployed
/data
Just deployed
/var/lib/postgresql/data
Just deployed
Deploy and Host a BullMQ Worker Stack on Railway
An API that answers immediately and a worker that does the slow part — with Redis as the queue, Postgres as the record of what happened, and a repeatable job standing in for cron.
Post a job, get an id back in milliseconds, read the outcome when it is ready.
About Hosting BullMQ
The BullMQ template in this catalogue has 299 installs and a 68% success rate. The whole Queues category is 55 templates and 2837 installs, on a platform where background work is table stakes.
BullMQ itself is not the difficult part. The difficult part is everything around it: two processes that must agree on a queue name, a Redis client configured so that a blocking read does not take the worker down, an outcome written somewhere that outlives Redis, and retry handling that does not mark a job failed while an attempt is still pending.
Each of those is one line or one decision, and each of them fails quietly.
Common Use Cases
- Anything too slow for a request — mail, image processing, report generation, a third-party API that sometimes takes twenty seconds.
- Work that must survive a restart. The job stays in Redis and the record stays in Postgres; a redeploy mid-job costs an attempt, not the work.
- Recurring tasks through BullMQ's repeatable jobs, from the same codebase as everything else.
Dependencies for BullMQ Hosting
Deployment Dependencies
- Node 24, Express 5 and BullMQ
- Redis — the queue
- Postgres — the record of every job and its outcome
- Template source
Implementation Details
maxRetriesPerRequest: nullon the Redis connection. With ioredis's default, BullMQ's blocking read eventually throws and the worker exits. This single line is the most common reason a BullMQ deployment dies quietly overnight.family: 0as well. Railway's private network is IPv6, and the default resolver asks for A records only — the symptom is a worker that never connects to a Redis that is plainly running.- The database row is written before the job is added to the queue. The other order loses work: a worker fast enough to pick the job up first has no row to update.
- A job is marked failed only once its attempts are exhausted, not on the first throw — otherwise the status contradicts the retry that is still coming.
- The repeatable job is registered by the worker, not the API. Registered from the API, every replica would register its own copy of the schedule.
- Shutdown closes the worker rather than killing it, so an in-flight job finishes instead of being abandoned half-done.
- The enqueue endpoint is behind a token, because an open one is a way for strangers to spend your worker's CPU.
- Redis runs with a password, an append-only file on a volume and dual-stack binding — a broker bound only to IPv4 is unreachable across the private network.
The repository carries scripts/verify-queue.sh, which checks that enqueuing a three-second job returns in under three seconds — the difference between a queue and a slow endpoint — that the worker finishes it, that a job which throws is recorded as failed after its retries rather than lost, and that the repeatable job fires with nobody asking. This template was published only after that script passed against a deployment created from the template itself.
Why Deploy a BullMQ Worker Stack on Railway?
Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.
By deploying a BullMQ worker stack on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.
Template Content