---
title: Migrate Fly.io worker processes to Vercel Daemons
description: Migrate Fly.io workers to Vercel Daemons with your existing Dockerfile. Map process settings, preserve queue state, and plan cutover and rollback.
url: "https://vercel.com/kb/guide/migrate-fly-io-workers-to-vercel-daemons"
published: 2026-10-26
last_updated: 2026-10-26
authors: Ben Sabic
related_resources:
  - title: "How to migrate from Fly.io to Vercel"
    url: "https://vercel.com/kb/guide/migrate-fly-io-to-vercel"
    description: "Move an app from Fly.io to Vercel. Map configuration, choose Functions or Daemons, plan persistent storage, and verify the production cutover."
  - 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 migrate a Fly.io worker process group to [Vercel Daemons](https://vercel.com/docs/daemons), reuse its Dockerfile, include any required image entrypoint in the daemon command, and connect it to a queue both platforms can reach. Deploy with zero instances, test the worker, then transfer processing from Fly Machines to Vercel.

The Rails example below has three process groups in `fly.toml`: `web` serves requests, `worker` runs Sidekiq, and `cron` runs Supercronic. The workers share application code with the web group, but their commands, shutdown behavior, and queue connections need separate configuration.

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

## Prerequisites

- Access to the Fly.io app and [flyctl](https://fly.io/docs/flyctl/install/), signed in to its organization.
  
- Vercel project with the repository as its [root directory](https://vercel.com/docs/builds/configure-a-build#root-directory). Subfolder roots such as `apps/web` aren't supported during the beta.
  
- Permission to deploy to production, 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/) and separate Redis, database, and application credentials for local testing.
  
- Enough [daemon quota](https://vercel.com/docs/daemons/limits#default-quotas) for the workload. Hobby allows 5 Active CPU-hours per month; reaching that quota stops the team's daemons until the next month or an upgrade.
  

Run CLI commands from the repository root containing `fly.toml` and `vercel.json`. Use `vercel link` to connect that directory to the intended Vercel project before adding variables or deploying.

## Should your process group become a daemon?

Check these requirements before moving a process group:

- **Does it pull its own work?** Queue consumers such as Sidekiq, Celery, and BullMQ fit this model. Check for direct connections over Fly's private network as well as `[http_service]` and `[[services]]`. HTTP handlers need a Vercel Function or [Service](https://vercel.com/docs/services); raw TCP listeners need a host that supports inbound TCP.
  
- **Is it meant to keep running?** Vercel restarts an instance even after exit code 0. Fly's default `on-failure` policy leaves a cleanly exited Machine stopped. Run-once tasks need a separate trigger, such as a [cron job](https://vercel.com/docs/cron-jobs) calling a Function.
  
- **Can it lose its local disk?** Daemon storage is ephemeral. Move persistent data from [Fly Volumes](https://fly.io/docs/volumes/overview/) to a database or [Vercel Blob](https://vercel.com/docs/vercel-blob), and update code that expects a mounted filesystem.
  
- **Does the image build for Linux** `amd64`**?** Test that architecture locally before deploying.
  
- **Does each job fit within 8 GiB and 4 vCPUs?** This is the largest daemon size during the beta. More instances help only when the work can be divided among them.
  

If you prefer Function-based processing, [Vercel Queues](https://vercel.com/docs/queues) and [Workflows](https://vercel.com/docs/workflows) provide alternatives that require adapting how jobs run. Daemons let you retain the worker process while updating its platform-specific configuration.

## How fly.toml worker settings map to Vercel

Each worker process group becomes a `daemons` entry in `vercel.json`. Reuse its Dockerfile and translate the settings below. Both platforms need access to the same queue before Vercel workers begin processing the backlog.

| Fly.io                                                                                          | Vercel                                                                                                                                        |
| ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Process group that needs no inbound traffic                                                     | Named entry under [`daemons`](https://vercel.com/docs/daemons/config-reference)                                                               |
| The group's command, which replaces `CMD` but keeps `ENTRYPOINT`                                | [`command`](https://vercel.com/docs/daemons/config-reference#command), which replaces both `ENTRYPOINT` and `CMD`                             |
| `[build] dockerfile`                                                                            | `entrypoint`, the Dockerfile path relative to the daemon's `root`                                                                             |
| `fly scale count worker=2 --region iad`                                                         | `instances: { "iad1": 2 }`, a count per [Vercel region](https://vercel.com/docs/regions)                                                      |
| `[[vm]]` with `processes = ["worker"]`                                                          | [`resources.requests.memory`](https://vercel.com/docs/daemons/config-reference#resources): 1, 2, 4, or 8 GiB, with 1 to 4 vCPUs               |
| `kill_signal`, `SIGINT` by default                                                              | Always `SIGTERM`                                                                                                                              |
| `kill_timeout`, 5 seconds by default, up to 300                                                 | [`terminationGracePeriodSeconds`](https://vercel.com/docs/daemons/config-reference#terminationgraceperiodseconds), 30 by default, up to 3,600 |
| `[[restart]]` policy, `on-failure` by default                                                   | Always restarts instances that exit or crash                                                                                                  |
| `fly secrets set` and `[env]`                                                                   | Project [environment variables](https://vercel.com/docs/environment-variables) in the Production scope                                        |
| `.internal` and `.flycast` addresses, Upstash Redis and Managed Postgres on the private network | Endpoints reachable over the internet, such as [Marketplace](https://vercel.com/marketplace) Redis and Postgres                               |
| Fly Volumes in `[mounts]`                                                                       | Vercel Blob or a database                                                                                                                     |
| Supercronic process group                                                                       | One configured daemon instance with protection against duplicate schedules, or [cron jobs](https://vercel.com/docs/cron-jobs)                 |
| Scheduled Machines from `fly machine run --schedule`                                            | Cron jobs that call a route                                                                                                                   |
| `release_command`                                                                               | A CI step before production promotion                                                                                                         |
| `fly logs`                                                                                      | [Logs](https://vercel.com/docs/logs/runtime) filtered by `daemon:<name>`                                                                      |
| `fly scale count` from the CLI                                                                  | `vercel daemons scale`                                                                                                                        |

Because daemons run only in production, deploy with zero instances so Vercel can build and register the daemon before it processes jobs.

## Steps

Keep the Fly worker group available until the handoff. Treat the queue and database changes in step 2 as separate production migrations, with their own maintenance window and rollback plan.

### 1\. Inventory the process groups

Start from `fly.toml`. The Rails app in this guide looks like this:

```text
app = "orders"
primary_region = "iad"

[processes]
web = "./bin/rails server -b [::] -p 8080"
worker = "bundle exec sidekiq"
cron = "supercronic /rails/config/crontab"

[http_service]
internal_port = 8080
processes = ["web"]

[[vm]]
size = "shared-cpu-1x"
memory = "1gb"
processes = ["web", "cron"]

[[vm]]
size = "performance-1x"
processes = ["worker"]
```

Check every group's inbound connections, running Machine count, and regions:

```bash
fly scale show
fly machine list
```

Estimate worker capacity from the started Machine count. By default, Fly creates one running Machine and one stopped standby for a group without services. Groups with mounted volumes receive one Machine instead. Record standby IDs so you can verify they remain stopped during cutover.

| Process group | What it does                  | Destination on Vercel                          |
| ------------- | ----------------------------- | ---------------------------------------------- |
| `web`         | Rails behind `[http_service]` | Container Function                             |
| `worker`      | Sidekiq, no service           | Daemon                                         |
| `cron`        | Supercronic, no service       | Daemon with duplicate protection, or cron jobs |

### 2\. Prepare shared data and queue access

Before moving the web group, check that Fly workers can reach the same Redis and database. The [Fly.io migration guide](https://vercel.com/kb/guide/migrate-fly-io-to-vercel) covers the web group, database, and volumes. Keep connection settings and job formats compatible across platforms.

Fly's `.internal` and `.flycast` endpoints require access to its private network. [Redis provisioned through Fly](https://fly.io/docs/upstash/redis/) and [Managed Postgres](https://fly.io/docs/mpg/) use private connectivity too. This migration uses authenticated endpoints reachable from both platforms, such as databases from the [Vercel Marketplace](https://vercel.com/marketplace/category/database). If your existing provider already supplies a suitable endpoint, you can keep the same database.

If Redis must move, complete that migration before the worker handoff:

1. Pause every producer, including web requests that enqueue jobs and recurring schedulers. Let active jobs finish, then stop the consumers before copying queue data.
   
2. Transfer the remaining queue state with a migration method supported by both Redis providers. Include delayed jobs and retries, retained failed jobs, and any scheduler or uniqueness data your application uses. An empty ready queue does not mean Redis has no pending work.
   
3. Confirm that the destination preserves this state, then update every producer and consumer to use it before resuming them. If the providers cannot transfer the required state, keep the old queue available under a separately tested drain plan.
   

For the Fly app, store the new `REDIS_URL=...` value in `.env.redis-migration`, excluded through both `.gitignore` and `.dockerignore`, with file permissions restricted to your user. Stage it only when you're ready to switch:

```bash
fly secrets import --stage < .env.redis-migration
fly secrets deploy
```

Staging defers the deployment, but any Machine started or updated afterward receives the new value. Keep producers paused through the deployment and verify each group's Redis connection before resuming work. Delete the temporary secret file when the change is complete.

Configure Sidekiq's Redis as a persistent store with `maxmemory-policy` set to `noeviction`. Account for Sidekiq's polling when choosing a plan with per-command billing. Confirm that new jobs complete and scheduled jobs and retries remain available on the destination before proceeding.

### 3\. Point the daemon at your existing Dockerfile

Reuse the Dockerfile that contains the application and worker dependencies. Keep that file for Fly and the daemons, and give the web Function its own copy:

```text
orders/
├── vercel.json
├── Dockerfile           # daemons and fly deploy build from this
├── Dockerfile.vercel    # copy for the web Function
├── fly.toml
├── app/
└── config/
```

Keep the files in sync. `Dockerfile` remains available for Fly during the rollback window; `Dockerfile.vercel` serves the web Function. Set `PORT` to `8080` for this Rails server, as described in the web migration guide.

Use `Dockerfile` for the daemon so the root-level `Dockerfile.vercel` remains available for web-app detection. In this example, set `root` to `./` and `entrypoint` to `Dockerfile`. The Dockerfile's directory becomes the build context. Files beneath a daemon's `root` aren't published as static assets, and the app's output directory must be separate.

Check Fly-specific build arguments and dependencies before reuse. Even if the image deploys on Fly, verify that Vercel can rebuild it for Linux `amd64`.

### 4\. Keep the image's entrypoint script

Fly's process-group command replaces `CMD` but preserves `ENTRYPOINT`. Vercel's `command` replaces both, so include any required entrypoint setup.

Build the image and inspect its entrypoint:

```bash
docker build --platform linux/amd64 -t orders .
docker inspect --format '{{json .Config.Entrypoint}}' orders
```

If the entrypoint is `["/rails/bin/docker-entrypoint"]`, use this daemon command:

```json
{
  "command": ["/rails/bin/docker-entrypoint", "bundle", "exec", "sidekiq"]
}
```

Retain any additional entrypoint arguments. Vercel doesn't insert a shell, so invoke one explicitly for shell expansion or pipelines.

Read the script before reuse: Rails-generated scripts commonly prepare the database only for server commands, while custom scripts may run migrations for every process. Move unconditional migrations to the CI release step, and use `exec` to pass control to Sidekiq so it receives shutdown signals.

### 5\. Switch the shutdown signal to SIGTERM

Fly and Vercel stop a process in different orders:

1. **Fly** sends `kill_signal`, which defaults to `SIGINT`, waits `kill_timeout` (5 seconds by default), then sends `SIGTERM`.
   
2. **Vercel** sends `SIGTERM` first, waits `terminationGracePeriodSeconds` (30 seconds by default), then force-stops the instance.
   

If a custom worker handles only `SIGINT`, add a `SIGTERM` handler before moving it.

On `SIGTERM`, Sidekiq stops fetching jobs, waits up to its `-t` timeout for active jobs, then tries to requeue unfinished jobs. Allow at least five more seconds for shutdown: with `-t 25`, set `terminationGracePeriodSeconds` to `30` or more.

| Setting                   | On Fly.io                | On Vercel                           |
| ------------------------- | ------------------------ | ----------------------------------- |
| Signal                    | `kill_signal = "SIGINT"` | `SIGTERM`                           |
| Sidekiq timeout           | `-t 25`                  | `-t 25`                             |
| Wait before a forced stop | `kill_timeout = 30`      | `terminationGracePeriodSeconds: 30` |

Test signal handling with Docker in step 9. Entrypoint scripts must pass signals to the worker, usually by ending with `exec "$@"`.

Jobs must tolerate retries without repeating an external effect such as charging a card. Sidekiq doesn't guarantee exactly-once execution, and a forced stop can lose active jobs with its default fetch strategy. Keep job payloads and database migrations compatible with the previous release because old and new instances overlap during a Vercel rollout.

### 6\. Declare the daemon in vercel.json

Add a `daemons` key next to the rest of your configuration. Start at zero instances; step 11 raises the count during the handoff:

```json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "daemons": {
    "sidekiq": {
      "root": "./",
      "entrypoint": "Dockerfile",
      "command": ["/rails/bin/docker-entrypoint", "bundle", "exec", "sidekiq", "-t", "25"],
      "instances": { "iad1": 0 },
      "resources": { "requests": { "memory": "2Gi" } },
      "terminationGracePeriodSeconds": 30
    }
  }
}
```

Choose the region based on Redis and database location, then check latency during handoff. The table lists candidate Vercel regions:

| Fly.io region      | Candidate Vercel region             |
| ------------------ | ----------------------------------- |
| `iad` Ashburn      | `iad1` Washington, D.C.             |
| `ewr` Secaucus     | `iad1` Washington, D.C.             |
| `ord` Chicago      | `cle1` Cleveland                    |
| `dfw` Dallas       | `cle1` or `iad1`                    |
| `yyz` Toronto      | `cle1` Cleveland or `yul1` Montréal |
| `sjc` San Jose     | `sfo1` San Francisco                |
| `lax` Los Angeles  | `sfo1` San Francisco                |
| `lhr` London       | `lhr1` London                       |
| `cdg` Paris        | `cdg1` Paris                        |
| `ams` Amsterdam    | `fra1` Frankfurt or `lhr1` London   |
| `fra` Frankfurt    | `fra1` Frankfurt                    |
| `arn` Stockholm    | `arn1` Stockholm                    |
| `jnb` Johannesburg | `cpt1` Cape Town                    |
| `nrt` Tokyo        | `hnd1` Tokyo                        |
| `sin` Singapore    | `sin1` Singapore                    |
| `syd` Sydney       | `syd1` Sydney                       |
| `gru` São Paulo    | `gru1` São Paulo                    |

Choose the instance size by measured memory use; Vercel allocates CPU with that size. Daemon names use lowercase letters with supported separators: `sidekiq` and `celery-beat` are valid, but `worker2` isn't.

| Fly.io `[[vm]]`                                                     | Daemon `resources.requests.memory`     |
| ------------------------------------------------------------------- | -------------------------------------- |
| `shared-cpu-1x` with `memory = "1gb"` (the size defaults to 256 MB) | `"1Gi"`, 1 vCPU (smallest daemon size) |
| `performance-1x`, 2 GB                                              | `"2Gi"`, 1 vCPU                        |
| `performance-2x`, 4 GB                                              | `"4Gi"`, 2 vCPUs                       |
| `performance-4x`, 8 GB                                              | `"8Gi"`, 4 vCPUs                       |

Fly's shared CPUs run at a small baseline with bursts, so a shared-CPU worker isn't directly comparable to a daemon vCPU. Size by the memory the worker uses, then check the CPU chart after the handoff.

### 7\. Rebuild secrets as environment variables

Use `fly secrets list` to inventory names and digests, then retrieve values from your secret manager or the service that issued them. Fly doesn't expose plaintext values through its secrets API. Avoid printing credentials from a running Machine into terminal output; command-history cleanup doesn't erase that output.

Review `[env]` in `fly.toml` alongside the secrets. Replace private hostnames and Fly-specific values before adding them to Vercel's Production environment:

```bash
fly secrets list
vercel env add RAILS_MASTER_KEY production --sensitive
vercel env add REDIS_URL production --sensitive
```

Add the database connection and any other settings the worker needs. Daemons use the project's environment variables, so the web Function and workers must agree on shared services. Replace uses of `FLY_REGION` with `VERCEL_REGION` and update any comparisons against Fly's region codes. If the app needs a primary-region setting, configure one explicitly with the intended Vercel region.

Environment variables are also available to daemon builds as build arguments. Keep secrets out of Dockerfile `ARG` instructions and image layers. For cloud authentication, use the short-lived [OIDC token](https://vercel.com/docs/oidc) available through `VERCEL_OIDC_TOKEN_FILE` where the provider supports it.

### 8\. Move schedulers and release\_command

Supercronic can run as a daemon with one configured instance in one region. Add `cron` alongside `sidekiq` in the existing `daemons` object. The excerpt below shows the new entry at zero instances while the Fly scheduler remains active:

```json
{
  "daemons": {
    "cron": {
      "root": "./",
      "entrypoint": "Dockerfile",
      "command": ["/rails/bin/docker-entrypoint", "supercronic", "/rails/config/crontab"],
      "instances": { "iad1": 0 },
      "resources": { "requests": { "memory": "1Gi" } }
    }
  }
}
```

Vercel briefly runs old and new instances during deployments, even when the configured count is one. Protect each scheduled occurrence against duplicate effects, for example with a database uniqueness constraint keyed by the schedule and intended run time. Test this protection before enabling the daemon. Give Supercronic enough shutdown time for any jobs it runs directly, or have it enqueue work for Sidekiq.

Other scheduling mechanisms need different handling:

- Scheduled Machines created with `fly machine run --schedule` run on a schedule and exit. Move suitable tasks to [cron jobs](https://vercel.com/docs/cron-jobs) that call protected Function routes, subject to the Function's runtime limits.
  
- [Sidekiq-Cron](https://github.com/sidekiq-cron/sidekiq-cron) coordinates scheduling across processes that share Redis. Preserve its job names and namespaces when moving it. Check other scheduler libraries individually; Redis coordination isn't a property of every scheduler embedded in Sidekiq.
  

During the initial handoff, stop Fly's standalone scheduler before starting its Vercel replacement. Check for missed schedule occurrences during that gap and reconcile them using the same duplicate protection.

Move Fly's `release_command` to a CI step that must pass before production promotion. Keep migrations compatible with workers running during rollout.

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

Run the production image against nonproduction Redis and a nonproduction database, with test credentials for any external services. Docker tests the process locally; production networking still needs verification during the handoff. Daemons don't run in preview deployments or `vercel dev` during the beta.

Create `.env.docker` with one unquoted `KEY=value` per line. Exclude it through both `.gitignore` and `.dockerignore`. Replace the example endpoints and add any test-only Rails credential key the app requires:

```text
REDIS_URL=rediss://default:dev-password@dev-redis.example.com:6379
DATABASE_URL=postgresql://dev-user:dev-password@dev-db.example.com:5432/orders_test
RAILS_ENV=production
SECRET_KEY_BASE=development-only-value
```

Run the daemon's command with those values:

```bash
docker run --rm --platform linux/amd64 --name sidekiq --env-file .env.docker \
  --entrypoint /rails/bin/docker-entrypoint orders bundle exec sidekiq -t 25
```

In another terminal, open a console inside the same container and enqueue a known test job:

```bash
docker exec -it sidekiq ./bin/rails console
```

Confirm that it completes against the test services. Then start a job that will still be running when you exercise shutdown:

```bash
docker stop --signal SIGTERM --timeout 30 sidekiq
```

The explicit signal overrides the image's `STOPSIGNAL`; the timeout enforces the 30-second shutdown window. Check logs and job state to confirm that the active job finished or was requeued before the container stopped. Restart the container and verify that retries complete without repeating external effects.

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

The first production deploy proves the image builds and registers the daemon. With zero instances, the command itself first runs in step 11:

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

If the build fails here, the [troubleshooting section](#troubleshooting) covers the common causes. The Fly worker group is still processing every job.

### 11\. Hand off the queue

Both platforms can consume the shared Redis queue while you move capacity. Keep them on compatible application versions and queue settings. Sidekiq supports multiple consumers, but retries can still execute a job more than once.

Start one Vercel instance without a deployment:

```bash
vercel daemons scale sidekiq iad1=1
vercel logs --query 'source:daemon'
```

Filter the project's **Logs** page by `daemon:sidekiq`. Confirm successful Redis and database connections, then check that representative jobs complete.

Increase Vercel's capacity while watching queue latency and downstream connection limits. The example uses two instances. Stop Fly workers one at a time, allowing Sidekiq its full shutdown window. Replace `WORKER_MACHINE_ID` with an ID from the inventory:

```bash
vercel daemons scale sidekiq iad1=2
fly machine stop WORKER_MACHINE_ID --signal SIGTERM --timeout 30
```

Check job outcomes and verify that every Fly worker Machine, including any standby, is stopped:

```bash
fly machine list
```

For Supercronic, stop every Fly `cron` Machine and confirm shutdown before enabling one Vercel instance:

```bash
vercel daemons scale cron iad1=1
```

Check the expected schedule occurrences and duplicate protection from step 8. Once processing is stable, record both final counts in `vercel.json` and deploy. Clear the overrides after that deployment so future scaling follows the file:

```bash
vercel daemons scale sidekiq --reset
vercel daemons scale cron --reset
```

Omit the cron commands if the app has no standalone scheduler.

### 12\. Verify under real load

Watch the daemon for at least one full business cycle:

- **Instances.** Run `vercel daemons instances ls sidekiq` and inspect logs for repeated restarts. Check startup failures, missing configuration, and memory pressure.
  
- **Throughput.** Compare queue latency and completion rates with the Fly baseline. Before increasing instances or concurrency, check Redis latency and database connection limits.
  
- **Resources.** Review CPU and memory on the daemon's dashboard page. Adjust the instance size or concurrency to the constraint you observe.
  

Check a controlled restart with a job whose outcome you can verify. Replace `INSTANCE_ID` with an ID from the instance list:

```bash
vercel daemons instances restart INSTANCE_ID
```

Confirm that the job finishes or returns to the queue and completes after restart. This checks graceful shutdown; crash recovery depends on the Sidekiq fetch strategy and must be tested separately with nonproduction jobs.

### 13\. Keep a rollback path, then retire the Fly group

Keep the Fly app, its `fly.toml`, and the stopped Machines for a rollback window long enough to observe the jobs and schedules you run. Keep their configuration pointed at the shared Redis and database from step 2.

To return worker processing to Fly, scale the Vercel daemon to zero in every configured region and confirm that its instances have stopped. For this example, run `vercel daemons scale sidekiq iad1=0`, then start the recorded Fly worker Machines with `fly machine start WORKER_MACHINE_ID`. Use a Fly image compatible with jobs enqueued by the current web app.

Roll back Supercronic separately. Set its Vercel count to zero, wait for shutdown, and only then start the Fly `cron` Machine. Reconcile any schedule occurrences missed during the gap. Leave the Redis migration in place unless you're following its separate rollback plan.

Avoid `fly deploy` during this window: deployments can start stopped Machines, and deploying an app with no Machines creates them again. This can unexpectedly resume a retired worker or scheduler.

For a later Vercel release, [Instant Rollback](https://vercel.com/docs/instant-rollback) restores the previous deployment's daemon configuration. Scaling overrides remain in effect, so inspect them before relying on rollback to change counts.

After the rollback window, remove retired process groups from `fly.toml` and deploy the remaining Fly app to apply that change. Destroy the app only if none of its workloads are still needed. Release unused static egress IPs with `fly ips release-egress`, and remove the local test environment files.

## What doesn't map from Fly.io

Check these constraints before retiring the Fly worker:

- **Private networking.** Daemons accept no inbound HTTP or TCP traffic and can't be targets of service bindings or rewrites. Calls to `worker.process.<app>.internal` need a queue-based design or an HTTP Service. The migration above uses databases reachable outside Fly's private network.
  
- **Preview deployments.** Daemons run only in production. Test images locally, then verify production connectivity during a controlled handoff.
  
- **Autoscaling.** Daemon counts are fixed per region. Fly Proxy also can't autostop a group without services; any separate autoscaling automation must be replaced or updated.
  
- **Volumes.** Daemon storage is ephemeral. Move persistent data to a database or object storage and adapt code that expects filesystem access.
  
- **Large jobs.** Each daemon instance is limited to 8 GiB and 4 vCPUs. Work exceeding that limit must be divided into smaller jobs or remain on a suitable host.
  
- **Subfolder root directories.** Projects whose Vercel root is a subfolder, such as `apps/web`, can't add daemons during the beta.
  

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 reach Redis or Postgres

Check the hostname in the Production environment variable. Fly `.internal` and `.flycast` names, and the private endpoint issued by Fly's Redis integration, require Fly-network access. Use the shared endpoint from step 2. If that endpoint resolves, check the provider's TLS requirements, credentials, and network access rules before changing it again.

### The worker starts but setup from the entrypoint script is missing

The daemon's `command` replaced the image's `ENTRYPOINT`. Add the entrypoint path as the first item of `command`, as in step 4.

### Jobs are killed mid-run on every deploy

Check that the entrypoint passes `SIGTERM` to the worker and the worker stops fetching new jobs. Allow at least Sidekiq's `-t` timeout plus five seconds before termination. With `-t 25`, use `terminationGracePeriodSeconds: 30` or more, then repeat the local shutdown test.

### Instances restart over and over

Inspect the logs and memory usage around each restart. Check for startup errors, missing configuration, or a process that finishes after one task. Daemons restart after clean exits too; move intentional run-once work to a suitable Function trigger or [cron job](https://vercel.com/docs/cron-jobs).

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

Check for a scaling override, which takes precedence across deployments. Run `vercel daemons scale NAME --reset` with the daemon name, or select **Clear scaling overrides** in its dashboard settings.

### The build fails

Check these configuration errors:

- The image has neither `CMD` nor `ENTRYPOINT`, and the daemon has no `command`.
  
- The daemon name is invalid or collides with another name after repeated hyphens are collapsed. Names contain lowercase letters separated by `_`, `__`, or hyphens, and can be at most 64 characters long.
  
- The app's output directory equals the daemon's `root`. Use a separate output directory or move the worker source into its own directory.
  

## Frequently asked questions

### Can Fly and Vercel process the same Sidekiq queue during migration?

Yes. Compatible Sidekiq processes can share a Redis queue during handoff. Keep job formats and database changes compatible across platforms, and make job effects safe to retry. Shared Redis doesn't guarantee exactly-once execution.

### Can web and workers use the same Dockerfile?

Yes. Use `Dockerfile` for the workers and a matching `Dockerfile.vercel` for the web Function in this example. Vercel builds them separately, and each daemon gets its own command. Keep shared dependencies and setup changes synchronized between the files.

### 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.

### Do I lose static egress IPs?

Fly's allocated egress IPs belong to the Fly app and don't move with its image. Before migrating a worker that depends on IP allowlists, confirm a supported Vercel egress configuration and have the destination service allow it. Verify the connection during the handoff before releasing the Fly addresses.

### Does a daemon waiting for work cost anything?

On Hobby, the quota counts Active CPU only, so an idle Sidekiq process uses little of it. On Pro and Enterprise, billing covers Active CPU and Provisioned Memory, so an instance's memory is billed while it runs.

## Related resources

- [How to migrate from Fly.io to Vercel](https://vercel.com/kb/guide/migrate-fly-io-to-vercel) for the web group, volumes, data, and DNS
  
- [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)
  
- Fly.io docs on [process groups](https://fly.io/docs/launch/processes/), [fly.toml runtime options](https://fly.io/docs/reference/configuration/), and [scaling counts](https://fly.io/docs/launch/scale-count/)

## Related Documentation

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

## Related Resources

- [How to migrate from Fly.io to Vercel](https://vercel.com/kb/guide/migrate-fly-io-to-vercel): Move an app from Fly.io to Vercel. Map configuration, choose Functions or Daemons, plan persistent storage, and verify the production cutover.
- [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 Render background workers to Vercel Daemons](/kb/guide/migrate-render-workers-to-vercel-daemons): Migrate Render workers to Vercel Daemons. Translate render.yaml, package Celery in Docker, prepare the broker, and verify the queue handoff.