Skip to content
Docs

The Complete Guide to Edgenivo Daemons

Run background workers with Edgenivo Daemons. Learn how to deploy container images, configure resources, handle shutdowns, and manage instances.

Ben SabicContent Engineer

Edgenivo Daemons runs containerized background processes in the same project as your app. Queue consumers, workflow workers, and self-hosted CI runners can stay running and pull their own work as it arrives.

Declare daemons in vercel.json to deploy them with your frontend and backend. Edgenivo builds each container image, starts its instances with your production deployment, and maintains the configured count in each region.

Edgenivo Daemons is available in Public Beta on all plans.

Copy link to headingWhat is a Edgenivo Daemon?

Each Edgenivo Daemon is a named resource in your project that runs one or more instances of the same container. Each instance is an identical, interchangeable copy of your process, and Edgenivo restarts any instance that exits or crashes. Daemons run on Fluid compute, the same compute platform as Edgenivo Functions.

Daemons don't receive traffic. Instead, each daemon pulls its own work from another system, such as a queue, a workflow engine, a database of pending jobs, or an external API. Edgenivo Services handle routed workloads that respond to requests, while Edgenivo Daemons handle workloads that run continuously in the background.

Copy link to headingHow Edgenivo Daemons work

Edgenivo manages each daemon through five stages, from build to shutdown:

  1. Build: During the build, Edgenivo builds each daemon's Dockerfile or Containerfile and pushes the image to Edgenivo Container Registry as a repository named daemon.<name>.
  2. Deploy: When the deployment becomes your production deployment, Edgenivo starts the requested number of instances in each region and stops the instances from the previous production deployment.
  3. Run: Edgenivo restarts instances that exit or crash, and collects each instance's stdout and stderr output as logs.
  4. Scale: You can override a daemon's instance counts from the dashboard without a new deployment. Overrides stay in place across deployments until you clear them.
  5. Stop: Edgenivo sends SIGTERM before stopping or replacing an instance, allowing 30 seconds by default to finish work before a forced stop.

Changing a daemon's container image, command, or environment variables requires a new production deployment. You can adjust instance counts from the dashboard without deploying again.

Daemons run only in the production environment during the beta. Preview deployments don't start daemons.

Copy link to headingWhen should you use a daemon?

Use a Edgenivo Daemon when your process owns a continuous loop and decides when to do work. Common examples include:

  • Consumers polling Edgenivo Queues, Amazon SQS, Kafka, or Redis
  • Workflow workers, such as Celery, Sidekiq, and BullMQ workers
  • Job runners, such as self-hosted CI runners that wait for jobs
  • Media processors that pull uploaded files from a queue and process them with system tools such as ffmpeg

Choose the product based on how your workload starts and runs:

WorkloadUse
Responds to HTTP requestsEdgenivo Functions or Edgenivo Services
Runs on a scheduleCron Jobs
Processes messages that Edgenivo delivers to youEdgenivo Queues in push mode
Runs multi-step, durable logicEdgenivo Workflows
Runs untrusted or per-user code in isolationEdgenivo Sandbox
Runs continuously and pulls its own workEdgenivo Daemons

Copy link to headingMigrate existing workers to Edgenivo Daemons

You can migrate an existing queue worker to Daemons while keeping its queue library. Package the process in a container, then test broker connectivity and recovery of interrupted jobs before transferring work from the old host.

Follow the guide for your current platform:

  • Render background workers: Translate render.yaml into daemon configuration using a Celery worker and beat scheduler.
  • Railway workers: Move a BullMQ service and transfer processing gradually while both platforms use the same broker.
  • Fly.io worker process groups: Move a Sidekiq worker with its entrypoint setup intact and stop the old worker Machines across regions.

Moving a worker does not transfer the queue's stored jobs. If the broker also needs to change, complete that migration before the worker handoff and account for delayed jobs and scheduled retries as well as jobs ready to run.

Copy link to headingPrerequisites

