Daemons
Daemons are available in Public Beta on all plans
Vercel Daemons run long-running background processes from container images, such as queue consumers, workflow workers, and job runners. Each daemon pulls work from a queue, workflow engine, or external API and processes it continuously.
Vercel keeps your configured number of instances running in each region and restarts instances that exit or crash. Daemons run on Fluid compute, the same compute platform as Vercel Functions.
Daemons are available on all plans, including Hobby, within each plan's quotas. Hobby has monthly quotas for Active CPU and Provisioned Memory. A daemon that is mostly waiting for work uses less Active CPU, but it uses Provisioned Memory for every hour it runs. See Default quotas.
Use a daemon when your process owns a continuous loop to retrieve work. Common examples include:
- Queue consumers that poll Vercel Queues, Amazon SQS, Kafka, or Redis, including workers for Celery, Sidekiq, and BullMQ.
- Workflow workers that retrieve tasks from a workflow engine.
- Job runners, such as self-hosted CI runners that wait for jobs.
Daemons complement other primitives, such as Vercel Functions to serve HTTP traffic, Vercel Workflows for durable execution, and Vercel Queues for serverless message processing with preview deployment support. Use Cron Jobs to run code on a schedule, Vercel Sandbox to run untrusted code in isolation, and Services to define multiple apps in a single project.
Compare background worker options to choose between continuous processes, queued jobs, and durable workflows. For detailed examples and migrations, see Running background workers on Vercel.
A daemon is a named resource in your project that runs one or more instances of the same workload. Each instance is an identical, interchangeable copy of the process:
- Build: During the build, Vercel builds each daemon's
DockerfileorContainerfileand pushes the image to Vercel Container Registry as a repository nameddaemon.<name>. - Deploy: When a deployment becomes your production deployment, including after a promotion or rollback, Vercel rolls out its configured daemons in each region while gracefully terminating instances from the previous deployment.
- Run: Your daemon runs continuously. Vercel forwards each instance's
stdoutandstderroutput to your project's Logs in the dashboard. - Retry: Vercel maintains the requested instance counts and restarts instances that exit, crash, or fail their health checks.
- Scale: You can override a daemon's instance counts without a new deployment. Overrides stay in place across deployments until you clear them. See Scaling a daemon.
- Stop: When it's time to stop your process, such as during a rollout or a manual restart, Vercel sends a
SIGTERMsignal. Your process has time to clean up resources before Vercel terminates it. See Graceful shutdown.
Package a long-running Node.js process as a daemon and deploy two instances to iad1 (Washington, D.C., USA).
Daemons use Dockerfile syntax to package code. In your worker's source directory, add a Dockerfile that builds a runnable version of your application:
FROM node:24-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "worker.js"]Then, declare a daemon using the daemons key in your project configuration:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"daemons": {
"worker": {
"root": "worker/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 2 }
}
}
}#:schema https://openapi.vercel.sh/vercel.json
[daemons.worker]
root = "worker/"
entrypoint = "Dockerfile"
instances = { iad1 = 2 }Deploy your project to production. Vercel will roll out two instances of worker to iad1, and their output will appear in your project's Logs.
Daemons run alongside your existing app without changing how Vercel builds it. For example, you can use Services to run a Next.js frontend alongside a FastAPI Python backend, then add BullMQ and Celery workers to consume work from each app.
Define a Dockerfile for each worker in workers/bullmq-worker/ and workers/celery-worker/, then add both daemons under the top-level daemons key in your project configuration:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"services": {
"web": { "root": "web/" },
"api": { "root": "api/", "entrypoint": "main:app" }
},
"rewrites": [
{ "source": "/api/(.*)", "destination": { "service": "api" } },
{ "source": "/(.*)", "destination": { "service": "web" } }
],
"daemons": {
"bullmq-worker": {
"root": "workers/bullmq-worker/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 1 }
},
"celery-worker": {
"root": "workers/celery-worker/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 1 }
}
}
}#:schema https://openapi.vercel.sh/vercel.json
[services.web]
root = "web/"
[services.api]
root = "api/"
entrypoint = "main:app"
[[rewrites]]
source = "/api/(.*)"
destination = { service = "api" }
[[rewrites]]
source = "/(.*)"
destination = { service = "web" }
[daemons.bullmq-worker]
root = "workers/bullmq-worker/"
entrypoint = "Dockerfile"
instances = { iad1 = 1 }
[daemons.celery-worker]
root = "workers/celery-worker/"
entrypoint = "Dockerfile"
instances = { iad1 = 1 }See the Daemon configuration reference for every field.
By default, Vercel restarts an instance when its process exits or crashes. To also catch a process that is running but stuck, add a livenessProbe to the daemon. Vercel sends an HTTP GET request to the instance on a schedule and restarts it when enough consecutive checks fail.
First, serve a health endpoint from the same process as the work loop. The worker examples above don't start an HTTP server, so adding a probe to them unchanged would fail every check and restart the instance repeatedly:
import { createServer } from 'node:http';
createServer((req, res) => {
res.statusCode = req.url === '/health' ? 200 : 404;
res.end();
}).listen(1234);
// Start the work loop next to the server.Then, point the livenessProbe at the endpoint's port and path:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"daemons": {
"worker": {
"root": "worker/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 1 },
"livenessProbe": {
"httpGet": { "port": 1234, "path": "/health" },
"initialDelaySeconds": 5
}
}
}
}#:schema https://openapi.vercel.sh/vercel.json
[daemons.worker]
root = "worker/"
entrypoint = "Dockerfile"
instances = { iad1 = 1 }
livenessProbe.httpGet = { port = 1234, path = "/health" }
livenessProbe.initialDelaySeconds = 5A check succeeds on an HTTP status from 200 to 399. Redirects are not followed, so a 3xx response counts as a success. A check fails on a connection error, no response within timeoutSeconds, or any other status.
Checks run from inside the instance, starting initialDelaySeconds after the container starts and repeating every periodSeconds. Each instance is probed separately, and the schedule restarts from scratch after every restart. A changed livenessProbe applies only to instances started after it deploys.
Keep timeoutSeconds below periodSeconds, and make the endpoint cheap and based on whether the process can make progress rather than on downstream dependencies. See livenessProbe in the configuration reference for every field and default.
Each instance is pending until its first check completes, passing while checks succeed, and failing after a failed check. A success resets the failure count, and failureThreshold consecutive failures trigger a restart.
The same policy covers failed health checks, exits, and crashes:
- Liveness-triggered restarts send
SIGKILLimmediately and do not honorterminationGracePeriodSeconds. - Restarts use exponential backoff, waiting 1 second and doubling up to a cap of 5 minutes. A run of at least 5 minutes resets the backoff.
- After 10 minutes of runs each shorter than 5 minutes, Vercel stops restarting the instance and replaces it with a new one.
With the configuration above and the remaining defaults (periodSeconds 10, timeoutSeconds 1, failureThreshold 3):
- 0s: the container starts.
- 5s: the first check returns
200. Checks continue every 10 seconds. - ~605s: the app has hung, and the check times out after 1 second (1 of 3). The checks at 615s and 625s also fail.
- ~625s: Vercel kills the container. The run lasted over 5 minutes, so it restarts 1 second later, and the first check runs 5 seconds after that.
- If every run keeps failing, restarts wait 2, 4, 8, 16 seconds, and so on. After 10 minutes of short runs, Vercel replaces the instance.
As a new version of your daemon rolls out, Vercel stops instances from the previous version. To let your process finish current tasks and clean up resources, Vercel sends a SIGTERM signal. Stop accepting new work immediately and start draining resources. By default, Vercel waits 30 seconds after sending SIGTERM, then terminates the process if it's still running. The same shutdown sequence applies when you reduce instance counts or remove a daemon.
To change how long Vercel waits, set terminationGracePeriodSeconds on the daemon. The value is an integer number of seconds from 0 to 3600:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"daemons": {
"worker": {
"root": "worker/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 1 },
"terminationGracePeriodSeconds": 50
}
}
}#:schema https://openapi.vercel.sh/vercel.json
[daemons.worker]
root = "worker/"
entrypoint = "Dockerfile"
instances = { iad1 = 1 }
terminationGracePeriodSeconds = 50An instance can stop at any time, so make jobs idempotent and interrupted jobs safe to retry. An idempotent job produces the same result if it runs more than once. For example, acknowledge a queue message only after you finish processing it.
Handle SIGTERM to stop taking new work and finish the work in progress:
const shutdown = new AbortController();
process.on('SIGTERM', () => shutdown.abort());
async function processNextJob(signal: AbortSignal): Promise<void> {
// Wait for one unit of work, for example with a long-polling queue
// receive that stops when `signal` aborts, then process it. Back off
// when there's no work so the loop doesn't spin.
}
async function main(): Promise<void> {
while (!shutdown.signal.aborted) {
await processNextJob(shutdown.signal);
}
process.exit(0);
}
void main();import signal
from threading import Event
shutdown = Event()
signal.signal(signal.SIGTERM, lambda signum, frame: shutdown.set())
def process_next_job(shutdown: Event) -> None:
# Replace this wait with a bounded queue poll, then process the job.
# Finish processing before returning, even if shutdown is requested.
shutdown.wait(timeout=1)
while not shutdown.is_set():
process_next_job(shutdown)
# Close queue connections and other resources before exiting.A daemon can call the services in its project through service bindings, without exposing those services to public traffic. Bindings always point to the deployment that started the instance. After a promotion or rollback, instances from the previous deployment keep calling that deployment's services until they stop.
Declare the bindings on the daemon:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"services": {
"api": { "root": "api/", "entrypoint": "main:app" }
},
"daemons": {
"worker": {
"root": "worker/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 1 },
"bindings": [
{ "type": "service", "service": "api", "format": "url", "env": "API_URL" }
]
}
}
}#:schema https://openapi.vercel.sh/vercel.json
[services.api]
root = "api/"
entrypoint = "main:app"
[daemons.worker]
root = "worker/"
entrypoint = "Dockerfile"
instances = { iad1 = 1 }
[[daemons.worker.bindings]]
type = "service"
service = "api"
format = "url"
env = "API_URL"Each instance receives the service's URL in the binding's environment variable and calls it like any other HTTP endpoint:
await fetch(new URL('jobs/next', process.env.API_URL));Each instance receives your deployment's environment variables. Vercel passes the same variables to the image build as build arguments.
Vercel also sets the following environment variables on each instance:
| Variable | Description | Example |
|---|---|---|
VERCEL_REGION | The region the instance runs in. Vercel ignores a VERCEL_REGION that you define. | iad1 |
VERCEL_OIDC_TOKEN_FILE | The path of the file that holds the instance's OIDC token. | /var/run/secrets/vercel.com/token |
Use the OIDC token to authenticate with other services. The token is valid for 30 minutes, and Vercel refreshes the file about every 5 minutes, so read the file each time you need a token. To read the token, use @vercel/oidc 4.0.0 or later for Node.js, or vercel-oidc 0.10.0 or later for Python.
vercel dev currently doesn't run daemons. When your configuration contains daemons, vercel dev prints a warning and starts the rest of your project. Run your daemon locally with Docker:
docker build -t worker worker/
docker run --env-file .env.local workerUse vercel env pull to download your project's development environment variables into .env.local.
During the beta, daemons run in production deployments with manually configured instance counts and ephemeral storage. You can scale daemons from the Vercel dashboard or the Vercel CLI.
Preview deployments, autoscaling, and persistent storage aren't supported. See Daemons limits for all limits and available alternatives.
The container image, command, and environment variables are part of the deployment. To change them, create a new production deployment. The command field replaces the image's ENTRYPOINT and CMD. See Managing daemons.
Deploy new instances counts or set a scaling override without a new deployment. Overrides stay in place across deployments until you clear them. See Scaling a daemon.
It uses the Provisioned Memory quota, and it uses the Active CPU quota only while your code executes. The Active CPU quota counts the CPU time your code spends executing, not uptime. Time spent waiting, such as long polling an empty queue, doesn't count, so a daemon that is mostly waiting for work will use less of your monthly Active CPU quota. Some work that looks like waiting still uses CPU: for example, HTTPS requests require cipher operations such as encryption, decryption, and signing, which consume Active CPU. The Provisioned Memory quota counts the memory of each instance for every hour it runs. See Default quotas for each plan's quota and what happens when you reach it.
Yes. Vercel CLI 65.6.0 or later builds daemons when you run vercel build locally.
Follow these guides to deploy background workers or migrate them from another platform:
- The complete guide to Vercel Daemons
- Use Vercel Daemons with eve agents
- Run a Python polling worker with Vercel Queues
- Migrate Railway workers to Vercel Daemons
- Migrate Render background workers to Vercel Daemons
- Migrate Fly.io worker processes to Vercel Daemons
- Chat SDK with Vercel Daemons: Process queued chat jobs and reply using Vercel Connect credentials.
- Managing daemons: Scale, stop, and monitor daemons.
- Daemon configuration reference: Every field you can set on a daemon.
- Daemons limits: Limits that apply to daemons during the beta.
- Poll mode: Consume Vercel Queues messages from a daemon.
- Build Output API daemons: How daemons appear in the Build Output API.
Was this helpful?