← All playbooks
Hosting migration

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.

Typical timeline5 to 15 business days20 to 50 hours active work
Statusneeds review
Source profileVercel environment variable docs reviewed 2026-11-02
Destination profileFly.io secrets, certificates, and fly.toml docs reviewed 2026-11-02
Documentation date
Review window
Evidence5 sources
This route needs review.Use it as a planning baseline, then verify the dated sources and test with representative data before production.
Before you migrate

Should you make this move?

Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.

Current platform

Vercel

Reasons to stay
  • Exceptional frontend previews and framework integration make releases fast and polished
  • Managed deployments reduce infrastructure and release-management work
Reasons to leave
  • Costs and proprietary platform features can grow rapidly with traffic and complexity
  • Runtime assumptions, platform services, and pricing models shape the architecture
New platform

Fly.io

What gets better
  • Applications and databases can run close to users with container-level control
  • Managed deployments reduce infrastructure and release-management work
What gets worse
  • Networking, capacity, and operational debugging require more infrastructure skill
  • Runtime assumptions, platform services, and pricing models shape the architecture
Best of the move

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.

Worst of the move

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
01At a glance

Know the shape of the move.

Transfer outcome12 features audited
Transfer outcome distributionClean transfer: 1, Partial transfer: 5, Manual rebuild: 5, Not transferred: 1.
Clean1
Partial5
Manual5
Lost1
Mapping route9 of 10 fields have a destination path

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.
02What transfers

What survives the move.

“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.

ItemOutcomeImpactWhat happensMitigation
The git commitcleancriticalFly deploys an image built from your repo. It does not import a Vercel deployment.Record the SHA and deploy that SHA.
Serverless entrypointmanualcriticalVercel 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 configpartialhighVercel 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 secretsmanualcriticalVercel 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 variablespartialhighVercel 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 updatespartialhighfly 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 commandmanualhighA 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 waitpartialmediumfly 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 certificatemanualcriticalfly 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 frontmanualhighFly 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 logslostmediumVercel 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 VercelpartialhighOpenStatus 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.
03Field and feature mapping

Where each thing goes.

SourceDestinationMethodNotes
Production SHAFly releasemanualBuild an image from that commit.
Serverless functionMachine processmanualThe image has to listen. OpenStatus used Docker for a Hono API.
Config variablefly secret or build argtransformRuntime secrets are not in the Docker build.
Secret variablefly secrets setmanualRotate. Neither side will show the value later.
Preview variableAnother Fly app, or nowheremanualDo not mix it into the production app.
Release migrationrelease_commandmanual5 minute default. Non-zero exit stops the deploy. No volume on that Machine.
Custom domainfly certs addmanualAAAA, _acme-challenge, or _fly-ownership TXT.
www nameCNAME to the app's fly.dev targettransformFly recommends A and AAAA on the apex.
Cloudflare proxyFull or Full (Strict), plus ownership TXTmanualFlexible mode can redirect-loop.
Vercel preview URLNo destinationunsupportedStays on Vercel. Use the fly.dev name for the pilot.
04Before you begin

Make the move recoverable.

Backup procedure

Create the source-of-truth backup

Keep Vercel, the SHA, and the secret names before Machines restart.

  1. Record the production SHA and the current DNS.
  2. Run vercel env pull for the values you can still read.
  3. List Secret names that must be rotated.
  4. Note whether the frontend stays on Vercel.
  5. 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.

Transformation · Dockerfile and fly secrets

Image and secrets

Boot the process on Fly without copying write-only Vercel secrets by eye.

  1. Build an image from the recorded SHA. OpenStatus's first monorepo image was over 2 GB.
  2. Put credentials in fly secrets set. Use --stage if you do not want an immediate restart.
  3. Put a migration in release_command only after you time it. The default kill is 5 minutes.
  4. Deploy and open the fly.dev hostname.

Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.

Transformation · fly certs

Certificate

Serve your domain on Fly without burning Let's Encrypt's failed-validation limit.

  1. Run fly certs add for the apex and fly certs check before you announce it.
  2. Allocate IPs with fly ips allocate if the app has no A or AAAA yet.
  3. For www, add the CNAME target fly certs setup prints.
  4. 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.

05Handle with care

The things most likely to hurt.

These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.

Pilot

A secret is only in the Docker build

criticalpossible likelihood

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.

Cutover

Certificate validation fails five times

highpossible likelihood

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.

Cutover

Deploy wait hides a bad release