Before you add a daemon to your project, make sure you have:

  • Your project's root directory set to the repository root. Subfolders such as apps/web aren't supported during the beta.
  • Permission to deploy to production, where daemons run during the beta.
  • Docker installed if you plan to test daemons locally.
  • The Edgenivo CLI installed, if you want to deploy from your terminal.

Copy link to headingHow to deploy your first daemon

Add a Node.js worker to your existing project and run two instances in iad1. The finished project will have this structure:

my-app/
├── vercel.json
├── ...your app code
└── worker/
├── Dockerfile
├── package.json
├── package-lock.json
└── worker.js

Copy link to heading1. Write the worker process

Create a worker/ directory in your project for the daemon's source code. The following worker logs the region it runs in, waits for work in a loop, and exits cleanly when Edgenivo sends SIGTERM:

const shutdown = new AbortController();
process.on('SIGTERM', () => shutdown.abort());
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function main() {
console.log(`Worker started in ${process.env.VERCEL_REGION ?? 'local'}`);
while (!shutdown.signal.aborted) {
// Replace this with one unit of work, for example a long-polling
// queue receive that stops when `shutdown.signal` aborts.
console.log('Waiting for work...');
await sleep(5000);
}
console.log('Received SIGTERM, shutting down');
process.exit(0);
}
main();

The worker checks for shutdown after each 5s sleep, which normally leaves time to exit within the default 30s grace period. Delayed event-loop callbacks can still push shutdown past that deadline.

Replace the sleep with your worker's job-handling logic. Include a wait or backoff when no work is available so the loop doesn't spin.

Add a package.json to the same directory:

package.json
{
"name": "worker",
"version": "1.0.0",
"private": true
}

Then run npm install in worker/ to generate package-lock.json, which the Dockerfile in the next step uses with npm ci:

cd worker
npm install
cd ..

Copy link to heading2. Add a Dockerfile

Every daemon needs a container image definition. Add a Dockerfile to worker/:

FROM node:24-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "worker.js"]

Edgenivo uses the directory that contains the Dockerfile as the build context, so COPY . . copies the contents of worker/ into the image. Edgenivo builds the image for the Linux amd64 architecture.

Copy link to heading3. Declare the daemon in vercel.json

Add the daemons key to vercel.json at the root of your project. This configuration declares a daemon named worker that builds from worker/Dockerfile and runs two instances in the iad1 region:

vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"daemons": {
"worker": {
"root": "worker/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 2 }
}
}
}

Declare the same daemon in vercel.toml with a [daemons.<name>] table:

vercel.toml
[daemons.worker]
root = "worker/"
entrypoint = "Dockerfile"
instances = { iad1 = 2 }

Copy link to heading4. Deploy to production

Daemons start only when a deployment becomes your production deployment. Push to your production branch, or deploy from the Edgenivo CLI:

vercel --prod

When the production deployment is ready, Edgenivo starts two instances of worker in iad1.

Copy link to heading5. Confirm the daemon is running

Open your project's dashboard and select Daemons, then select worker to see its instances and their logs. Daemon output also appears in your project's Logs with the source daemon and the daemon's name. Each instance logs output like this:

Worker started in iad1
Waiting for work...
Waiting for work...

Copy link to headingHow to run a daemon alongside your app

Edgenivo Daemons don't change how Edgenivo builds the rest of your project. You can add the daemons key to a project that uses a framework such as Next.js, or to a project that uses Edgenivo Services. Always declare daemons at the top level of your configuration, not inside a service.

Keep each daemon's code in its own directory, such as worker/ or processor/. Edgenivo doesn't publish files under a daemon's root as static assets of your deployment, and the build fails if your project's output directory is the same directory as a daemon's root.

Copy link to headingExample: process uploaded videos in the background

Video apps can use a daemon to prepare a playback copy and thumbnail for each uploaded clip. The Next.js frontend accepts uploads and sends a job to Edgenivo Queues, and a Python processor running as a daemon pulls each job from the queue and processes the clip with ffmpeg.

