All articles
Background jobs·12 min read

Deploy a Node.js worker with Redis and BullMQ.

Connect a background worker to private Redis on Helicarrier, watch a failed job retry, and verify the results in a read-only dashboard.

Helicarrier team ·

Deploy a Node.js worker with Redis and BullMQ.

A report, a document conversion, or a scheduled sync can outlast an HTTP request. A queue gives your application somewhere to put that work; a separate worker processes it independently.

In this guide, you will deploy three services: Redis to hold a queue, a Node.js worker to process three fictional reports, and a small web dashboard to show their results. One report deliberately fails once and then succeeds. You will verify the retry and repeat the deployment without adding duplicate reports.

Verified on Helicarrier on 21 October 2026. The example uses Node.js 22, BullMQ 6.3.19, ioredis 6.0.0, and Redis 8. We checked the running jobs, release command, private connection, and results after a second deployment. Screenshots show the optional new dashboard interface; use your workspace’s switch to that experience if you currently see the classic dashboard.

Before you start

  • A Helicarrier workspace where you can create a disposable project and three services.
  • A GitHub account and a fork of the Redis worker example. Give Helicarrier’s GitHub connection access to your copy.
  • A few minutes to follow deployment logs and check the completed jobs. A successful image build alone does not prove that a worker is processing anything.

Keep all three services in the same project and environment. Our project is named Helicarrier Tutorials Demo; its default environment, Production, contains only disposable tutorial resources.

The example uses Heli KV-N for Redis (256 MB) and Heli S1 for each application (512 MB each). Standard full-month reference rates are $2 + $3 + $3, or $8/month combined, before applicable fees, taxes, or optional resources. A short tutorial run is not a full-month quote. Check your account’s displayed prices before creating resources, and remove them when finished. See the Redis plans and platform pricing.

The public dashboard only reads known sample results. It has no public enqueue or queue-management endpoint, contains no customer data, and makes no external calls from its report processor. This keeps the exercise small and safe to repeat.

1. Create a private Redis database

Open your project and choose New service → Databases → Redis. Name it tutorial-queue-redis, choose version 8, and select Heli KV-N. Leave the password field empty so Helicarrier generates one.

Redis creation form with tutorial-queue-redis, version 8, and an empty password field
The database setup during our test. The account displays its estimate in NGN. Select the image to view it at full size.

Select Provision, then Apply to apply the staged change. Wait for the database to become active. Keep external access disabled: both applications will connect through private service references.

The example reads maxmemory-policy at startup and requires noeviction. It does not change Redis settings. A cache eviction policy can remove keys that a queue needs, so use a dedicated queue database with the appropriate policy. This follows BullMQ’s production guidance.

2. Deploy the background worker from GitHub

Choose New service → Deploy from source → GitHub. Select your fork, or use Or paste URL to enter its GitHub URL. Choose the main branch and set Service type to Background worker.

SettingValue
Nametutorial-report-worker
PlanHeli S1, one instance
Root directoryLeave blank for the standalone example
Build strategyAuto, which detects the included Dockerfile
Install, build, and start overridesLeave blank
Release commandLeave blank on the worker
GitHub service form showing the main branch and Background worker selected
The worker runs continuously without a public endpoint or an HTTP listener. Select the image to view it at full size.

Select Create & deploy, then Apply. The first container cannot start correctly until its role and database reference are configured. Open Variables & secrets, set SERVICE_ROLE to worker, and save it.

In the same section, select Reference a variable from another service. Choose tutorial-queue-redis, choose Connection URL, and name the variable REDIS_URL. Select Add reference, then Deploy.

Worker variables panel showing REDIS_URL linked to tutorial-queue-redis through an internal URL
The reference supplies the connection privately. No database password needs to be copied into the repository. Select the image to view it at full size.

Open Runtime logs and look for Report worker ready. No HTTP listener is required. The service should have no public domain. Do not rely on the active badge alone: during our initial setup, the service showed active while its unconfigured process was restarting.

Keep the default read-only filesystem enabled. The image runs as a non-root user and stores queue state in Redis. Both applications must use the same queue prefix; the example defaults to helicarrier-guide. You do not need to set that variable for this walkthrough.

3. Add the dashboard and queue the reports

Create another GitHub service from the same repository and branch. Name it tutorial-queue-board, choose Web service, use Heli S1, and set the internal port to 3000. Leave the root directory and command overrides blank. Select Create & deploy, then apply the change.

In the web service’s Variables & secrets, set SERVICE_ROLE=web and add its own private REDIS_URL reference to tutorial-queue-redis. Each consuming service needs a reference; the worker’s reference is not inherited by the web service.

Open the web service’s Service settings. Choose Force Dockerfile, set the Dockerfile path to Dockerfile, and set the Release command to:

Web service · release command
npm run seed
Web service build settings with npm run seed as the release command and a read-only filesystem
The release command adds the three sample jobs before the new web release starts. Select the image to view it at full size.