highpossible likelihood

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.

06Precise timeline

Do the work in this order.

Estimate forOne API moving to Fly, with the option to leave a frontend on Vercel
Total elapsed5 to 15 business days
Active work20 to 50 hours
BufferAdd time for a first Docker build of a monorepo, which dominated OpenStatus's move.
01
Days 1 to 2Split the hosts1 to 2 days
02
Days 2 to 3Save Vercel1 day
03
Days 3 to 9Image on fly.dev4 to 7 days
04
Days 9 to 12Certificate and DNS1 to 3 days
05
Days 12 to 18Keep the old image3 to 6 days
  1. 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 checkpoint

    Build an image?

    Go when: Each hostname has an owner, Fly or Vercel.

    Stop when: The API and the frontend are still one unnamed deploy.

  2. 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 checkpoint

    Deploy to Fly?

    Go when: You can restore the current DNS from notes.

    Stop when: The public records are unknown.

  3. 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 checkpoint

    Add a certificate?

    Go when: The fly.dev host serves the SHA.

    Stop when: The process only runs as a Vercel function.

  4. 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 checkpoint

    Send traffic?

    Go when: fly certs check is clean and one authenticated call works.

    Stop when: Validation has failed repeatedly on the same record.

  5. 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 checkpoint

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

07The point of change

Cut over with a way back.

Go live

Cutover

Serve the chosen host from Fly while Vercel still has the previous deployment.

Recommended window: A low-traffic hour with DNS access and the previous image identified.

  1. Deploy the SHA and confirm fly.dev.
  2. Set production secrets and restart, or fly secrets deploy if they were staged.
  3. Add the certificate and the DNS records Fly prints.
  4. Request the public host over HTTPS.
  5. Leave the Vercel deployment in place.

Proof to capture: The public host hits Fly, and Vercel still has the previous deployment.

Return to safety

Rollback

Put the hostname back on Vercel, or redeploy the previous Fly image.

Deadline: Before you delete the Vercel project or the previous image.

  1. Restore the saved DNS if the domain should leave Fly.
  2. Or deploy the previous image if the domain should stay.
  3. Confirm which host is live.
  4. Unset any secret that the bad release required, knowing the filesystem was reset on restart.
  5. Write down the failed check.

Proof to capture: Clients hit the host you chose as the fallback.

Rollback immediately when
  • 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
08Verification report

Prove the migration worked.

Every blocking check must pass. Capture the evidence before cleanup begins.

0%
Interactive report preview0 / 8 checks passed
PassIDCheckMethodExpected resultEvidence
V-01BlockingSHAMatch the deployed image to the recorded commit.They match.Release log.
V-02Blockingfly.devRequest the app hostname.The process answers.HTTP check.
V-03BlockingNames onlyRun 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-04BlockingNo runtime secret in the imageConfirm credentials were set with fly secrets, not only as build args.The Machine has them after boot.Boot log.
V-05Blockingrelease_commandRead the deploy log if you set one.It exits 0 inside the timeout.Deploy log.
V-06Blockingfly certs checkRun it for the apex and for www.Validation succeeded.certs check output.
V-07Cloudflare modeIf you proxy, confirm Full or Full (Strict) and the ownership TXT.No redirect loop.DNS notes.
V-08BlockingPrevious image or VercelName the image or the Vercel deployment you would return to.It still exists.Rollback note.
09Post-migration cleanup

Remove the scaffolding safely.

Safe after: A few days of healthy deploys, with the certificate issued and the previous image still deployable.

  1. Store the SHA, DNS notes, and secret names.
  2. Remove staged secrets you never deployed, or deploy them on purpose.
  3. Drop the Vercel copy of this service only after the split is documented.
  4. Keep a staging hostname so the next certificate test is not the production name.
  5. Write down the release_command timeout you actually needed.
10Experiences from the field

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.

First-person accountOpenStatus

Why we migrated our backend from Vercel to Fly.ioUndated

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.

What they recommend
  • 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.
Worth noticing
  • 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.
Sources and evidence

Verify against the primary material.

Platform behavior changes. Check these sources and the review dates above before executing a production migration.

  1. Vercel: environment variablesAccessed 2026-11-02
  2. Fly.io: secretsAccessed 2026-11-02
  3. Fly.io: custom domainsAccessed 2026-11-02
  4. Fly.io: app configurationAccessed 2026-11-02
  5. OpenStatus: moving a backend from Vercel to Fly.ioAccessed 2026-11-02