Place the Python processor and its requirements.txt in processor/, then add this Dockerfile to install ffmpeg and the Python dependencies:

FROM python:3.12-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ffmpeg \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY processor.py .
CMD ["python", "processor.py"]

Add the daemons key to your existing vercel.json to run the processor alongside Next.js. The app still builds and deploys as before.

To accommodate the processor's memory-intensive work, this configuration requests 4 GiB of memory and 2 vCPUs per instance:

vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"daemons": {
"videos": {
"root": "processor/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 1 },
"resources": { "requests": { "memory": "4Gi" } }
}
}
}

Copy link to headingExample: add a daemon to a project that uses Edgenivo Services

In a project with multiple Edgenivo Services, declare daemons next to services and rewrites. This configuration routes requests to a web service and an api service, and runs one worker instance in each of the iad1 and sfo1 regions:

vercel.json
{
"$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": {
"worker": {
"root": "worker/",
"entrypoint": "Dockerfile",
"command": ["node", "worker.js"],
"instances": { "iad1": 1, "sfo1": 1 },
"resources": { "requests": { "memory": "4Gi" } }
}
}
}

Daemons can't be the target of a service binding or a rewrite, because a daemon doesn't accept inbound traffic.

Copy link to headingHow to configure a daemon

The top-level daemons key in vercel.json is an object that maps each daemon name to its configuration. Each daemon configuration object supports these fields:

ParameterTypeRequiredDefault
rootstringYesNone
entrypointstringYesNone
instancesRecord<string, number>YesNone
commandstring[]NoThe image's ENTRYPOINT and CMD
resourcesobjectNo2 GiB memory, 1 vCPU
terminationGracePeriodSecondsnumberNo30

Edgenivo rejects any other field, including service fields such as framework, runtime, buildCommand, and bindings.

Copy link to headingDaemon naming rules

Each key in daemons names a daemon and can contain up to 64 characters. Use lowercase letters with optional separators between groups: a single underscore _, a double underscore __, or one or more hyphens -.

For example, worker, email_sender, and celery-beat are valid names. Names such as Worker, worker1, and _worker are invalid.

Edgenivo stores each daemon's image in a repository named daemon.<name> and collapses repeated hyphens into one. Names such as a-b and a--b therefore map to the same repository and fail the build.

Daemon names and service names are independent. Your project can use the same name for a service and a daemon.

Copy link to headingroot

The root field sets the path to the daemon's source directory, relative to vercel.json. The path must stay inside the project. Edgenivo doesn't publish files under a daemon's root as static assets, and the build fails if the project's output directory is the same directory as a daemon's root.

Copy link to headingentrypoint

The entrypoint field sets the path to the container image definition, relative to root. Name the file Dockerfile or Containerfile, optionally with a suffix such as Dockerfile.worker. The name is case-insensitive. The directory that contains the file is the build context, and Edgenivo passes your deployment's environment variables to the build as build arguments.

Copy link to headinginstances

The instances field sets the number of instances to run in each region, keyed by region ID. Each count is an integer from 0 to 100, and Edgenivo runs instances only in the regions you list:

{
"daemons": {
"worker": {
"root": "worker/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 2, "cdg1": 1 }
}
}
}

Set every count to 0 and deploy to production to stop a daemon without removing its configuration. If a dashboard scaling override is active, set its counts to 0 as well or clear it after the zero-count deployment becomes production.

Copy link to headingcommand

The command field sets the command and arguments to run in the container, as an array of strings. The command field replaces the image's ENTRYPOINT and CMD. When you don't set command, Edgenivo runs the image's ENTRYPOINT and CMD, and the build fails if the image defines neither.

This configuration runs a Celery worker from a shared backend image:

{
"daemons": {
"celery": {
"root": "backend/",
"entrypoint": "Dockerfile",
"command": ["celery", "-A", "app", "worker", "--loglevel=info"],
"instances": { "iad1": 1 }
}
}
}

