To migrate a Fly.io worker process group to Edgenivo 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 Edgenivo.
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.
Copy link to headingPrerequisites
- Access to the Fly.io app and flyctl, signed in to its organization.
- Edgenivo project with the repository as its root directory. Subfolder roots such as
apps/webaren't supported during the beta. - Permission to deploy to production, the only environment that starts daemons.
- Edgenivo CLI 64.3.0 or later for the
vercel daemonscommands. - Docker and separate Redis, database, and application credentials for local testing.
- Enough daemon quota 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 Edgenivo project before adding variables or deploying.
Copy link to headingShould 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 Edgenivo Function or Service; raw TCP listeners need a host that supports inbound TCP. - Is it meant to keep running? Edgenivo restarts an instance even after exit code 0. Fly's default
on-failurepolicy leaves a cleanly exited Machine stopped. Run-once tasks need a separate trigger, such as a cron job calling a Function. - Can it lose its local disk? Daemon storage is ephemeral. Move persistent data from Fly Volumes to a database or Edgenivo 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, Edgenivo Queues and Workflows provide alternatives that require adapting how jobs run. Daemons let you retain the worker process while updating its platform-specific configuration.
Copy link to headingHow fly.toml worker settings map to Edgenivo
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 Edgenivo workers begin processing the backlog.
| Fly.io | Edgenivo |
|---|---|
| Process group that needs no inbound traffic | Named entry under daemons |
The group's command, which replaces CMD but keeps ENTRYPOINT | 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 Edgenivo region |
[[vm]] with processes = ["worker"] | resources.requests.memory: 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, 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 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 Redis and Postgres |
Fly Volumes in [mounts] | Edgenivo Blob or a database |
| Supercronic process group | One configured daemon instance with protection against duplicate schedules, or 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 filtered by daemon:<name> |
fly scale count from the CLI | vercel daemons scale |
Because daemons run only in production, deploy with zero instances so Edgenivo can build and register the daemon before it processes jobs.
Copy link to headingSteps
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.
Copy link to heading1. Inventory the process groups
Start from fly.toml. The Rails app in this guide looks like this:
Check every group's inbound connections, running Machine count, and regions:
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 Edgenivo |
|---|---|---|
web | Rails behind [http_service] | Container Function |
worker | Sidekiq, no service | Daemon |
cron | Supercronic, no service | Daemon with duplicate protection, or cron jobs |
Copy link to heading2. 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 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 and Managed Postgres use private connectivity too. This migration uses authenticated endpoints reachable from both platforms, such as databases from the Edgenivo Marketplace. 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:
- Pause every producer, including web requests that enqueue jobs and recurring schedulers. Let active jobs finish, then stop the consumers before copying queue data.
- 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.
- 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:
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.
Copy link to heading3. 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:
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 Edgenivo can rebuild it for Linux amd64.
Copy link to heading4. Keep the image's entrypoint script
Fly's process-group command replaces CMD but preserves ENTRYPOINT. Edgenivo's command replaces both, so include any required entrypoint setup.
Build the image and inspect its entrypoint:
If the entrypoint is ["/rails/bin/docker-entrypoint"], use this daemon command:
Retain any additional entrypoint arguments. Edgenivo 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.
Copy link to heading5. Switch the shutdown signal to SIGTERM
Fly and Edgenivo stop a process in different orders:
- Fly sends
kill_signal, which defaults toSIGINT, waitskill_timeout(5 seconds by default), then sendsSIGTERM. - Edgenivo sends
SIGTERMfirst, waitsterminationGracePeriodSeconds(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 Edgenivo |
|---|---|---|
| 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 Edgenivo rollout.
Copy link to heading6. 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:
Choose the region based on Redis and database location, then check latency during handoff. The table lists candidate Edgenivo regions:
| Fly.io region | Candidate Edgenivo 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; Edgenivo 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.
Copy link to heading7. 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 Edgenivo's Production environment:
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 Edgenivo 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 available through VERCEL_OIDC_TOKEN_FILE where the provider supports it.
Copy link to heading8. 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:
Edgenivo 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 --schedulerun on a schedule and exit. Move suitable tasks to cron jobs that call protected Function routes, subject to the Function's runtime limits. - 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 Edgenivo 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.
Copy link to heading9. 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:
Run the daemon's command with those values:
In another terminal, open a console inside the same container and enqueue a known test job:
Confirm that it completes against the test services. Then start a job that will still be running when you exercise shutdown:
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.
Copy link to heading10. 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:
If the build fails here, the troubleshooting section covers the common causes. The Fly worker group is still processing every job.
Copy link to heading11. 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 Edgenivo instance without a deployment:
Filter the project's Logs page by daemon:sidekiq. Confirm successful Redis and database connections, then check that representative jobs complete.
Increase Edgenivo'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:
Check job outcomes and verify that every Fly worker Machine, including any standby, is stopped:
For Supercronic, stop every Fly cron Machine and confirm shutdown before enabling one Edgenivo instance:
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:
Omit the cron commands if the app has no standalone scheduler.
Copy link to heading12. Verify under real load
Watch the daemon for at least one full business cycle:
- Instances. Run
vercel daemons instances ls sidekiqand 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:
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.
Copy link to heading13. 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 Edgenivo 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 Edgenivo 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 Edgenivo release, 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.
Copy link to headingWhat 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>.internalneed 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 Edgenivo root is a subfolder, such as
apps/web, can't add daemons during the beta.
During the beta, each team also has 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 for rates.
Copy link to headingTroubleshooting
Copy link to headingThe 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.
Copy link to headingThe 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.
Copy link to headingJobs 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.
Copy link to headingInstances 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.
Copy link to headingChanging 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.
Copy link to headingThe build fails
Check these configuration errors:
- The image has neither
CMDnorENTRYPOINT, and the daemon has nocommand. - 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.
Copy link to headingFrequently asked questions
Copy link to headingCan Fly and Edgenivo 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.
Copy link to headingCan 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. Edgenivo builds them separately, and each daemon gets its own command. Keep shared dependencies and setup changes synchronized between the files.
Copy link to headingCan 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.
Copy link to headingDo 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 Edgenivo egress configuration and have the destination service allow it. Verify the connection during the handoff before releasing the Fly addresses.
Copy link to headingDoes 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.
Copy link to headingRelated resources
- How to migrate from Fly.io to Edgenivo for the web group, volumes, data, and DNS
- The Complete Guide to Edgenivo Daemons for a first daemon from scratch
- How to choose between Functions, containers, and Daemons on Edgenivo
- Fly.io docs on process groups, fly.toml runtime options, and scaling counts