Migrate from Netlify
Last updated: June 24, 2026
Netlify is a fast way to get a static site or a Jamstack front end online. But the moment your traffic grows — bandwidth that creeps past the plan, build minutes you keep topping up, a function that's busier than the free tier expects — the bill starts climbing in ways that are hard to predict, and the platform owns more of your stack than you'd like. This recipe moves your site to a server you own on American Cloud, where the cost is a flat monthly rate and the whole runtime is yours.
You don't have to learn the Linux part. With the American Cloud MCP server connected to your AI assistant, the assistant reads your repo, maps each Netlify feature to its place on the new server, prices the move so you can compare it against your real Netlify bill, provisions a small VM, builds and deploys the site, and walks you through a DNS cutover that keeps Netlify serving traffic until you're confident.
Your Netlify site's build output is a folder of static files. On the new server, nginx serves that folder — that's the landing zone, and it fits comfortably on the smallest VM tier. Object storage isn't where the site lives; it's an optional companion for large media or user uploads the site references.
This playbook is a sibling of Migrate from Vercel — same shape (inventory, plan, provision, map features, cut over), different platform. The underlying provision-and-deploy mechanics come from Deploy a Next.js app with your AI assistant; read that if you want the details of how a VM, nginx, and HTTPS get set up.
Provisioning and DNS changes are write operations. You'll need a
read-write API key from
console.americancloud.com/api-keys
and the --allow-writes flag on the MCP server. See the
overview for setup and safety. The inventory phase
below is fully read-only — do all of it with a read-only key before you
commit to anything.
What this looks like
The migration runs as a guided conversation in six phases. Phases 1 and 2 are read-only — your assistant inspects and plans, nothing is created or billed. Phases 3 through 6 do the move.
- Inventory — the assistant reads your repo and Netlify config and produces a migration map.
- Plan and price — it proposes the smallest VM and a cost estimate to set against your Netlify bill.
- Provision and deploy — it builds the server, builds the site, and points nginx at the output.
- Map the features — redirects, headers, functions, forms, and env vars each get a home.
- Cut over — DNS zone move (if needed) ahead of time, lower TTLs, verify against the new server, switch.
- Decommission — once the new server has proven itself, retire the Netlify site.
Do it in a Claude Code session: that client combines the American Cloud tools with your terminal, so one conversation can read the repo, provision the VM, and run the build and config steps over SSH. Cursor and the other clients work too — you'll just run the SSH steps yourself when prompted.
Phase 1: inventory your site (read-only)
Before moving anything, get an honest picture of what Netlify does for you. Some of it is your app (the build command, the publish directory); some of it is Netlify platform behavior — _redirects, _headers, netlify.toml, functions, forms, environment variables, and possibly your DNS — that needs an explicit home on the new server. Open your project and paste this.
I'm planning to migrate this site off Netlify to a small VM I own on American
Cloud. Don't change or create anything yet — this is a read-only inventory pass.
Read the repo and build me a migration map:
1. Read netlify.toml if it exists. List the build command, the publish
directory, the functions directory, and every [[redirects]], [[headers]],
and [[plugins]] block.
2. Read any _redirects and _headers files. Translate each rule into plain
language so I can confirm intent (path, status code, destination, header).
3. List the Netlify Functions (the functions directory) and any scheduled
functions. For each, note what it does and what it talks to.
4. Note whether the site uses Netlify Forms (a form with a netlify or
data-netlify attribute) and where submissions are expected to go.
5. Read package.json: Node version, build script, and whether the build is
memory-hungry. Confirm the build output is a folder of static files.
6. Make a checklist of the environment variables this site reads at build
time and at runtime (scan for process.env.* and any framework env usage).
List the NAMES only — do NOT ask me to paste secret values into the chat.
7. Tell me whether my domain's DNS is currently served by Netlify (Netlify
DNS / Netlify nameservers) or by another registrar/provider. This decides
how the cutover works.
Output a single migration map: what serves as static files as-is, what needs a
server-side equivalent (functions, forms), what nginx rules to write
(redirects, headers), and what I need to provide (env values, domain list).What your assistant will do
This phase touches your filesystem only — no MCP write tools, no calls that cost anything.
- Reads your config. It opens
netlify.toml,_redirects,_headers, your functions directory, andpackage.jsondirectly from the repo. - Translates the rules. Each
_redirectsline and_headersblock becomes a plain-language statement of intent, so you can confirm it before it's turned into nginx config. These get re-implemented, not copied — Netlify's rule syntax doesn't run on nginx. - Classifies the dynamic bits. Netlify Functions and scheduled functions don't have a static home; the map notes that each becomes a route in a small server-side service (or a cron job) on the same VM. Forms get the same treatment.
- Builds the env-var checklist by scanning for env usage, listing names only. You'll put the values on the server directly, never in chat.
- Checks who serves your DNS. This is the single most important line in the map. If your domain is on Netlify DNS, moving the zone to American Cloud is an early step (Phase 5), not an afterthought.
The output is a plain-language map you can sanity-check before a single resource exists.
Phase 2: plan and price (read-only)
Now turn the map into a concrete plan with a number attached — before creating anything. The MCP server's cost-estimate tools are read-only, so the assistant can price the whole setup and you can lay it next to your Netlify invoice.
Based on the migration map, propose an American Cloud setup and price it.
Don't create anything yet.
- This site's build output is static files served by nginx, so plan the
SMALLEST VM tier — there's no application server to run, just nginx serving a
folder. Recommend a region near my users and a current Ubuntu LTS image.
- If the inventory found Netlify Functions or Forms, note that a small Node
service runs on the same VM alongside nginx. It still fits the smallest tier
for typical traffic — call it out if you think it doesn't.
- If the site references large media or user uploads, plan an object storage
unit for those assets (NOT for the site itself) and price it too.
- List the regions, images, and VM packages so I can see the options.
- Call the cost-estimate tool for the VM and show me hourly and monthly
numbers. Add a public IP if one's needed, and object storage if the map
calls for it.
- Give me a single monthly total I can compare against my Netlify bill.What your assistant will do
- Sizes for static hosting. Because nginx is just serving a folder, the assistant calls
list_regions,list_imagesfiltered to Ubuntu, andlist_vm_packages, then recommends the smallest compute tier. A static site has very little to run. - Accounts for functions and forms. If the map includes a server-side service (for translated functions or a form endpoint), that's still a lightweight Node process on the same box — usually no bigger a VM, and the assistant flags it if the traffic profile suggests otherwise.
- Adds object storage only when it belongs. Large media or uploads the site references can move to an S3-compatible object storage unit, priced with
get_cost_estimate_object_storage. The site's HTML/CSS/JS still live on the VM. - Prices it before building. It calls
get_cost_estimate_vmwith the exact region, package, size, and image (plusget_cost_estimate_public_ip, andget_cost_estimate_object_storageif relevant) and shows the monthly figure. Nothing is billed until you say go.
This is the comparison that matters. American Cloud bills a flat rate for the server you choose — not per GB of bandwidth or per build minute, which are the usual reasons a Netlify bill drifts. Put the assistant's monthly estimate next to your last few Netlify invoices and decide with real numbers.
Phase 3: provision and deploy
With the plan approved, the provision-and-deploy follows the same flow as Deploy a Next.js app with your AI assistant: create a small Ubuntu VM, open the right inbound ports, install nginx over SSH, and turn on HTTPS with Let's Encrypt. The difference for a static Netlify site is the middle: instead of running an app as a service, the assistant runs your build and points nginx at the output folder.
A few migration-specific notes:
- Where the build runs. The assistant can build the site locally and copy the output to the VM, or run the build on the VM itself — whichever the inventory found simpler. The result is the same: a folder of static files in nginx's web root.
- Ports.
create_vmcarriesnetworkAccess.inboundPortsto open 22 (SSH), 80 (HTTP), and 443 (HTTPS) in one step. That argument opens the firewall rule and the port forwarding together — both halves of making a port reachable. (A firewall rule on its own does not route traffic to the VM; if you ever open another port later, you need both acreate_firewall_ruleand acreate_port_forwarding_rule, orenable_static_naton the IP.) - Hold the DNS switch. Don't point your live domain at the new server yet. Deploy and test against the VM's IP first, while Netlify keeps serving your real traffic. The full end-to-end prompt in Phase 5 wires this together.
Phase 4: map the Netlify features
Most of a Netlify migration is translating platform features into things that run on your own server. Here's how each one maps, and which MCP tools or server-side steps your assistant uses.
| Netlify feature | On American Cloud | How |
|---|---|---|
| Build & hosting | Static files served by nginx on the smallest VM | The assistant builds locally or on the VM, drops the output in nginx's web root |
_redirects / _headers / netlify.toml rules | nginx config | The assistant translates each redirect and header rule into nginx directives, then verifies them against the live server after deploy |
| Netlify Functions | Routes in a small Node service on the same VM | A long-running service handles them — often simpler: no cold starts, no per-invocation pricing |
| Scheduled functions | cron or systemd timers | The assistant writes a timer (or crontab line) per schedule over SSH |
| Netlify Forms | A small form endpoint in that same Node service | Submissions land in your own store — your data, your server |
| Environment variables | Server environment / systemd unit | Written into the service's env file over SSH — names in chat, values on the server only |
| Netlify DNS | Hosted DNS zone | create_dns_zone, create_dns_record; update your registrar's nameservers (see Phase 5) |
| Large media / uploads | Object storage | create_object_storage_unit, create_object_storage_bucket, get_object_storage_keys — S3-compatible; see object storage |
A few of these are worth a closer look.
Redirects and headers
Netlify's _redirects, _headers, and the [[redirects]] / [[headers]] blocks in netlify.toml are a rule syntax that Netlify's edge applies for you. On your own server, nginx does the same job with its own directives. Your assistant reads each rule, translates it — a 301/302 redirect becomes an nginx return or rewrite; a security or caching header becomes an add_header; an SPA fallback (/* /index.html 200) becomes a try_files directive — and then, after deploy, walks every rule against the running server to confirm it behaves as it did on Netlify. That verification pass (Phase 5) is what catches a translation that almost-but-not-quite matches.
Functions
A Netlify Function is a small piece of server-side code invoked per request. On the new server, the assistant collects them into one small Node service that runs alongside nginx as a systemd service, with nginx reverse-proxying the function paths (commonly /.netlify/functions/* or whatever paths your site calls) to it. In practice this is usually simpler: one always-warm process, no cold starts, no per-invocation billing. If a function relied on a Netlify-specific runtime helper, the assistant flags it in Phase 1 so you can swap it for the standard Node equivalent.
Scheduled functions
A scheduled function is just a function on a cron expression. Your assistant recreates each schedule as a systemd timer (or a crontab line) on the VM that hits the same route on the same cadence — over SSH, so it survives reboots.
Forms
Netlify Forms captures form submissions for you. On your own server, the assistant adds a tiny form endpoint to the same Node service: the form posts to it, and the endpoint writes each submission somewhere you control — a file, a small local store, or an email/notification of your choosing. The positive part of this trade: your submission data lands in your own store on your own server, not a third party's dashboard.
Environment variables
Build-time and runtime env vars move into the service's environment file (or its systemd unit) on the VM. Your assistant lists the names it found in Phase 1; you supply the values directly on the server, never pasted into chat.
Continuous deploys
On Netlify, a git push triggers a build and deploy. On a server you own, you replace that with a one-prompt deploy script — the same pattern as the deploy-nextjs follow-up: ask your assistant to write a script that pushes the latest committed code to the VM, rebuilds the site, refreshes the nginx web root (and restarts the function service if you have one). After that, shipping a change is "run the deploy script" — or just "deploy the latest."
Set up a deploy script in this repo I can run to ship updates to the server. It
should: push the latest committed code to the VM over SSH, run the build there,
replace the files in the nginx web root with the new build output, reload
nginx, and restart the function service if there is one. Add a "deploy" entry to
package.json scripts and document the one command I run from now on.Deploy previews
Netlify's deploy previews — a fresh URL per branch — don't have a one-to-one equivalent, and that's an honest difference. The workflow becomes "deploy from a branch when I ask." If you want a standing preview environment, your assistant can stand up a second, cheap VM for a staging copy of the site on request, deploy a branch to it, and tear it down when you're done — you only pay for it while it's up.
Phase 5: cut over without downtime
This is the part unique to migrating a live site. The goal: bring the new server fully online, prove it works, and only then move traffic — with Netlify still up as your safety net.
If your domain is on Netlify DNS, move the zone first
Check the Phase 1 finding. If your domain's DNS is served by Netlify DNS (your registrar points at Netlify's nameservers), the zone move is a required early step, because the place you change records is currently Netlify. Do this ahead of the cutover, not during it:
- The assistant creates the zone on American Cloud with
create_dns_zoneand recreates your existing records (create_dns_record) — keeping them pointed where they point today, including the record(s) that still send web traffic to Netlify. - You then change your nameservers at the registrar to American Cloud's. That nameserver change has its own propagation window (it can take a while, since registrar/TLD nameserver TTLs are long), so doing it early — while records still resolve to Netlify — means no visitor sees a gap.
- Once the zone is authoritative on American Cloud, the actual cutover is a single record edit with a short TTL, fully under your control.
If your domain is not on Netlify DNS (it's at another registrar/provider), you don't have to move the zone at all — you can simply edit the record at your current provider during the cutover. Either way, the TTL rule below applies.
Lower your DNS TTL ahead of time
TTL is how long resolvers cache a record. A high TTL means a cutover takes hours to take effect everywhere — and a rollback would too. Lower the TTL on the record you'll switch — at whichever host currently serves the domain — a day or two ahead (60 seconds is fine). If the domain is still on Netlify DNS at this point, that's where you lower it; if you've already moved the zone to American Cloud, new records there get short TTLs automatically and the assistant can confirm. Then, when you flip the record, traffic moves fast and a rollback is just as quick.
Test against the new server before you touch DNS. Your assistant can hit
the VM's public IP directly with the right Host header — e.g.
curl -H "Host: your-domain.com" https://VM_IP/ --resolve your-domain.com:443:VM_IP
— so it sees exactly what visitors will see while real DNS still points at
Netlify. Or it can add a temporary line to your local hosts file mapping
your domain to the VM's IP so you can click through the whole site in a
browser. Ask: "verify the migrated site against the VM's IP with my domain's
Host header — walk every redirect rule, every form, and every function the
inventory found, and show me the results."
Here's the prompt that runs the migration end to end and lands on a careful cutover:
Execute the migration to American Cloud using the plan and prices we agreed on.
Narrate each step and pause before anything destructive or anything that moves
real traffic.
1. Provision the VM: small Ubuntu VM, my SSH key, inbound ports 22/80/443 open
(networkAccess.inboundPorts, which opens firewall + forwarding together).
Wait until it's STARTED with a public IP.
2. Install nginx. Build the site (locally or on the VM, whichever we decided),
and put the output in the nginx web root.
3. Translate every _redirects / _headers / netlify.toml rule into nginx config
and load it.
4. If the inventory found Functions/Forms, set up the small Node service as a
systemd service, reverse-proxied behind nginx, and recreate any scheduled
functions as systemd timers. Put my env-var VALUES in the service's env
file directly (I'll give them to you — not in chat).
5. If the site references large media/uploads, set up object storage and point
the site at it as we discussed.
6. Provision a Let's Encrypt certificate so the VM serves HTTPS for my domain,
even though DNS doesn't point here yet (use the DNS-01 path or a temporary
verification as needed).
7. Before any DNS change: verify the site against the VM's IP using my domain's
Host header. Walk EVERY redirect rule, every header, every form, and every
function from the inventory. Show me the results.
8. When I confirm it's good: make sure the TTL on the record I'm switching is
already low, then point the A record for my domain at the VM's public IP.
9. Watch resolution until my domain points at the VM, then confirm the live
site loads over HTTPS. Leave my Netlify site running untouched.What your assistant will do
- Builds and deploys as in the deploy recipe:
create_vmwithnetworkAccess.inboundPortsto open 22/80/443 andkeypairsfor your SSH key, pollingget_vmuntilSTARTED, then nginx and your built site over SSH. - Re-implements your rules. Translated redirects and headers go into the nginx config; functions become a
systemd-managed Node service behind nginx; scheduled functions become timers; env values go into the service's env file. - Gets HTTPS ready early so the new server can serve your domain over TLS before any traffic arrives.
- Verifies against the IP. Using a
Host-headercurlor a temporaryhostsentry, it walks every redirect, header, form, and function the inventory found — all while Netlify still serves your users. - Cuts over deliberately. Only on your confirmation does it switch the
Arecord (update_dns_recordon an American Cloud zone, or it tells you the change to make at your DNS provider) to the VM's public IP, then watches resolution until your domain points at the new server. - Leaves Netlify running. Because the TTL is low, rollback is fast: if anything looks wrong, point the record back at Netlify and you're restored in seconds.
Phase 6: decommission Netlify — when you're sure
Give it a day or two. Watch the new server's traffic and error logs, confirm every redirect still behaves, check that forms are landing where they should and any scheduled jobs are firing. When you're confident the new server carries everything Netlify used to, then — and only then — retire the Netlify site. There's no rush: keeping it up a little longer costs little and buys you a clean rollback the whole time.
The migrated site has been healthy for a couple of days. Confirm the VM is
serving traffic, the redirects and headers all behave, forms are landing in my
store, and the scheduled-job timers have run on schedule. Then give me a
checklist for safely removing the Netlify site (and, if I moved my domain off
Netlify DNS, confirming nothing still depends on it).Next steps
- Deploy a Next.js app with your AI assistant — the full provision-and-deploy mechanics this playbook builds on
- Migrate from Vercel — the sibling playbook for a Next.js deployment on Vercel
- Object storage with your AI assistant — a home for large media and uploads your site references
- Backups with your AI assistant — snapshots and object-storage backups for your new server
- Write an AGENTS.md for your project — capture your deploy conventions so future sessions repeat them