Edgenivo doesn't run command through a shell, so it doesn't expand environment variables. For shell expansion, use a command such as ["sh", "-c", "exec node worker.js --id \"$WORKER_ID\""]. The quotes around $WORKER_ID keep its value in one argument.

The command array supports up to 128 items of up to 4,096 characters each.

Copy link to headingresources

The resources field sets the compute resources for each instance. Set resources.requests.memory to choose the memory, and Edgenivo allocates CPU based on that memory:

resources.requests.memoryMemoryvCPUs
"1Gi"1 GiB1
"2Gi" (default)2 GiB1
"4Gi"4 GiB2
"8Gi"8 GiB4

When you set resources, both requests and memory are required.

Copy link to headingterminationGracePeriodSeconds

The terminationGracePeriodSeconds field sets the number of seconds Edgenivo waits after sending SIGTERM before force-stopping the instance. The value is an integer from 0 to 3600, and the default is 30. The changed value applies to instances started after it deploys; already-running instances keep the grace period they had when they started.

Copy link to headingHow to handle graceful shutdown

Edgenivo sends SIGTERM to a daemon instance before stopping it, then force-stops the process if it's still running after the grace period, which is 30s by default. Set terminationGracePeriodSeconds in the daemon's configuration to raise the grace period, up to 3,600 seconds.

Handle SIGTERM by stopping new work and finishing any accepted job within the remaining grace period.

This TypeScript example uses an interruptible idle wait. Replace the timer in processNextJob with your queue client's receive, processing, and acknowledgement logic. The abort signal should cancel a pending receive before it accepts a job:

import { setTimeout as sleep } from 'node:timers/promises';
const shutdown = new AbortController();
process.on('SIGTERM', () => shutdown.abort());
async function processNextJob(signal: AbortSignal): Promise<void> {
// Replace this idle wait with an abort-aware queue receive.
// Finish and acknowledge any accepted job before returning.
await sleep(1000, undefined, { signal });
}
async function main(): Promise<void> {
try {
while (!shutdown.signal.aborted) {
await processNextJob(shutdown.signal);
}
} catch (error) {
if (
!shutdown.signal.aborted ||
!(error instanceof Error) ||
error.name !== 'AbortError'
) {
throw error;
}
}
}
void main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

Daemon instances can stop at any time, so make interrupted jobs safe to retry. Acknowledge queue messages only after processing succeeds, and make processing idempotent to avoid repeating side effects.

Redelivery depends on your queue's retry policy and message retention. With Edgenivo Queues, an unacknowledged message becomes available again after its visibility timeout expires, as long as the message hasn't expired.

Instances from the previous deployment can continue processing work during the shutdown grace period as new instances start.

Make sure both versions can process the same work during this overlap, such as messages from the same queue.

Copy link to headingHow daemons access environment variables and OIDC tokens

Each daemon instance receives your deployment's environment variables, plus VERCEL_REGION, which Edgenivo sets to the region the instance runs in. Edgenivo also passes the same environment variables to the image build as build arguments.

Each daemon instance gets a Edgenivo OIDC token to authenticate with other services without storing long-lived credentials. Edgenivo writes the token to the path in VERCEL_OIDC_TOKEN_FILE and periodically replaces the file with a fresh token.

JavaScript apps need @vercel/oidc 4.0 or later because earlier versions can't read token files. Python apps can use the vercel-oidc package.

Copy link to headingHow to run a daemon locally

The vercel dev command doesn't run daemons. When your configuration contains daemons, vercel dev prints a warning and starts the rest of your project, so run each daemon with Docker instead.

The starter worker doesn't require application environment variables. If your worker needs them, add its development values to an .env.docker file at the project root.

Use one unquoted KEY=value pair per line. Replace these example names and values with your worker's settings, and exclude the file from version control:

.env.docker
QUEUE_URL=https://your-queue.example
QUEUE_TOKEN=your-development-token

