---
title: Migrate Render background workers to Vercel Daemons
description: Migrate Render workers to Vercel Daemons. Translate render.yaml, package Celery in Docker, prepare the broker, and verify the queue handoff.
url: "https://vercel.com/kb/guide/migrate-render-workers-to-vercel-daemons"
published: 2026-10-26
last_updated: 2026-10-26
authors: Ben Sabic
related_resources:
  - title: "How to migrate from Render to Vercel"
    url: "https://vercel.com/kb/guide/migrate-render-to-vercel"
    description: "Migrate from Render to Vercel: map web services, cron jobs, and containers to their equivalents, and handle what doesn't move."
  - title: "Migrate a Python API from Render to Vercel"
    url: "https://vercel.com/kb/guide/migrate-a-python-api-from-render-to-vercel"
    description: "Move a FastAPI, Flask, or Django API from Render to Vercel Functions. Translate render.yaml and replace persistent disks, background workers, and cron jobs."
  - title: "Running Docker on Vercel"
    url: "https://vercel.com/kb/guide/docker"
    description: "Learn how to run Docker on Vercel by deploying OCI container images as Vercel Functions, storing them in Vercel Container Registry (VCR), and running containers in Vercel Sandbox."
  - title: "Backends on Vercel: Python, containers, and Daemons"
    url: "https://vercel.com/blog/backends-on-vercel-python-containers-and-daemons"
    description: "Explore how Python, container images, and background workers fit into the Vercel platform."
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

