Vercel → Fly.io
Move a Vercel app onto Fly Machines. You bring a Docker image, fly secrets, and DNS for your own certificate. The frontend can stay on Vercel.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Vercel
- Exceptional frontend previews and framework integration make releases fast and polished
- Managed deployments reduce infrastructure and release-management work
- Costs and proprietary platform features can grow rapidly with traffic and complexity
- Runtime assumptions, platform services, and pricing models shape the architecture
Fly.io
- Applications and databases can run close to users with container-level control
- Managed deployments reduce infrastructure and release-management work
- Networking, capacity, and operational debugging require more infrastructure skill
- Runtime assumptions, platform services, and pricing models shape the architecture
Fly.io: Applications and databases can run close to users with container-level control. This removes a major source-side concern: Costs and proprietary platform features can grow rapidly with traffic and complexity.
What you lose: Exceptional frontend previews and framework integration make releases fast and polished. What you inherit: Networking, capacity, and operational debugging require more infrastructure skill.
Jump to a section
Know the shape of the move.
This timeline assumes
- You are moving a service that needs a running process. The site already documents Vercel → Railway, Vercel → Render, and Heroku → Fly.io.
- A Docker build is acceptable. OpenStatus cut a monorepo image from over 2 GB to 700 MB and still owned that Dockerfile.
- Vercel Secret values are write-only after saving, so production secrets are rotated into fly secrets.
- The Vercel project stays up until the Fly certificate is issued and you have requested the public host.
- A frontend can remain on Vercel. OpenStatus kept theirs there and moved the API.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| The git commit | clean | critical | Fly deploys an image built from your repo. It does not import a Vercel deployment. | Record the SHA and deploy that SHA. |
| Serverless entrypoint | manual | critical | Vercel invokes functions. Fly runs Machines from your image. OpenStatus moved an API to Hono on Bun and had to produce a Docker image Vercel had not required. | Run the image locally and confirm it listens before fly deploy. |
| Readable config | partial | high | Vercel Config values stay readable. fly secrets set stores a value in an encrypted vault and injects it as an environment variable when a Machine boots. fly secrets list shows the name and a digest, not the value. | Set secrets from a file you still have. You will not be able to read them back from Fly. |
| Write-only Vercel secrets | manual | critical | Vercel Secret values cannot be copied out after save. Fly also refuses to show secret values later. | Rotate into fly secrets set, then start the Machines. |
| Build-time variables | partial | high | Vercel exposes variables to the Build Step and to functions. A change applies only to a new deployment. Fly says runtime secrets are not available while the Docker image builds. Build secrets are a separate path. | Keep NEXT_PUBLIC_ and other baked values in the build, and keep credentials in fly secrets. |
| Secret updates | partial | high | fly secrets set restarts each Machine and resets its ephemeral filesystem. fly secrets set --stage waits until a later start, update, or fly secrets deploy. fly secrets deploy redeploys the current release without rebuilding the image. | Do not store uploads on the Machine disk. Stage secrets if you are not ready to restart. |
| Release command | manual | high | A fly.toml release_command runs in a temporary Machine before the release. It replaces CMD, not ENTRYPOINT, has no volume, and a non-zero exit stops the deploy. The default timeout is 5 minutes. Change it with release_command_timeout or fly deploy --release-command-timeout. | Put migrations in that command only if they finish inside the timeout you set. |
| Deploy wait | partial | medium | fly machine update waits 300 seconds by default for state changes and health checks. OpenStatus hit deploy timeouts and raised the wait with flyctl deploy --wait-timeout=500. They also wrote that they had not found a quick rollback to the previous version. | Watch the first deploys. Keep the previous image so you can deploy it again. |
| Custom domain certificate | manual | critical | fly certs add example.com prints the DNS records. Fly can issue a certificate only if it can verify the domain with an AAAA record, an _acme-challenge CNAME, or a _fly-ownership TXT record. Apex names should use A and AAAA. CNAME fits a subdomain such as www. Let's Encrypt limits include 50 certificates per registered domain per week, 5 duplicate certificates per week, and 5 failed validations per hostname per hour. Renewals do not count against the 50. | Add the records fly certs add prints, then run fly certs check. Use a staging name while you learn the records. |
| Cloudflare in front | manual | high | Fly says an orange-cloud proxy should use Full or Full (Strict). Flexible can loop. Add the _fly-ownership TXT record when the proxy blocks direct validation. If HTTP-01 still fails, import a Cloudflare origin certificate with fly certs import. | Check the SSL mode before you blame the app. |
| Preview URLs and old logs | lost | medium | Vercel preview URLs and the old deployment list stay on Vercel. A new Fly app gets a fly.dev name from the app name. | Smoke-test the fly.dev host before the custom domain. |
| Anything you left on Vercel | partial | high | OpenStatus moved the API and kept the frontend on Vercel. That split is a working setup, and it is also a second system to roll back. | Write down which host is Fly and which host is still Vercel. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Production SHA | Fly release | manual | Build an image from that commit. |
| Serverless function | Machine process | manual | The image has to listen. OpenStatus used Docker for a Hono API. |
| Config variable | fly secret or build arg | transform | Runtime secrets are not in the Docker build. |
| Secret variable | fly secrets set | manual | Rotate. Neither side will show the value later. |
| Preview variable | Another Fly app, or nowhere | manual | Do not mix it into the production app. |
| Release migration | release_command | manual | 5 minute default. Non-zero exit stops the deploy. No volume on that Machine. |
| Custom domain | fly certs add | manual | AAAA, _acme-challenge, or _fly-ownership TXT. |
| www name | CNAME to the app's fly.dev target | transform | Fly recommends A and AAAA on the apex. |
| Cloudflare proxy | Full or Full (Strict), plus ownership TXT | manual | Flexible mode can redirect-loop. |
| Vercel preview URL | No destination | unsupported | Stays on Vercel. Use the fly.dev name for the pilot. |
Make the move recoverable.
Create the source-of-truth backup
Keep Vercel, the SHA, and the secret names before Machines restart.
- Record the production SHA and the current DNS.
- Run vercel env pull for the values you can still read.
- List Secret names that must be rotated.
- Note whether the frontend stays on Vercel.
- Leave the Vercel deployment in place.
Proof to capture: A SHA, a DNS note, and a secret list that does not contain values you are not allowed to store.
Image and secrets
Boot the process on Fly without copying write-only Vercel secrets by eye.
- Build an image from the recorded SHA. OpenStatus's first monorepo image was over 2 GB.
- Put credentials in fly secrets set. Use --stage if you do not want an immediate restart.
- Put a migration in release_command only after you time it. The default kill is 5 minutes.
- Deploy and open the fly.dev hostname.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Certificate
Serve your domain on Fly without burning Let's Encrypt's failed-validation limit.
- Run fly certs add for the apex and fly certs check before you announce it.
- Allocate IPs with fly ips allocate if the app has no A or AAAA yet.
- For www, add the CNAME target fly certs setup prints.
- Behind Cloudflare, set Full or Full (Strict) and the _fly-ownership TXT record.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
A secret is only in the Docker build
The image built, and the Machine has no DATABASE_URL because it was not a fly secret.
- Consequence
- The process boots and fails on the first real request.
- Mitigation
- Separate build args from fly secrets set.
Stop if: A production credential was never set with fly secrets.
Certificate validation fails five times
fly certs check keeps failing on a half-edited DNS record.
- Consequence
- Let's Encrypt allows 5 failed validations per hostname per hour, and Fly cannot reset that.
- Mitigation
- Fix DNS, then check. Practice on a staging name.
Stop if: You are about to retry the same broken record again.
Deploy wait hides a bad release
fly deploy sits until the wait timeout. OpenStatus raised it to 500 seconds and still had no one-click rollback.
- Consequence
- You do not know whether the new Machines are healthy, and the previous image is the way back.
- Mitigation
- Keep the previous image and a health check.
Stop if: You cannot name the image you would deploy to undo this release.
Do the work in this order.
- Days 1 to 2 · inventory
Split the hosts
4 to 8 hours active1 to 2 days elapsedSecret owners waiting- Decide what stays on Vercel.
- List secrets you must rotate.
Depends on: Vercel access
Stop / go checkpointBuild an image?
Go when: Each hostname has an owner, Fly or Vercel.
Stop when: The API and the frontend are still one unnamed deploy.
- Days 2 to 3 · backup
Save Vercel
2 to 4 hours active1 day elapsedNone waiting- Record the SHA and DNS.
- Pull readable env values.
Depends on: Inventory
Stop / go checkpointDeploy to Fly?
Go when: You can restore the current DNS from notes.
Stop when: The public records are unknown.
- Days 3 to 9 · pilot
Image on fly.dev
10 to 24 hours active4 to 7 days elapsedImage builds and Machine restarts waiting- Shrink the image enough to deploy.
- Set secrets and a release command if you need one.
- Open the fly.dev URL.
Depends on: Dockerfile
Stop / go checkpointAdd a certificate?
Go when: The fly.dev host serves the SHA.
Stop when: The process only runs as a Vercel function.
- Days 9 to 12 · cutover
Certificate and DNS
3 to 8 hours active1 to 3 days elapsedLet's Encrypt waiting- Run fly certs add and fly certs check.
- Point apex and www using the records Fly prints.
- Request the public API.
Depends on: Healthy fly.dev host
Stop / go checkpointSend traffic?
Go when: fly certs check is clean and one authenticated call works.
Stop when: Validation has failed repeatedly on the same record.
- Days 12 to 18 · observe
Keep the old image
2 to 6 hours active3 to 6 days elapsedReal traffic waiting- Watch deploy waits.
- Leave Vercel up for the part you did not move.
- Confirm secret changes were not staged and forgotten.
Depends on: Public host on Fly
Stop / go checkpointStop the Vercel copy of this service?
Go when: You can redeploy the previous image.
Stop when: Rollback is a hope rather than an image you can name.
Cut over with a way back.
Cutover
Serve the chosen host from Fly while Vercel still has the previous deployment.
- Deploy the SHA and confirm fly.dev.
- Set production secrets and restart, or fly secrets deploy if they were staged.
- Add the certificate and the DNS records Fly prints.
- Request the public host over HTTPS.
- Leave the Vercel deployment in place.
Proof to capture: The public host hits Fly, and Vercel still has the previous deployment.
Rollback
Put the hostname back on Vercel, or redeploy the previous Fly image.
- Restore the saved DNS if the domain should leave Fly.
- Or deploy the previous image if the domain should stay.
- Confirm which host is live.
- Unset any secret that the bad release required, knowing the filesystem was reset on restart.
- Write down the failed check.
Proof to capture: Clients hit the host you chose as the fallback.
- fly certs check fails
- A secret is missing at runtime
- The release command hits the 5 minute kill
- Deploy wait times out and health checks do not pass
- The frontend and API disagree about which host moved
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | SHA | Match the deployed image to the recorded commit. | They match. | Release log. | |
V-02Blocking | fly.dev | Request the app hostname. | The process answers. | HTTP check. | |
V-03Blocking | Names only | Run fly secrets list and compare names with the production list. | Each required name is present. Values are not shown, which is expected. | fly secrets list. | |
V-04Blocking | No runtime secret in the image | Confirm credentials were set with fly secrets, not only as build args. | The Machine has them after boot. | Boot log. | |
V-05Blocking | release_command | Read the deploy log if you set one. | It exits 0 inside the timeout. | Deploy log. | |
V-06Blocking | fly certs check | Run it for the apex and for www. | Validation succeeded. | certs check output. | |
V-07 | Cloudflare mode | If you proxy, confirm Full or Full (Strict) and the ownership TXT. | No redirect loop. | DNS notes. | |
V-08Blocking | Previous image or Vercel | Name the image or the Vercel deployment you would return to. | It still exists. | Rollback note. |
Remove the scaffolding safely.
Safe after: A few days of healthy deploys, with the certificate issued and the previous image still deployable.
- Store the SHA, DNS notes, and secret names.
- Remove staged secrets you never deployed, or deploy them on purpose.
- Drop the Vercel copy of this service only after the split is documented.
- Keep a staging hostname so the next certificate test is not the production name.
- Write down the release_command timeout you actually needed.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
OpenStatus moved its API from Vercel to Fly.io and left the frontend on Vercel. The API became Hono on Bun, which meant owning a Docker image. The first monorepo image was over 2 GB and they cut it to 700 MB. Their sketch put multi-region monitor checks at about $150 a month on Vercel, against $23.34 for six Fly machines at $3.89 each. Deploys timed out until they used flyctl deploy --wait-timeout=500, and they wrote that they had not found a quick rollback. Request failures after the move were a Bun keepalive bug, fixed by setting keepalive to false.
- Plan for a Docker image before you leave Vercel. Their first monorepo image was over 2 GB.
- Keep the previous image. Raising the deploy wait did not give them a one-step rollback.
- The cutover was a split. The API went to Fly.io and the frontend stayed on Vercel.
- A runtime bug showed up only after the move, and the fix was a Bun keepalive setting.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Vercel: environment variablesAccessed 2026-11-02
- Fly.io: secretsAccessed 2026-11-02
- Fly.io: custom domainsAccessed 2026-11-02
- Fly.io: app configurationAccessed 2026-11-02
- OpenStatus: moving a backend from Vercel to Fly.ioAccessed 2026-11-02