Select Save & redeploy. Follow the deployment logs and confirm that the release command succeeds. The image build needs no Redis connection; the release command receives it from the private reference.

Expected deployment messages
Running release command: npm run seed
Release command succeeded.

The seed uses three fixed job IDs. Running it again leaves retained jobs in place instead of creating another three reports. Once a job is removed, its ID can be reused; this is a bounded demonstration of repeatable seeding, not permanent protection against duplicate business actions.

4. Verify completion and the retry

Open the web service’s generated URL from its overview. Expect 3 / 3 completed, with these results:

ReportStateAttemptsUnits
Launch reportcompleted124
Retry demonstrationcompleted212
Usage reportcompleted136
The deployed queue dashboard with three completed reports and attempt counts 1, 2, and 1
The real deployment after another GitHub release. All three original jobs and their attempt counts remain. Select the image to view it at full size.

The worker processes one job at a time. The retry report deliberately throws on its first attempt. The queue allows three attempts, with exponential backoff starting at 1.5 seconds. Read BullMQ’s retry documentation before choosing delays for your own workload.

Return to the worker’s Runtime logs and filter for retry-report. You should see the first attempt start and fail, then the second attempt start and complete.

Worker runtime logs filtered to retry-report, showing a failed first attempt and successful second attempt
Job IDs and attempt counts connect the dashboard result to actual worker execution. Select the image to view it at full size.

Check the dashboard’s readiness endpoint, replacing the placeholder with your service’s URL:

Check the web service’s Redis connection
curl --fail https://YOUR-SERVICE.on.helicarrier.xyz/health
# Expected: {"ok":true}
HTTP health is not worker health. This endpoint only checks whether the web process can ping Redis. It does not prove that the separate worker is running. Check completed jobs and worker logs as well.

Under the web service’s Service settings → Health checks & auto-recovery, enable automatic checks, set the path to /health, keep the interval at 30 seconds and failure threshold at 3, then save. These checks can restart the web process after failures; they cannot fix a stopped worker.

Finally, deploy both applications again without changing the queue configuration. The release command should succeed and the same three reports should remain, with attempt counts 1, 2, and 1. We checked this after a GitHub commit that improved the dashboard’s mobile layout. This verifies a worker replacement and repeated seed while Redis stays running.

5. Decide what must survive a failure

Our Redis instance returned noeviction, appendonly=no, and an RDB snapshot schedule. Snapshots can omit writes made since the last snapshot. The current Helicarrier Redis plans also lack the daily-backup add-on available to supported relational databases. A successful worker redeploy does not establish recovery from a Redis restart or lost queue data.

Redis also reported maxmemory=0; the service’s memory limit still applies. Bound job history, watch backlog and memory use, and plan capacity. The sample retains up to 20 completed and 20 failed jobs. That history is a debugging aid, not a durable audit log. See BullMQ’s retention options.

For valuable work, keep business state in a durable system of record and design a way to replay or reconcile unfinished jobs. Real jobs can run more than once after interruptions. Use durable idempotency keys for effects such as payments and emails; the example’s fixed queue IDs do not provide exactly-once delivery.

The example handles SIGTERM and SIGINT, closes the HTTP server or waits for its current short job, and closes Redis connections. It has a 15-second shutdown deadline. Longer jobs need their own shutdown and interruption tests. Read BullMQ’s graceful-shutdown guidance.

If a step fails

  • Startup keeps failing: check that Redis is active, the consuming service has REDIS_URL, its SERVICE_ROLE is correct, and you deployed after changing those settings. The startup check must also be allowed to read the Redis eviction policy.
  • The page loads but jobs wait: check the worker’s runtime logs and ensure both services use the same Redis database and queue prefix. The web health check cannot detect this problem.
  • No jobs appear: check that the web service’s npm run seed release command succeeded. Opening the dashboard never creates jobs.
  • The retry report stays failed: inspect current worker logs and configuration. The intentional failure should happen only on the first attempt.
  • A second release adds no new reports: that is expected while the three fixed IDs remain. Do not remove production jobs to repeat a tutorial.
  • Memory keeps growing: review job size, retention, backlog, and worker throughput. More memory does not solve a producer that continually outruns its consumers.

When using support, share the failing step and a sanitized error. Keep connection strings, passwords, and keys out of screenshots.

Clean up, or adapt the example

For a disposable exercise, remove tutorial-queue-board, then tutorial-report-worker, then tutorial-queue-redis through each service’s Service settings → Remove service action. Verify each name and check the project for remaining tutorial services. Removing Redis removes the queue data; export anything you need first and never remove a shared database.

We removed all three test services after recording the results. The repository includes local Docker Compose instructions and integration checks if you want to continue locally. Next, learn how to deploy an application with PostgreSQL or inspect a deployment through a scoped coding agent.

Keep exploring

Another useful next step.

Ready when you are

Make something.
We’ll keep it running.

Bring your code. Give your next idea a home on Helicarrier Cloud.