To move a Render worker to [Vercel Daemons](https://vercel.com/kb/guide/vercel-daemons), package it in a Docker image, declare it under `daemons` in `vercel.json`, and deploy to production. Vercel maintains the instance count and restarts processes that exit. Before the handoff, make the broker reachable from Vercel and test task recovery after shutdown.

The example below uses a Django app with a Celery worker, a Celery beat scheduler, and a nightly cron job in `render.yaml`. The [Render migration guide](https://vercel.com/kb/guide/migrate-render-to-vercel) covers the web service, data, and DNS. This procedure translates the worker configuration and moves queue consumption while preserving a rollback path.

> Vercel Daemons is in [Public Beta](https://vercel.com/docs/release-phases#public-beta) on all plans.

## Prerequisites

- Access to the Render workspace, and optionally the [Render CLI](https://render.com/docs/cli) to list services and their IDs.
  
- Your Vercel project configured with the repository root as its [root directory](https://vercel.com/docs/builds/configure-a-build#root-directory). Daemons don't support subfolder roots such as `apps/web` during the beta.
  
- Permission to deploy to production, since that's the only environment that starts daemons.
  
- [Vercel CLI](https://vercel.com/docs/cli) 64.3.0 or later, for the `vercel daemons` commands.
  
- [Docker](https://docs.docker.com/get-docker/), to build and test worker images locally.
  
- Enough [daemon quota](https://vercel.com/docs/daemons/limits#default-quotas) for the workload. Hobby includes 5 Active CPU-hours per month; time spent waiting on a queue is different from CPU time spent processing tasks.
  

## Should your Render worker become a daemon?

Check these requirements before moving a Render worker:

- **Outbound work:** Render background workers receive no incoming traffic, which matches Daemons. Review any `type: pserv` service separately because a callable private endpoint has different networking requirements.
  
- **Fixed capacity:** Render supports autoscaling on Pro workspaces and higher. Daemons use a fixed count per region during the beta, so choose capacity for the queue's expected peaks.
  
- **Local testing:** Vercel preview deployments don't start daemons. Use Docker with development data to test the worker before production.
  
- **External state:** Daemon storage is ephemeral. Move durable data off any attached Render disk and update the worker to use [Vercel Blob](https://vercel.com/docs/vercel-blob) or a database.
  
- **Per-instance resources:** The largest daemon has 8 GiB and 4 vCPUs. More instances can process more independent jobs, but each job still has to fit in one instance. Reduce concurrency or redesign jobs that exceed that limit.
  

For tasks that fit function duration limits, [Celery on Vercel Queues](https://vercel.com/docs/frameworks/backend/celery) provides another migration path with preview support. Daemons let you retain Celery's worker process and broker integration.

## How render.yaml worker settings map to Vercel

Map the worker's command, capacity, and shutdown settings to a daemon entry. Network access, persistent data, and deployment hooks need separate changes.

Both example workers use one Dockerfile with different commands. Before they move, configure a broker that Render and Vercel can both reach.

| Render (`render.yaml`)                                      | Vercel (`vercel.json`)                                                                                                                               |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type: worker`                                              | A named entry under [`daemons`](https://vercel.com/docs/daemons/config-reference)                                                                    |
| `runtime: python`, `node`, or another native runtime        | A `Dockerfile` or `Containerfile` you add (required)                                                                                                 |
| `runtime: docker` with `dockerfilePath` and `dockerContext` | `entrypoint` and `root`; the Dockerfile's directory is the build context                                                                             |
| `rootDir`                                                   | `root`                                                                                                                                               |
| `startCommand` or `dockerCommand`                           | [`command`](https://vercel.com/docs/daemons/config-reference#command) replaces the image's `ENTRYPOINT` and `CMD`; omit it to use the image defaults |
| `numInstances`                                              | [`instances`](https://vercel.com/docs/daemons/config-reference#instances), a count per region from 0 to 100                                          |
| `scaling` (autoscaling, Pro workspaces)                     | None during the beta; set a fixed count                                                                                                              |
| `region`                                                    | A [Vercel region ID](https://vercel.com/docs/regions) as the key in `instances`                                                                      |
| `plan`, such as `1c-2g` (formerly `standard`)               | [`resources.requests.memory`](https://vercel.com/docs/daemons/config-reference#resources): 1, 2, 4, or 8 GiB                                         |
| `maxShutdownDelaySeconds`, 30 by default, up to 300         | [`terminationGracePeriodSeconds`](https://vercel.com/docs/daemons/config-reference#terminationgraceperiodseconds), 30 by default, up to 3,600        |
| `envVars`, including `fromGroup` references                 | Project [environment variables](https://vercel.com/docs/environment-variables) in the Production scope                                               |
| `fromService` and `fromDatabase` references                 | The provider's real connection string                                                                                                                |
| Secret files at `/etc/secrets/<filename>`                   | Environment variables, written to a file at startup if needed                                                                                        |
| Key Value and Postgres internal URLs                        | Endpoints reachable over the internet                                                                                                                |
| `disk`                                                      | Move the data to Vercel Blob or a database and update the worker's storage access                                                                    |
| `type: cron`                                                | A [cron job](https://vercel.com/docs/cron-jobs) that calls a route                                                                                   |
| `preDeployCommand`                                          | A CI step before production promotion                                                                                                                |
| `previews` for workers                                      | None during the beta; test with Docker                                                                                                               |
| Logs and metrics in the dashboard                           | [Logs](https://vercel.com/docs/logs/runtime) filtered by `daemon:<name>`, plus CPU and memory charts                                                 |

Both platforms use `SIGTERM` followed by a grace period, so you can reuse the worker's shutdown handler. Test it in the container and check Celery acknowledgment settings before relying on that handler during a rollout.

## Steps

Keep the Render workers available until their tasks drain and the Vercel workers pass the handoff checks. The broker move in step 2 changes production task routing, so schedule it separately from the worker deployment.

### 1\. Inventory the Blueprint

Start from your existing `render.yaml`. This excerpt shows the example app's service types and worker settings; retain the full environment and database configuration for each service:

```yaml
services:
  - type: web
    name: orders-web
    runtime: python
    rootDir: backend
    buildCommand: pip install -r requirements.txt
    startCommand: gunicorn orders.wsgi

  - type: worker
    name: orders-worker
    runtime: python
    rootDir: backend
    buildCommand: pip install -r requirements.txt
    startCommand: celery -A orders worker --loglevel=info
    plan: 1c-2g
    numInstances: 2
    region: oregon
    maxShutdownDelaySeconds: 120
    envVars:
      - key: REDIS_URL
        fromService:
          type: keyvalue
          name: orders-queue
          property: connectionString
      - fromGroup: orders-shared

  - type: worker
    name: orders-beat
    runtime: python
    rootDir: backend
    buildCommand: pip install -r requirements.txt
    startCommand: celery -A orders beat --loglevel=info
    plan: 0.5c-512mb

  - type: cron
    name: prune-sessions
    runtime: python
    rootDir: backend
    schedule: "0 3 * * *"
    buildCommand: pip install -r requirements.txt
    startCommand: python manage.py clearsessions

  - type: keyvalue
    name: orders-queue
    ipAllowList: []
```

Dashboard edits can drift from the Blueprint, so confirm each worker's live plan, instance count, and region on its Settings page, or list services with `render services`. Then write down where each one lands:

| Render service   | What it does                 | Destination on Vercel                                                                                     |
| ---------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------- |
| `orders-web`     | Django site                  | A Vercel Function, per the [Render migration guide](https://vercel.com/kb/guide/migrate-render-to-vercel) |
| `orders-worker`  | Celery worker, 2 instances   | A daemon                                                                                                  |
| `orders-beat`    | Celery beat scheduler        | A one-instance daemon                                                                                     |
| `prune-sessions` | Cron job that runs and exits | A [cron job](https://vercel.com/docs/cron-jobs)                                                           |
| `orders-queue`   | Key Value broker             | Redis from a provider both platforms can reach                                                            |

### 2\. Prepare the broker before moving web traffic

Use the [Render migration guide](https://vercel.com/kb/guide/migrate-render-to-vercel) to prepare the web service, Postgres, and any persistent data. Keep the web service and both worker sets connected to the same authoritative application database during the handoff. Before switching web traffic, give the Vercel app access to the broker and any result backend it uses.

The `connectionString` injected by `fromService` is a Key Value internal URL. Render also provides an external URL that requires an IP allowlist. Because Daemons has no documented fixed egress IP guarantee, this example uses a Redis endpoint that both platforms can reach with authentication and TLS. Confirm that the provider supports your Celery Redis transport and configure certificate verification.

For a broker-only migration, keep result-backend configuration unchanged while old tasks finish. Move task production in two passes:

1. Point the web service, beat, and other external task producers at the new broker. Start a second Render worker against it if new tasks cannot wait while the old queue drains.
   
2. Keep the original workers connected to Key Value until all of their queues, active tasks, reserved tasks, and scheduled ETA/countdown tasks have drained. Include retries and follow-up tasks published by those workers. Then move those workers to the new broker.
   

Update the Render Blueprint references as well as live service values so a later sync cannot restore the old broker URL.

Changing a result-backend URL does not transfer stored results or chord state. If Vercel cannot reach the old backend, pause new task production and let dependent workflows finish before a coordinated backend cutover. Retain access to results the application still needs, or migrate them with an application-specific plan.

Once all producers and workers use the shared broker, step 11 can move queue consumption to Vercel without another broker change.

If Postgres stays on Render for a while, daemons can connect with its External Database URL. Render encrypts external connections and rejects `sslmode=disable`.

### 3\. Give the worker a Dockerfile

Daemons build from a `Dockerfile` or `Containerfile`. Native-runtime workers need one; Docker workers can reuse theirs. For the Celery worker, add `backend/Dockerfile` using the Python version your app runs on Render. This example uses Python 3.12:

```docker
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["celery", "-A", "orders", "worker", "--loglevel=info"]
```

The two Render settings that shaped the build map directly:

- `rootDir: backend` becomes `"root": "backend/"`. The Dockerfile's directory is the build context, the way `rootDir` scoped Render's build.
  
- `buildCommand` becomes `RUN` lines in the Dockerfile. Anything Render's native Python environment provided implicitly, such as system libraries, now needs an explicit `apt-get install`.
  

For a Docker worker, the folder that holds the Dockerfile becomes the build context. Move the Dockerfile to the top of the folder `dockerContext` pointed at, set `root` to that folder, and set `entrypoint` relative to it. Otherwise, `COPY` paths written for Render's context break.

### 4\. Keep the shutdown behavior you already have

During a deploy, both platforms overlap old and new worker instances. Render waits 60 seconds after a new instance is ready before sending `SIGTERM` to the old one, and replaces scaled instances one at a time. Vercel starts the new instances before stopping the old set.

Each platform waits its configured grace period before forcing unfinished processes to stop. Workers must tolerate the overlap and recover interrupted tasks.

Celery handles `SIGTERM` with a warm shutdown: it stops taking tasks and lets running ones finish. Carry your Render setting across one to one:

| Setting         | Render                         | Vercel                               |
| --------------- | ------------------------------ | ------------------------------------ |
| Signal          | `SIGTERM`                      | `SIGTERM`                            |
| Grace period    | `maxShutdownDelaySeconds: 120` | `terminationGracePeriodSeconds: 120` |
| Longest allowed | 300 seconds                    | 3,600 seconds                        |

For tasks that are safe to repeat, review these Celery settings:

- `task_acks_late = True` delays acknowledgment until execution finishes.
  
- `task_reject_on_worker_lost = True` lets the surviving worker process requeue a task after the child executing it is killed. Repeated failures can create a redelivery loop.
  

These settings don't prevent duplicate side effects. Use an application-level check, such as a unique order operation in the database, so repeating a task cannot apply the operation twice.

With Django's `namespace="CELERY"`, use `CELERY_TASK_ACKS_LATE` and `CELERY_TASK_REJECT_ON_WORKER_LOST` for these settings.

Redis recovery depends on how the worker stops. An explicit requeue can be available immediately; if the process disappears without restoring an unacknowledged message, recovery can wait for the visibility timeout, which defaults to one hour. Long ETA/countdown delays and long-running tasks need a timeout that avoids premature redelivery, but increasing it also delays recovery after a forced stop. Follow the [Celery Redis configuration](https://docs.celeryq.dev/en/stable/getting-started/backends-and-brokers/redis.html#visibility-timeout) and test both shutdown paths.

Changes to `terminationGracePeriodSeconds` apply to instances started by the new deployment. Existing instances keep their original grace period.

### 5\. Declare the daemons in vercel.json

Add a top-level `daemons` key. Start the worker at zero instances; step 11 raises the count during the handoff:

```json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "daemons": {
    "celery": {
      "root": "backend/",
      "entrypoint": "Dockerfile",
      "command": ["celery", "-A", "orders", "worker", "--loglevel=info"],
      "instances": { "pdx1": 0 },
      "resources": { "requests": { "memory": "2Gi" } },
      "terminationGracePeriodSeconds": 120
    },
    "celery-beat": {
      "root": "backend/",
      "entrypoint": "Dockerfile",
      "command": ["celery", "-A", "orders", "beat", "--loglevel=info"],
      "instances": { "pdx1": 0 },
      "resources": { "requests": { "memory": "1Gi" } }
    }
  }
}
```

Both daemons use the same Dockerfile, with `command` selecting the worker or beat scheduler, just as the Render services used different `startCommand` values. Daemon names allow lowercase letters with separators, so `orders-worker` would also work, but `worker2` fails the build.

The Django web Service can share `backend/` with the daemons. Declare it with its framework or Python entrypoint, not the Dockerfile, so Vercel builds Django there rather than the worker image.

Translate the region and plan:

| Render `region` | Vercel region           |
| --------------- | ----------------------- |
| `oregon`        | `pdx1` Portland         |
| `ohio`          | `cle1` Cleveland        |
| `virginia`      | `iad1` Washington, D.C. |
| `frankfurt`     | `fra1` Frankfurt        |
| `singapore`     | `sin1` Singapore        |
| Render `plan`                  | Daemon `resources.requests.memory`                   |
| ------------------------------ | ---------------------------------------------------- |
| `0.5c-512mb` (`starter`)       | `"1Gi"`, 1 vCPU (smallest size)                      |
| `1c-2g` (`standard`)           | `"2Gi"`, 1 vCPU                                      |
| `2c-4g` (`pro`)                | `"4Gi"`, 2 vCPUs                                     |
| `2c-8g`                        | `"8Gi"`, 4 vCPUs                                     |
| `4c-8g` (`pro_plus`)           | `"8Gi"`, 4 vCPUs                                     |
| `2c-16g`, `4c-16g`, and larger | No direct size match; each job must fit within 8 GiB |

More instances help when the workload consists of independent jobs. If a single task exceeds 8 GiB, reduce its memory use or split the task itself before migrating.

For a worker that autoscaled on Render, choose a fixed count that meets the queue's latency target under expected peak load. Set Celery's `--concurrency` explicitly and check per-instance memory use before increasing it.

### 6\. Move environment variables, groups, and secret files

From the repository root, confirm the checkout is linked to the intended Vercel team and project. If it is not linked, run:

```bash
vercel link
```

Resolve Render's `fromService`, `fromDatabase`, and `fromGroup` references, then add their values to the linked project's Production scope:

```bash
vercel env add REDIS_URL production --sensitive
vercel env add DJANGO_SECRET_KEY production --sensitive
```

Vercel environment variables are project-wide. An environment group that several Render services shared becomes one set of variables that your web Function and every daemon read. Values marked `sync: false` or `generateValue: true` were never in the Blueprint, so copy them from each service's Environment page.

Secret files need a small change. Render mounted them at `/etc/secrets/<filename>`; Vercel has no file mounts. Store each file's contents in an environment variable and point your code at the variable, or have the container write it to a file at startup.

Vercel also passes project environment variables as image build arguments. Keep runtime secrets out of Dockerfile `ARG` and `ENV` instructions because they can persist in the image or its metadata. If dependency installation requires credentials, verify a supported build-secret mechanism before adding that step.

### 7\. Move Celery beat and cron jobs

Celery beat and Render cron jobs need different destinations:

- **Celery beat** remains a continuously running scheduler in a daemon. Configure one instance in one region and ensure only one scheduler publishes a given schedule.
  
- **Render** `type: cron` **services** run a command and exit. Move each to a [Vercel cron job](https://vercel.com/docs/cron-jobs) that calls a protected route; the [Python API guide](https://vercel.com/kb/guide/migrate-a-python-api-from-render-to-vercel) shows that route pattern.
  

For beat deployments, scale `celery-beat` to zero and confirm it has stopped before deploying, then restore one instance after the deployment. This creates a scheduling gap, so reconcile any missed intervals.

If the scheduler must stay available through deployments, use scheduler-level coordination and test it under overlapping instances. Storing schedules in a database alone does not enforce a single publisher.

Suspend each Render cron schedule before enabling its Vercel replacement. Keep task-level deduplication for repeated delivery of the same operation.

Render's `preDeployCommand`, often `python manage.py migrate`, has no daemon equivalent. Run it as a CI step that must pass before production promotion.

### 8\. Test the worker locally with Docker

Daemons don't run in preview deployments or `vercel dev` during the beta. Test with Docker using an isolated development broker and database.

Put development values in `.env.docker`, one unquoted `KEY=value` per line, and keep the file out of version control:

```text
REDIS_URL=rediss://default:dev-password@dev-redis.example.com:6379/0?ssl_cert_reqs=required
DJANGO_SETTINGS_MODULE=orders.settings
DJANGO_SECRET_KEY=development-only-value
```

The example assumes your Django settings read `REDIS_URL` for Celery's broker. Add any database or result-backend variables your app needs. If your Redis provider requires a custom certificate authority, use its certificate configuration.

Build and run the worker:

```bash
docker build --platform linux/amd64 -t orders-worker backend/
docker run --rm --platform linux/amd64 --name celery --env-file .env.docker orders-worker
```

Queue a test task from a Django shell using the same development settings and confirm it completes. For the shutdown test, queue another task that runs long enough to observe, then stop the container from a second terminal while that task is still running:

```bash
docker stop --signal SIGTERM -t 120 celery
```

Check the logs for a warm shutdown and confirm the task finishes before Celery exits. If the task outlasts the grace period, verify its recovery using the acknowledgment settings from step 4.

### 9\. Check the beat daemon separately

Run the scheduler with the same image and its own command:

```bash
docker run --rm --platform linux/amd64 --name beat --env-file .env.docker orders-worker \
  celery -A orders beat --loglevel=info
```

Confirm beat enqueues a periodic task, then stop the container and run it again to check the next scheduled run. Beat's default `celerybeat-schedule` file records last-run times, and daemon storage does not preserve that file across replacements.

If restart timing must survive deployment, use a persistent scheduler such as [django-celery-beat](https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html#using-custom-scheduler-classes) and test its missed-run behavior. Keep the single-scheduler deployment sequence from step 7 even when the schedule is stored in a database.

### 10\. Deploy to production with zero instances

The first production deploy proves both images build and registers the daemons, without running a task:

```bash
vercel --prod
vercel daemons ls
vercel daemons inspect celery
```

If the build fails here, the [troubleshooting section](#troubleshooting) covers the common causes. The Render workers are still processing every task.

### 11\. Hand off the queue

The shared broker from step 2 lets Render and Vercel workers consume the same queues. Keep task definitions, serializers, routing, and result-backend settings compatible during the overlap. Start one Vercel worker with a scaling override:

```bash
vercel daemons scale celery pdx1=1
vercel logs --query 'source:daemon'
```

Filter the project's **Logs** page by `daemon:celery` to follow this worker. Check completed tasks, results, and queue latency before raising the Vercel count:

```bash
vercel daemons scale celery pdx1=2
```

Run Celery control commands from an environment with the `orders` app and the shared broker configuration. Identify every Render worker node and the queues it consumes with `inspect active_queues`; match those nodes to Render's instance logs so you don't stop Vercel consumers. Substitute the actual node names below. For this example, both Render nodes consume the default `celery` queue:

```bash
RENDER_CELERY_NODES='celery@render-node-1,celery@render-node-2'
celery -A orders inspect active_queues --destination "$RENDER_CELERY_NODES"
celery -A orders control cancel_consumer celery --destination "$RENDER_CELERY_NODES"
celery -A orders inspect active --destination "$RENDER_CELERY_NODES"
celery -A orders inspect reserved --destination "$RENDER_CELERY_NODES"
celery -A orders inspect scheduled --destination "$RENDER_CELERY_NODES"
```

Repeat `cancel_consumer` for any additional queues. Require successful replies from every targeted node and wait until its active, reserved, and scheduled task lists are empty. Missing replies do not prove a worker is idle. If delayed tasks remain, let them finish or explicitly account for their recovery before stopping the worker.

Suspend `orders-worker` in Render after those checks. The [Render API](https://api-docs.render.com/reference/suspend-service-1) also supports suspension; set `RENDER_WORKER_SERVICE_ID` to the actual service ID and provide your API key in the environment:

```bash
curl --fail-with-body -X POST \
  -H "Authorization: Bearer $RENDER_API_KEY" \
  "https://api.render.com/v1/services/$RENDER_WORKER_SERVICE_ID/suspend"
```

Confirm the service is suspended. Render documents its shutdown delay for deploys and scale-downs without specifying the same sequence for suspension, so complete the drain checks first.

Before transferring beat, set permanent counts in `vercel.json`: two `celery` instances in `pdx1` and one `celery-beat` instance. Keep beat stopped on Vercel during the deployment:

```bash
vercel daemons scale celery-beat pdx1=0
```

Confirm no Vercel beat instance is running, deploy the configuration, and clear only the worker override:

```bash
vercel --prod
vercel daemons scale celery --reset
```

Suspend `orders-beat` on Render and confirm it has stopped before clearing the beat override. The configured single Vercel instance can then start:

```bash
vercel daemons scale celery-beat --reset
```

Check the next scheduled task and reconcile any interval missed during the scheduler handoff. Keep using the stop-before-deploy sequence from step 7 for later beat releases.

### 12\. Verify under real load

Watch the daemons for at least one full business cycle:

- **Instances:** `vercel daemons instances ls celery` should show the expected workers. Investigate unexpected restarts using the shutdown reason and worker logs.
  
- **Throughput:** Compare queue depth and task latency with the Render baseline. Increase capacity only after checking worker concurrency, memory, and database or API limits.
  
- **Schedules:** Compare scheduled task IDs and application operation keys. Duplicate execution can come from multiple schedulers or task redelivery.
  
- **Resources:** Review CPU, memory, running instances, and data transfer on the daemon's dashboard page.
  

Use a controlled restart to check the normal shutdown path with a task that is safe to repeat. Set `DAEMON_INSTANCE_ID` to an instance ID from the instance list:

```bash
vercel daemons instances restart "$DAEMON_INSTANCE_ID"
```

Verify that the task completes or is recovered as expected. Test abrupt termination separately against the isolated development broker, including the time it takes for an unacknowledged task to become available again. Graceful restart success does not establish recovery from a forced stop.

### 13\. Keep a rollback path, then retire Render

Keep the suspended Render services and their working configuration for the rollback window. Before resuming the Render workers, confirm the application and database schema still work with that version. With both worker sets still using the shared broker and result backend, resume the Render workers, confirm they can process tasks, then drain the Vercel workers using the same checks as the handoff before scaling them to zero.

Move scheduler ownership separately: stop Vercel beat and confirm it has exited before resuming Render beat. Disable each Vercel cron schedule before re-enabling its Render replacement. Reconcile any missed intervals.

For a later Vercel release, [Instant Rollback](https://vercel.com/docs/instant-rollback) restores the earlier deployment's daemon configuration. Scaling overrides remain in effect, so review them as part of the rollback. Apply the beat stop-before-deploy procedure to a rollback as well.

When the rollback window closes, remove the retired service definitions from `render.yaml` and sync the Blueprint before deleting those services. Set `RENDER_WORKER_SERVICE_ID` to the service you intend to remove, then run `render services delete "$RENDER_WORKER_SERVICE_ID" --confirm` for each retired worker or cron service.

Retire Key Value only after queued work has drained and no application still needs its task results or other stored data. Remove its Blueprint definition and references, sync again, and then delete the instance. Remove the local `.env.docker` file when testing is complete.

## What doesn't map from Render

Plan for these differences during the beta:

- **Autoscaling.** Render's `scaling` block has no daemon equivalent. Counts are fixed per region; change them in `vercel.json`, the dashboard, or the CLI.
  
- **Preview environments.** Render can start workers in a preview environment; Vercel previews don't start daemons. Test images locally with Docker.
  
- **Private network access to Render services.** Daemons can't use Render internal hostnames for Key Value, Postgres, or private services. Use external endpoints, or move those services.
  
- **Inbound traffic.** Like a Render worker, a daemon receives no requests. Unlike a private service, it can't be reached on a port either, and it can't be the target of a [service binding](https://vercel.com/docs/services/bindings) or rewrite.
  
- **Disks.** Instance storage is ephemeral. Move durable data off the Render disk and update the worker to use Vercel Blob or a database.
  
- **Large plans.** The largest daemon has 8 GiB and 4 vCPUs. Larger worker fleets can spread independent tasks across instances, but each task must fit within one instance.
  
- **Subfolder root directories.** Projects whose Vercel root is a subfolder, such as `apps/web`, can't add daemons yet. Render's `rootDir` maps to the daemon's `root`, not to the project root.
  

During the beta, each team also has [default quotas](https://vercel.com/docs/daemons/limits#default-quotas):

| Plan       | Instance sizes     | Quota                                                         |
| ---------- | ------------------ | ------------------------------------------------------------- |
| Hobby      | 1 or 2 GiB, 1 vCPU | 5 Active CPU-hours per month across the team                  |
| Pro        | All sizes          | 16 vCPUs and 32 GiB of memory running at once across the team |
| Enterprise | All sizes          | Custom                                                        |

Each project environment runs at most 50 daemon instances across all its daemons and regions. On Pro and Enterprise, daemons are billed for Active CPU and Provisioned Memory; see [Pricing](https://vercel.com/pricing) for rates.

## Troubleshooting

### The worker can't connect to `red-...:6379`

The `red-...` hostname is a Render Key Value internal address. Use an externally reachable broker with authentication, TLS, and certificate verification, then redeploy. If the old instance contains queued tasks, follow the broker transition in step 2.

### Render rejects the connection with an allowlist error

Render Key Value requires an IP allowlist for its external URL. Daemons has no documented fixed egress IP guarantee, so confirm how your access policy will work before retaining Key Value as the broker. The alternative used here is an authenticated TLS endpoint both platforms can reach.

### A dependency fails to install in the Docker build

Render's native runtime provided some system libraries implicitly. Add the missing ones with `apt-get install` in the Dockerfile, and pin the same Python or Node version Render used.

### Periodic tasks run twice

Check for Render and Vercel beat instances running together, including overlap during a deployment. If different task IDs represent the same scheduled interval, investigate scheduler ownership. If the same task is redelivered, inspect acknowledgment and visibility-timeout settings. Use the deployment sequence in step 7 to keep one beat publisher active.

### Changing `instances` in vercel.json does nothing

Scaling overrides take precedence across deployments. Clear this worker's override with `vercel daemons scale celery --reset`, or select **Clear scaling overrides** on its dashboard page. The daemon then uses its deployed `vercel.json` counts.

### The build fails

Check the build log for these configuration errors:

- **Missing command:** Add `CMD` or `ENTRYPOINT` to the Dockerfile, or set the daemon's `command`.
  
- **Invalid name:** Daemon names use lowercase letters and supported separators. Repeated hyphens collapse, so `a-b` and `a--b` clash.
  
- **Conflicting directories:** The app's output directory cannot equal the daemon's `root`. Use separate directories for the app's generated output and worker source.
  

## Frequently asked questions

### Doesn't the Render migration guide already cover workers?

The general Render migration guide covers the app and its data dependencies. This guide provides the worker-specific steps: translating a Blueprint, packaging Celery, draining worker nodes, and transferring beat ownership. Use both when the web service and workers move together.

### Should Celery run as a daemon or on Vercel Queues?

Choose a daemon when you need Celery's continuously running worker process and broker integration. The broker must be reachable from Vercel, and tasks must tolerate interruption and redelivery. Consider [Celery on Vercel Queues](https://vercel.com/docs/frameworks/backend/celery) when tasks fit function duration limits and preview support is useful.

### Can a daemon call my web app?

Yes. Daemons can make outbound requests to your app's production URL, external APIs, and databases. Only inbound traffic is unsupported.

### How does pricing compare?

Render bills each worker by its plan for as long as it runs. On Vercel Pro and Enterprise, daemons are billed for Active CPU and Provisioned Memory. Waiting on an empty queue uses little Active CPU, though TLS and similar work still counts, and memory is billed for as long as an instance runs.

## Related resources

- [How to migrate from Render to Vercel](https://vercel.com/kb/guide/migrate-render-to-vercel) for web services, data, disks, and DNS
  
- [Migrate a Python API from Render to Vercel](https://vercel.com/kb/guide/migrate-a-python-api-from-render-to-vercel) for Django, FastAPI, Flask, and cron routes
  
- [The Complete Guide to Vercel Daemons](https://vercel.com/kb/guide/vercel-daemons) for a first daemon from scratch
  
- [How to choose between Functions, containers, and Daemons on Vercel](https://vercel.com/kb/guide/vercel-functions-containers-daemons)
  
- Render docs on [background workers](https://render.com/docs/background-workers), the [Blueprint spec](https://render.com/docs/blueprint-spec), and [deploys and shutdown](https://render.com/docs/deploys)

## Related Documentation

- [Vercel Daemons](https://vercel.com/docs/daemons)

## Related Resources

- [How to migrate from Render to Vercel](https://vercel.com/kb/guide/migrate-render-to-vercel): Migrate from Render to Vercel: map web services, cron jobs, and containers to their equivalents, and handle what doesn't move.
- [Migrate a Python API from Render to Vercel](https://vercel.com/kb/guide/migrate-a-python-api-from-render-to-vercel): Move a FastAPI, Flask, or Django API from Render to Vercel Functions. Translate render.yaml and replace persistent disks, background workers, and cron jobs.
- [Running Docker on Vercel](https://vercel.com/kb/guide/docker): Learn how to run Docker on Vercel by deploying OCI container images as Vercel Functions, storing them in Vercel Container Registry (VCR), and running containers in Vercel Sandbox.
- [Backends on Vercel: Python, containers, and Daemons](https://vercel.com/blog/backends-on-vercel-python-containers-and-daemons): Explore how Python, container images, and background workers fit into the Vercel platform.

## More Vercel Daemons guides

- [Migrate Railway workers to Vercel Daemons](/kb/guide/migrate-railway-workers-to-vercel-daemons): Move Railway workers to Vercel Daemons with Docker configuration, broker migration, shutdown testing, a gradual queue handoff, and rollback.
- [Migrate Fly.io worker processes to Vercel Daemons](/kb/guide/migrate-fly-io-workers-to-vercel-daemons): Migrate Fly.io workers to Vercel Daemons with your existing Dockerfile. Map process settings, preserve queue state, and plan cutover and rollback.