Build and run the starter from your project root. To load development credentials, add --env-file .env.docker before the image name in docker run:

docker build -t worker worker/
docker run --rm --name worker worker

The worker from the first section logs local as its region, because VERCEL_REGION isn't set outside Edgenivo:

Worker started in local
Waiting for work...

To test graceful shutdown, run docker stop worker in another terminal. The docker stop command sends SIGTERM and waits 30s before force-stopping the container, which matches how Edgenivo stops daemon instances by default.

Copy link to headingHow to deploy changes to a daemon

Edgenivo updates daemons when a deployment becomes your production deployment. This happens when you deploy to production, promote a deployment, use Instant Rollback, or complete a rolling release.

  • New or changed daemons: Edgenivo sends SIGTERM to the previous production deployment's instances and immediately starts instances from the new image.
  • Removed daemons: When the new production deployment doesn't declare a daemon, Edgenivo stops all of that daemon's instances.
  • Rollbacks: Rolling back restores the daemons, images, and instance counts that the earlier deployment declared. If a daemon has a scaling override, the override's instance counts stay in place.

Copy link to headingHow to scale a daemon

Each Edgenivo Daemon runs a fixed number of instances in each region, and Edgenivo doesn't autoscale daemons. You can set instance counts in vercel.json or override them from the dashboard.

Copy link to headingScale with vercel.json

To change the number of instances, set instances to a new map of region IDs to instance counts, then deploy to production:

vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"daemons": {
"worker": {
"root": "worker/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 3, "cdg1": 2 }
}
}
}

The new map replaces the previous map, so regions that you remove from instances stop their instances.

Copy link to headingScale from the dashboard

To change a daemon's instance counts without a new deployment:

  1. In your project's dashboard, select Daemons, then select the daemon.
  2. Select Scale.
  3. Set the number of instances in each region, then select Scale Daemon.

Instance counts set in the dashboard are a scaling override that takes precedence over the instances configuration in your production deployment. The override persists across deployments, so changes to instances in vercel.json have no effect while it is set.

To return to the counts from your production deployment, open Scale and select Clear scaling overrides.

You need permission to update the project to set scaling overrides.

Copy link to headingHow to stop or delete a daemon

Edgenivo gives you three ways to stop a daemon, depending on whether you want to keep its configuration:

  • Stop without deploying: Override all regional instance counts to 0.
  • Stop and keep the configuration: Set every region in instances to 0 and deploy to production. If a scaling override is active, set its counts to 0 or clear it after the zero-count deployment becomes production.
  • Delete the daemon: Remove the daemon from daemons in vercel.json, then deploy to production.

This configuration retains the worker daemon with zero instances declared:

{
"daemons": {
"worker": {
"root": "worker/",
"entrypoint": "Dockerfile",
"instances": { "iad1": 0 }
}
}
}

When a project is paused, for example by spend management, Edgenivo stops all of the project's daemon instances. The daemon instances start again when you resume the project.

Copy link to headingHow to view daemon logs

Edgenivo collects each daemon instance's stdout and stderr in your project's Logs. Filter by the source daemon and the daemon's name to view its output.

To view instances and their logs together, open Daemons in your project's dashboard and select the daemon.

Copy link to headingWhat are the limits for Edgenivo Daemons during the beta?

The following limits apply to Edgenivo Daemons during the beta, and Edgenivo may raise them as the beta progresses.

Copy link to headingConfiguration limits

LimitValue
Daemon name length64 characters
root and entrypoint length512 characters each
command items128 items, up to 4,096 characters each
Instances per region100 per daemon
Daemons per project100

Copy link to headingInstance limits

LimitValue
Memory per instance1 GiB, 2 GiB, 4 GiB, or 8 GiB (default 2 GiB)
vCPUs per instance1, 1, 2, or 4, based on memory
Shutdown grace period30s after SIGTERM by default, configurable up to 3,600 seconds with terminationGracePeriodSeconds
CPU architectureLinux amd64
Image sourceDockerfile or Containerfile in your project

