Skip to main content

daemons

Vercel Daemons guidance — long-running background workers built from a Dockerfile and declared under the `daemons` key in vercel.json. Use when running queue consumers, Celery/Sidekiq/BullMQ workers, workflow workers, or job runners next to an app on Vercel; configuring daemon instances per region, memory, or command; handling SIGTERM; or choosing between Daemons, Functions, Queues, and Workflows.

Source facts

Repository
vercel/vercel-plugin
Last source activity
October 29, 2026 at 10:13
Detected SKILL.md language
English
Stars
319
Forks
72

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
daemons
description
Vercel Daemons guidance — long-running background workers built from a Dockerfile and declared under the `daemons` key in vercel.json. Use when running queue consumers, Celery/Sidekiq/BullMQ workers, workflow workers, or job runners next to an app on Vercel; configuring daemon instances per region, memory, or command; handling SIGTERM; or choosing between Daemons, Functions, Queues, and Workflows.
summary
Vercel Daemons (public beta) run long-running containers with no inbound traffic, declared under the top-level `daemons` key in vercel.json as `{ root, entrypoint: 'Dockerfile', instances: { iad1: 2 } }`. Vercel builds the image, rolls it out with the production deployment, keeps the instance counts per region, and sends SIGTERM (30s default grace, configurable with `terminationGracePeriodSeconds`) on rollout. Production only during the beta.
metadata
{"priority":6,"docs":["https://vercel.com/docs/daemons","https://vercel.com/docs/daemons/config-reference","https://vercel.com/docs/daemons/managing-daemons","https://vercel.com/docs/daemons/limits","https://vercel.com/docs/build-output-api/daemons"],"sitemap":"https://vercel.com/sitemap.xml","pathPatterns":["vercel.json"],"bashPatterns":[],"importPatterns":[],"promptSignals":{"phrases":["vercel daemon","daemons key","daemon on vercel","daemons on vercel","worker on vercel","celery on vercel","sidekiq on vercel","bullmq on vercel"],"allOf":["[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]"],"anyOf":["daemon","worker","long-running","always-on","celery","sidekiq","bullmq","dockerfile","instances","sigterm"],"noneOf":["systemd","docker daemon","dockerd","daemonset","launchd","web worker","service worker","cloudflare worker"],"minScore":6}}
retrieval
{"aliases":["Vercel Daemons","daemon","background worker","long-running worker","queue consumer"],"intents":["run a long-running background worker on Vercel","run a queue consumer next to my app","deploy a Celery, Sidekiq, or BullMQ worker","configure the daemons key in vercel.json","scale daemon instances per region","handle SIGTERM for graceful shutdown","choose between Daemons, Functions, Queues, and Workflows"],"entities":["daemons","instances","entrypoint","root","resources.requests.memory","VERCEL_OIDC_TOKEN_FILE","PollingQueueClient"],"examples":["add a BullMQ worker to my Next.js project on Vercel","run a Celery worker next to my FastAPI service","poll Vercel Queues from an always-on worker","my daemon does not run on preview deployments","how do I scale my daemon to two regions"]}
# Vercel Daemons You are an expert in Vercel Daemons, the long-running background processes that run from a container image in the same project as an app. Daemons are [in Public Beta on all plans](https://vercel.com/docs/daemons). Say so when you recommend them. ## What It Is A daemon is a named resource in a project that runs a fixed number of identical instances of one container image in each region you list. Vercel builds the image from the daemon's `Dockerfile` or `Containerfile`, rolls it out when the deployment becomes the production deployment, restarts instances that exit or crash, and forwards `stdout`/`stderr` to the project's Logs. Daemons run on Fluid compute. Daemons **cannot receive inbound traffic**. They pull work from a queue, a workflow engine, or an external API. ## When to Use Daemons Use a daemon when the process owns a continuous loop that retrieves work. | Need | Use | Why | |------|-----|-----| | Always-on queue consumer (Vercel Queues poll mode, SQS, Kafka, Redis), Celery/Sidekiq/BullMQ worker, workflow-engine worker, self-hosted CI runner | **Daemons** | Long-running loop, fixed instances per region, no request needed | | HTTP APIs, webhooks, streaming, WebSockets | Vercel Functions (`⤳ skill: vercel-functions`) | Daemons can't receive traffic | | Fire-and-forget background job, fan-out, buffering with Vercel invoking your handler | Vercel Queues push mode (`⤳ skill: queues`) | Serverless, scales automatically, works in previews | | Durable multi-step logic with sleep, hooks, or approvals | Vercel Workflows (`⤳ skill: workflow`) | Durable steps and replay | | Run code on a schedule | Cron Jobs (`⤳ skill: vercel-functions`) | Time-based trigger | | Run untrusted or AI-generated code in isolation | Vercel Sandbox (`⤳ skill: vercel-sandbox`) | Ephemeral microVMs | | Multiple frontends and backends in one project | Vercel Services (`⤳ skill: vercel-services`) | Daemons sit next to services; they are not services | If the user's work is request-driven, prefer Functions or Queues push mode. Reach for a daemon only when something must stay running. ## Quick Start Add a `Dockerfile` in the worker's source directory: ```docker # worker/Dockerfile FROM node:24-slim WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --omit=dev COPY . . CMD ["node", "worker.js"] ``` Declare the daemon under the top-level `daemons` key: ```json filename="vercel.json" { "$schema": "https://openapi.vercel.sh/vercel.json", "daemons": { "worker": { "root": "worker/", "entrypoint": "Dockerfile", "instances": { "iad1": 2 } } } } ``` The same configuration works in `vercel.toml` as a `[daemons.worker]` table. Deploy to production. Vercel builds the image, pushes it to Vercel Container Registry as `daemon.worker`, and starts two instances in `iad1`. Daemons are always declared at the top level, never inside `services`. They run alongside an existing app (Next.js, Services, or anything else) without changing how Vercel builds it. ## Configuration Reference | Field | Type | Required | Notes | |-------|------|----------|-------| | `root` | `string` | Yes | Source directory, relative to `vercel.json`. Must stay inside the project. Files under it are not published as static assets | | `entrypoint` | `string` | Yes | Path to the `Dockerfile` or `Containerfile`, relative to `root`. Its directory is the build context | | `instances` | `Record<region, number>` | Yes | Instances per region ID, each `0`–`100`. Vercel runs only in the listed regions | | `command` | `string[]` | No | Replaces the image's `ENTRYPOINT` and `CMD`. Not run through a shell | | `resources` | `{ requests: { memory } }` | No | `1Gi` (1 vCPU), `2Gi` (1 vCPU, default), `4Gi` (2 vCPU), `8Gi` (4 vCPU) | | `terminationGracePeriodSeconds` | `number` | No | Seconds to wait after `SIGTERM` before terminating, `0`–`3600`, default `30` | - **`instances` vs `replicas`**: the published `vercel.json` schema and the Build Output API use `instances`. `replicas` is a deprecated alias that is ignored when `instances` is set. If the project already uses `replicas`, leave it working, but write `instances` in new configuration. - **Names**: lowercase letters only, joined by `_`, `__`, or one or more `-` (`worker`, `email_sender`, `celery-beat`; not `Worker`, `worker1`, `_worker`), 64 characters max. Two names that collapse to the same image repository once repeated hyphens are merged, such as `a-b` and `a--b`, fail the build. A daemon and a service may share a name. - **Rejected fields**: service fields such as `framework`, `runtime`, `buildCommand`, and `bindings` fail validation on a daemon. - **Build**: images are built for Linux `amd64`. Deployment environment variables are passed to the image build as build arguments. - **Shell features**: `command` is exec-form. Use `["sh", "-c", "exec node worker.js --id $WORKER_ID"]` for variable expansion. - **No `CMD`**: the build fails if the image has neither `ENTRYPOINT` nor `CMD` and `command` is not set. ```json filename="vercel.json" { "$schema": "https://openapi.vercel.sh/vercel.json", "daemons": { "celery": { "root": "backend/", "entrypoint": "Dockerfile", "command": ["celery", "-A", "app", "worker", "--loglevel=info"], "instances": { "iad1": 2, "cdg1": 1 }, "resources": { "requests": { "memory": "4Gi" } } } } } ``` ## Lifecycle and Scaling - **Production only during the beta.** Daemons run only for the production deployment. Preview deployments do not start daemons; test changes by running the image locally. - **Rollout**: when a deployment becomes production (deploy, promote, Instant Rollback, or a completed rolling release), Vercel starts the new instances and sends `SIGTERM` to the previous deployment's instances. For a short time both versions run at once, so both must be able to consume the same queue and tolerate the same database schema. - **Image, command, and environment variables** belong to the deployment. Change them with a new production deployment. - **Scaling with `vercel.json`**: change `instances` and deploy to production. The map replaces the previous one; regions removed from it stop. - **Scaling overrides**: in the project dashboard, **Daemons** → select the daemon → **Scale** → **Scale Daemon**, or with Vercel CLI 64.3.0+ run `vercel daemons scale worker iad1=3 fra1=0` (regions you don't name keep their counts). Overrides persist across deployments and win over `instances` until cleared with **Scale** → **Clear scaling overrides** or `vercel daemons scale worker --reset`. If a `vercel.json` change to `instances` seems ignored, check for an override first. There is no autoscaling. - **Stop without deleting**: set every region to `0` or `instances` to `{}`, or set a `0` override from the dashboard. - **Delete**: remove the daemon from `daemons` and deploy to production. - **Paused projects** stop all daemon instances; they start again when the project resumes. ## Graceful Shutdown Vercel sends `SIGTERM` on rollout, scale-down, removal, and manual restart, then waits 30 seconds by default (`terminationGracePeriodSeconds`, `0`–`3600`) before terminating the process. Stop taking new work immediately and finish the job in progress. Instances can stop at any time, so jobs must be idempotent and safe to retry; acknowledge a queue message only after processing it. ```ts // worker/worker.ts const shutdown = new AbortController(); process.on('SIGTERM', () => shutdown.abort()); async function main(): Promise<void> { while (!shutdown.signal.aborted) { await processNextJob(shutdown.signal); // bounded poll; back off when there is no work } process.exit(0); } void main(); ``` ```python # worker/worker.py import signal from threading import Event shutdown = Event() signal.signal(signal.SIGTERM, lambda signum, frame: shutdown.set()) while not shutdown.is_set(): process_next_job(shutdown) # bounded poll, finish the current job before returning ``` ## Consuming Vercel Queues From a Daemon A daemon is the long-running consumer for [Vercel Queues poll mode](https://vercel.com/docs/queues/poll-mode). Use `PollingQueueClient` from `@vercel/queue` with an explicit region, because messages can only be received from the region they were sent to: ```ts // worker/worker.ts import { PollingQueueClient } from '@vercel/queue'; const { receive } = new PollingQueueClient({ region: 'iad1' }); const shutdown = new AbortController(); process.on('SIGTERM', () => shutdown.abort()); while (!shutdown.signal.aborted) { const result = await receive('orders', 'fulfillment', async (message) => { await processOrder(message); // acknowledged when this resolves, retried if it throws }, { limit: 10 }); if (!result.ok && result.reason === 'empty') { await new Promise((r) => setTimeout(r, 1000)); } } ``` Run the daemon in the same region the producer sends to. Topics are partitioned by deployment ID by default; read the poll-mode versioning section before relying on cross-deployment consumption. For the producer side and push-mode consumers, load `⤳ skill: queues`. ## Environment Variables and OIDC - Each instance receives the deployment's environment variables. - Each instance receives an OIDC token written to a file whose path is in `VERCEL_OIDC_TOKEN_FILE`, refreshed periodically. Read it with `@vercel/oidc` 4.0.0 or later (Node.js) or `vercel-oidc` 0.10.0 or later (Python). Older SDK versions that only read `VERCEL_OIDC_TOKEN` will not find it. ## Local Development `vercel dev` does not run daemons; it prints a warning and starts the rest of the project. Run the daemon with Docker instead: ```bash vercel env pull # writes .env.local docker build -t worker worker/ docker run --env-file .env.local worker ``` ## Logs and Observability Daemon output appears in the project's Logs with source `daemon` and the daemon's name, so you can filter to one daemon. The project dashboard's **Daemons** tab shows each daemon's instances, status, and logs. Vercel CLI 64.3.0+ has `vercel daemons ls`, `vercel daemons inspect <name>`, and `vercel daemons instances ls|inspect|restart` (production only). ## Build Output API With `vercel build` or a custom builder, `.vercel/output/config.json` lists daemons as `"daemons": { "worker": {} }` (the complete set; omitted daemons are stopped), and each has `.vercel/output/daemons/<name>/.vc-config.json` with `runtime: "container"`, an `image` already in Vercel Container Registry, `instances`, and optional `command`, `resources`, and `terminationGracePeriodSeconds`. `vercel build` builds and pushes the images and writes these files for you; during the beta, set `VERCEL_ENABLE_DAEMONS_BOA=1` when you run `vercel build` locally. ## Limits | Limit | Value | |-------|-------| | Instances per region | 100 per daemon | | Memory per instance | 1, 2 (default), 4, or 8 GiB | | Shutdown grace period | 30 seconds after `SIGTERM` by default, configurable up to 3,600 seconds with `terminationGracePeriodSeconds` | | CPU architecture | Linux `amd64` | Default per-team quotas apply during the beta: Hobby is limited to 1–2 GiB instances and monthly Active CPU and Provisioned Memory quotas (Provisioned Memory counts every hour an instance runs, including while waiting for work; when either runs out, daemons stop until the first of the next month (UTC) or until the team upgrades to Pro), Pro allows all sizes up to a concurrent vCPU and memory cap, and Enterprise is custom. Check [Daemons limits](https://vercel.com/docs/daemons/limits) for the current numbers before sizing a deployment. Not supported during the beta: preview deployments, inbound HTTP or TCP traffic, autoscaling, persistent local storage (use Vercel Blob or a Marketplace database), running in `vercel dev`, being the target of a service binding or rewrite, builds without a `Dockerfile`/`Containerfile`, and projects whose root directory is a subfolder such as `apps/web`. ## Common Pitfalls 1. **Expecting traffic**: a daemon that listens on a port receives nothing. Serve requests with Functions or Services, and have the daemon pull work. 2. **Testing on a preview**: daemons never start for preview deployments during the beta. Deploy to production or run the image locally. 3. **Ignored scaling change**: a scaling override (dashboard or CLI) wins over `instances` until it is cleared. 4. **Unsafe overlap**: old and new versions run side by side during a rollout. Keep message formats and schema migrations backward compatible. 5. **No SIGTERM handling**: the process is killed 30 seconds after `SIGTERM` (the default) unless `terminationGracePeriodSeconds` is raised. Make jobs idempotent and acknowledge only after completion. 6. **Shell syntax in `command`**: `$VAR` and `&&` are not expanded. Wrap in `sh -c`. 7. **Monorepo root directory**: projects with a subfolder root directory cannot add daemons yet. 8. **Stale OIDC SDK**: use `@vercel/oidc` ≥ 4.0.0 or `vercel-oidc` ≥ 0.10.0 so the token file is read. ## References - 📖 docs: https://vercel.com/docs/daemons - 📖 configuration reference: https://vercel.com/docs/daemons/config-reference - 📖 managing daemons: https://vercel.com/docs/daemons/managing-daemons - 📖 limits and quotas: https://vercel.com/docs/daemons/limits - 📖 Build Output API: https://vercel.com/docs/build-output-api/daemons - 📖 Queues poll mode: https://vercel.com/docs/queues/poll-mode - 📖 Functions vs containers vs Daemons: https://vercel.com/kb/guide/vercel-functions-containers-daemons
View on GitHub