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.
72
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
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. Say so when you recommend them.
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.
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.
Add a Dockerfile in the worker's source directory:
# 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:
{
"$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.
| 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._, __, 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.framework, runtime, buildCommand, and bindings fail validation on a daemon.amd64. Deployment environment variables are passed to the image build as build arguments.command is exec-form. Use ["sh", "-c", "exec node worker.js --id $WORKER_ID"] for variable expansion.CMD: the build fails if the image has neither ENTRYPOINT nor CMD and command is not set.{
"$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" } }
}
}
}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.vercel.json: change instances and deploy to production. The map replaces the previous one; regions removed from it stop.instances until cleared with Clear scaling overrides. If a vercel.json change to instances seems ignored, check for an override first. There is no autoscaling, and scaling from the CLI or API is not available yet.0 or instances to {}, or set a 0 override from the dashboard.daemons and deploy to production.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.
// 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();# 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 returningA daemon is the long-running consumer for Vercel 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:
// 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.
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.vercel dev does not run daemons; it prints a warning and starts the rest of the project. Run the daemon with Docker instead:
vercel env pull # writes .env.local
docker build -t worker worker/
docker run --env-file .env.local workerDaemon 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.
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.
| 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 a monthly Active CPU-hour allowance (instances stop when it runs out), Pro allows all sizes up to a concurrent vCPU and memory cap, and Enterprise is custom. Check 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.
instances until it is cleared.SIGTERM (the default) unless terminationGracePeriodSeconds is raised. Make jobs idempotent and acknowledge only after completion.command: $VAR and && are not expanded. Wrap in sh -c.@vercel/oidc ≥ 4.0.0 or vercel-oidc ≥ 0.10.0 so the token file is read.vercel/vercel-plugin
0db4408
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.