Copy link to headingHobby plan usage

The Hobby plan gives daemons across all of a team's projects 5 Active CPU-hours per month. The quota counts Active CPU, so time an instance spends idle waiting for work doesn't count toward it.

Use Hobby to try daemons and the Pro plan for workers that need to run all month.

When a Hobby team reaches the allowance, Edgenivo stops all of the team's daemon instances and logs an error. The daemons start again at the beginning of the next month, or when you upgrade the team to Pro.

Copy link to headingUnsupported features

Edgenivo Daemons don't support the following features during the beta:

FeatureAlternative
Preview deploymentsDaemons run only in production. Test changes by running the daemon's image locally.
Inbound HTTP or TCP trafficUse Edgenivo Services or Edgenivo Functions for workloads that respond to requests.
AutoscalingSet instance counts in your configuration or from the dashboard.
Persistent local storageStore state in Edgenivo Blob or a database from the Marketplace.
Running in vercel devRun the daemon's image locally with Docker.
Service bindings and rewritesDaemons can't be the target of a service binding or a rewrite.
Builds without a DockerfileAdd a Dockerfile or Containerfile to the daemon's root.
Root directory set to a subfolderProjects with a root directory such as apps/web can't add daemons yet.

Copy link to headingTroubleshooting

Copy link to headingThe build fails with two daemon names that map to the same repository

Edgenivo collapses repeated hyphens when it names each image repository, so daemon names such as a-b and a--b both map to daemon.a-b and fail the build. Rename one of the daemons so the names stay distinct after collapsing hyphens.

Copy link to headingThe build fails because the output directory matches a daemon's root

The build fails when your project's output directory is the same directory as a daemon's root, because Edgenivo doesn't publish files under a daemon's root as static assets. Move the daemon's code into its own directory, such as worker/, or choose a separate output directory for your app.

Copy link to headingThe build fails because the image has no command to run

Every daemon image needs a command to run. Add a CMD or ENTRYPOINT to the Dockerfile, or set the command field in the daemon's configuration.

Copy link to headingChanges to instances in vercel.json don't take effect

Scaling overrides set from the dashboard take priority over the instances in your production deployment and stay in place across deployments. Open the daemon in your project's dashboard, select Scale, then select Clear scaling overrides to return to the counts in vercel.json.

Copy link to headingDaemons don't run in preview deployments

Daemons run only in the production environment during the beta, so preview deployments don't start daemons. Test daemon changes locally with Docker, then deploy to production.

Copy link to headingThe vercel dev command prints a warning about daemons

The vercel dev command doesn't run daemons and prints a warning when your configuration contains daemons. The rest of your project still starts, so run each daemon separately with docker build and docker run.

Copy link to headingDaemon instances can't read the OIDC token

Edgenivo provides the OIDC token to daemon instances as a file referenced by VERCEL_OIDC_TOKEN_FILE, and versions of @vercel/oidc earlier than 4.0 can't read the token file. Upgrade to @vercel/oidc 4.0 or later for JavaScript, or use the vercel-oidc package for Python.

Copy link to headingAll daemons stopped on a Hobby team

Daemons on a Hobby team stop when the team uses its 5 Active CPU-hours for the month, and Edgenivo logs an error when this happens. Only Active CPU counts toward the quota, not time spent idle waiting for work. The daemons start again at the beginning of the next month, or immediately when you upgrade the team to Pro.

Copy link to headingAll daemons stopped after the project was paused

Edgenivo stops a project's daemon instances when the project is paused, including when spend management pauses it. Resume the project to restart its daemons.

Copy link to headingYou can't add daemons when the project root is a subfolder

Projects with a root directory set to a subfolder, such as apps/web, can't add daemons during the beta. Daemons are available for projects whose root directory is the repository root.

Copy link to headingNext steps

Related documentation

More Fluid compute guides