A runbook for moving a Netlify site to Levelrail: build settings, netlify.toml mapping, redirects and headers, deploy contexts, forms, build hooks, a DNS cutover and the features with no equivalent.
This is a runbook for moving one Netlify site onto a Levelrail instance you run yourself. It covers only features that exist in this repository today. Where something has no equivalent, it says so. There is no Netlify importer, so every step is manual.
Some areas are still beta (see feature status), including Forms. Keep the Netlify site live as your rollback path until the new one has run clean for a while.
levelrail-cli apps create --name my-site --file app.yaml --service web \ --repo https://github.com/you/site.git --ref main
To change the build later, use the app’s Source tab, or:
levelrail-cli apps git-source set my-site --build-type static --build-command "pnpm build" --publish-dir buildlevelrail-cli apps git-source set my-site --build-type static --infer-build
--infer-build detects the framework and fills whichever of the command and publish directory you leave out.
The build runs in a Node based container. It installs dependencies with the package manager that matches your lockfile, then runs your command. For Hugo, Jekyll or another generator, set APP_STATIC_BUILD_IMAGE to an image that has the tool and a shell, or build elsewhere and serve the committed output. The output is capped at APP_STATIC_BUILD_MAX_MB (default 2048). See Static pages.Leave domains: out of the first deploy so nothing competes with Netlify for the real hostname. Check the result with levelrail-cli static-sites list.
Copy _redirects and _headers into the published directory unchanged. They are compiled at deploy time and removed from the served files. Single page app fallback, clean URLs, trailing slash and the 404 page are set under static: in app.yaml:
A rule that is not forced is skipped when a file exists at that path. Add ! (or force: true) where you relied on a redirect winning over a file.
Country=, Language=, Role= and Cookie= conditions are skipped and reported. These need an edge runtime.
A 200 rewrite must point at a path on the same site. Proxying to another origin is not available in static routing. A path forwarder on the domain can send a path to an external URL or another app.
Limits apply: 1000 rules, 100 header blocks and 256 KiB per file by default. See Static routing.
Static build env goes in build.args. For different values on previews and branches, set a context:
levelrail-cli apps contexts set my-site --context deploy-preview --env API_URL=https://staging.example.comlevelrail-cli apps contexts set my-site --context "branch:release/*" --build-command "npm run build:rc" --publish distlevelrail-cli apps contexts show my-site --context branch-deploy --branch release/1.2
Context env is stored as plain values. Put secrets in branch env overrides (apps branch-env set ... --secret) or app secrets. Production deploys use the app’s own settings only. See Deploy contexts.
Each branch gets a stable URL and rebuilds on every push. Removal happens when the branch is deleted or after APP_BRANCH_DEPLOY_TTL (default 336h). At most 10 live branch deploys per app by default.
Pull request previews are a separate feature, enabled per app with levelrail-cli apps previews enable my-site. They need a pull request and a primary domain on the control plane. See Preview environments. The docs do not say whether pull request previews cover static sites specifically, so try one on a test pull request before relying on it. Branch deploys are documented for static sites.
Netlify finds forms in your HTML. Levelrail does not. Create each form and paste its snippet:
levelrail-cli forms create my-site contact --redirect-url https://example.com/thankslevelrail-cli forms snippet my-site contact
The endpoint takes URL encoded, multipart and JSON posts. The bot-field input is the default spam trap, so a honeypot named bot-field can stay. Submissions have an inbox, search, CSV export and notifications to your existing alert channels. File uploads are not stored, there is no CAPTCHA check, and no confirmation goes back to the visitor. Forms are beta. Export old submissions from Netlify yourself, since there is no importer. See Forms.
levelrail-cli apps build-hooks create my-site --name "cms publish" --branch main
The URL is shown once. Point your CMS webhook at it. A POST answers 202 and builds the latest commit on the branch. Triggers are limited to 6 per hook per minute by default. See Build hooks.
A Netlify scheduled function runs on a clock. Here the clock calls a path on a container app, and wakes it if it is asleep. A static site has no server to call, so move the handler to a Functions app or another container app, then:
levelrail-cli jobs create web digest --mode http --http-path /cron/digest --schedule "@hourly" --retries 3
Add --background for work that outlives a request. Http jobs cannot be declared in app.yaml yet, and the call carries no custom headers. See Scheduled functions.
Netlify’s lock publishing maps to the publish lock. Builds still run, then wait as held builds until you publish them. Manual deploys and rollbacks still go live, as do safety rollbacks. Use the lock control on the app page, levelrail-cli apps lock <app> and levelrail-cli apps unlock <app>, or PUT and DELETE on /api/v1/apps/{name}/publish-lock. Publish a held build with levelrail-cli apps publish <app> <deploy-id>. See Publish lock.
Not available for static sites yet. Split testing works for container apps with a domain, between a branch deploy or a retained release and production. If you used Netlify split tests on a static site, there is no replacement today. See Split testing.
5. Where Netlify is ahead, and what has no equivalent
Netlify feature
On Levelrail
Netlify Functions
Functions are container apps that sleep when idle, with a cold start of a container start. Rewrite the handler as a small HTTP server. Not an edge runtime
Background functions
An http job with --background, which the control plane waits on for up to 15 minutes by default
Edge Functions
Not offered. A stated non-goal. Logic that depends on the edge runtime has to move into your app or a CDN
Identity
No equivalent. Use a separate auth service or build login into an app. See Integrations
Blobs
No equivalent. Managed Postgres and Redis exist, and S3-compatible storage is used for backups only
Image CDN
Not offered. Pre-generate sizes at build time
Analytics
No browser-side analytics. Server-side request rate, errors and latency percentiles per app are built in
Global CDN and edge network
Not provided. Files are served from the node or nodes you run
Lower TTLs first. At least one old TTL period before the cutover, set the TTL on the relevant records to 60 to 300 seconds.
If the domain uses Netlify DNS, recreate the zone at a DNS host you control before you move anything, and copy every record, not only the site’s.
Add the domains: levelrail-cli domains add my-site example.com www.example.com. Set the redirect between them with levelrail-cli domains redirect set my-site www.example.com --target https://example.com.
Get a certificate ready. HTTPS is self-signed until Let’s Encrypt is enabled, and issuance needs the domain to already point at the server. See Domains and ingress.
Test before DNS changes: curl --resolve example.com:443:<server-ip> https://example.com/ (add -k while the certificate is self-signed).
Cut over. Point the A and AAAA records at the Levelrail server. Keep the site on Netlify untouched.
Watch dig +short example.com from outside your network and the app’s request metrics.
To roll back, restore the old records. Write the old values down first. A bad release on Levelrail is a different case: levelrail-cli apps deploys rollback-to my-site DEPLOY_ID, or the Rollback button on the deploy.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Levelrail","description":"Self-hosted deployment platform. Push to git, get a running app with TLS, logs, metrics and rollback. One Go binary, no Kubernetes.","colors":{"primary":"#107292","light":"#2FB3DC","dark":"#084F67"},"logo":{"light":"https://mintcdn.com/levelrail/56HjW70mOjUmjIrm/assets/brand/wordmark-light.svg?fit=max\u0026auto=format\u0026n=56HjW70mOjUmjIrm\u0026q=85\u0026s=cbe02b3c3d17b11f9cc5a18551a30782","dark":"https://mintcdn.com/levelrail/56HjW70mOjUmjIrm/assets/brand/wordmark-dark.svg?fit=max\u0026auto=format\u0026n=56HjW70mOjUmjIrm\u0026q=85\u0026s=a4aa78b496c43e0a5edd9dffce778a45","href":"https://levelrail.com"},"favicon":"https://mintcdn.com/levelrail/zPhEnShTGkGFCjPc/public/favicon.svg?fit=max\u0026auto=format\u0026n=zPhEnShTGkGFCjPc\u0026q=85\u0026s=65f3143dd7539d4b1d34090204f05be0","appearance":{"default":"dark","strict":false},"background":{"color":{"light":"#ffffff","dark":"#000000"}},"navbar":{"links":[{"icon":"globe","href":"https://levelrail.com","label":"Website"},{"icon":"clock-rotate-left","href":"/changelog","label":"Changelog"},{"icon":"github","href":"https://github.com/glincker/levelrail","label":"GitHub"}],"primary":{"type":"button","label":"Get started","href":"/getting-started"}},"footer":{"socials":{"github":"https://github.com/glincker/levelrail","x":"https://x.com/thegdsks","website":"https://levelrail.com"}},"search":{"prompt":"Search docs, CLI, API, guides"},"seo":{"metatags":{"og:site_name":"Levelrail Docs","og:type":"website","og:image":"https://docs.levelrail.com/public/og/default.png","og:image:width":"1200","og:image:height":"630","twitter:card":"summary_large_image","twitter:image":"https://docs.levelrail.com/public/og/default.png","twitter:site":"@thegdsks","keywords":"self-hosted PaaS, Coolify alternative, Docker deployment platform, self-hosted deploys, Levelrail"},"indexing":"navigable"},"fonts":{"heading":{"family":"Geist","weight":500},"body":{"family":"Geist"}},"styling":{"codeblocks":"dark","latex":true},"errors":{"404":{"redirect":false,"title":"Page not found","description":"This page moved or never existed. Try the [quickstart](/getting-started), the [all pages index](/all-pages) or search above."}},"contextual":{"options":["copy","view","chatgpt","claude","cursor","mcp"],"display":"header"},"interaction":{"drilldown":true},"metadata":{"timestamp":true},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"levelrail","hasAudioNarration":false,"actualSubdomain":"levelrail","customDomains":["docs.levelrail.com"],"gitSource":{"type":"github","owner":"glincker","repo":"levelrail","deployBranch":"main","contentDirectory":"docs","isPrivate":false,"mountPath":"","label":""},"chroma":{"latestBuiltDeploymentHistoryId":"6ad90ff9aa73c7866e2711ee","collectionId":"76113204-d55b-43ad-91f9-beb53b09b896","liveDeploymentHistoryId":"6ad90ff9aa73c7866e2711ee","schemaVersion":"v3"},"entitlements":{},"buildId":"6ad90ff9aa73c7866e2711ee","clientVersion":"0.0.3918","searchFilterCounts":[{"language":"default","tag":"Self-host gallery","count":360},{"language":"default","tag":"How-to guides","count":170},{"language":"default","tag":"Productivity","count":71},{"language":"default","tag":"Developer Tools","count":56},{"language":"default","tag":"Deploying","count":40},{"language":"default","tag":"Get started","count":36},{"language":"default","tag":"Reference","count":32},{"language":"default","tag":"AI","count":32},{"language":"default","tag":"Appendix","count":29},{"language":"default","tag":"Media","count":28}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-v7-file-tree","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"pause-universal-search-reconcile","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-branches-v2-autocommit","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-branches-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"teamspace","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-nav-generations","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-agent-model-picker","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"client-snapshot-docs-config","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"isAtlas":false,"googleFontsInlined":true},"customCss":[{"filePath":"style.css","content":":root{--lr-live:#2fb3dc;--lr-soft:#58b1ce;--lr-teal:#107292;--lr-mono:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--lr-r:12px;--lr-face:#0e0f11;--lr-face-2:#141617;--lr-text:#cfcfd3;--lr-link:#58b1ce;--lr-line:rgba(255, 255, 255, 0.08);--lr-ring:linear-gradient(180deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.04) 40%, rgba(255, 255, 255, 0.06));--lr-ring-hot:linear-gradient(180deg, rgba(47, 179, 220, 0.55), rgba(47, 179, 220, 0.12) 45%, rgba(47, 179, 220, 0.2));--lr-sheen:linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0));--lr-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.07), 0 1px 1px rgba(0, 0, 0, 0.5), 0 8px 16px -4px rgba(0, 0, 0, 0.55), 0 24px 48px -12px rgba(0, 0, 0, 0.65);--lr-glow-op:1;--lr-grain:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 9 0 0 0 -4.3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\")}html:not(.dark){--lr-face:#ffffff;--lr-face-2:#f6f6f7;--lr-text:#3a3d40;--lr-link:#107292;--lr-line:rgba(14, 15, 17, 0.1);--lr-ring:linear-gradient(180deg, rgba(14, 15, 17, 0.2), rgba(14, 15, 17, 0.08) 40%, rgba(14, 15, 17, 0.1));--lr-ring-hot:linear-gradient(180deg, rgba(16, 114, 146, 0.6), rgba(16, 114, 146, 0.18) 45%, rgba(16, 114, 146, 0.26));--lr-sheen:linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0));--lr-shadow:0 1px 1px rgba(14, 15, 17, 0.06), 0 8px 16px -6px rgba(14, 15, 17, 0.1);--lr-glow-op:0.35}html.dark body{background-color:#000}body{position:relative;font-feature-settings:\"cv11\",\"ss03\";-webkit-font-smoothing:antialiased}body::after,body::before{content:\"\";position:absolute;inset:0 0 auto 0;height:440px;z-index:-1;pointer-events:none;background:radial-gradient(54% 92% at 50% 0,rgba(47,179,220,.42),transparent 72%),radial-gradient(38% 78% at 10% 0,rgba(31,200,176,.3),transparent 72%),radial-gradient(36% 70% at 92% 0,rgba(108,125,255,.3),transparent 72%)}body::before{opacity:calc(.4*var(--lr-glow-op))}body::after{opacity:calc(.9*var(--lr-glow-op));filter:saturate(1.2) brightness(1.25);-webkit-mask-image:var(--lr-grain),linear-gradient(to bottom,#000 0%,transparent 88%);-webkit-mask-size:220px 220px,100% 100%;-webkit-mask-composite:source-in;mask-image:var(--lr-grain),linear-gradient(to bottom,#000 0%,transparent 88%);mask-size:220px 220px,100% 100%;mask-composite:intersect}html{isolation:isolate}#content-area,#content-container{color:var(--lr-text)}#page-title{font-weight:300;letter-spacing:-.03em;line-height:1.08;text-wrap:balance}#content-area h2{font-weight:300;letter-spacing:-.025em}#content-area h3,#content-area h4{font-weight:500;letter-spacing:-.02em}#content-area p,#header p{text-wrap:pretty}#header>div>div:first-child:not(:has(h1)){font-family:var(--lr-mono);font-size:.75rem;letter-spacing:.08em;text-transform:uppercase;color:#8d8d96}#content-area a:not(.card):not([data-component-part]){color:var(--lr-link)}#content-area a[href]:not(.card){text-decoration-color:color-mix(in srgb,var(--lr-link) 40%,transparent)}#content-area a[href]:not(.card):hover{text-decoration-color:currentColor}code,kbd,pre,samp{font-family:var(--lr-mono)!important;font-feature-settings:\"calt\" 0}#content-area :not(pre)>code{background:var(--lr-face-2);border:1px solid var(--lr-line);border-radius:6px;padding:.1em .4em;font-size:.875em;color:inherit}html.dark #content-area :not(pre)>code{color:#e4e4e7}.code-block{border-radius:var(--lr-r);border:1px solid transparent;box-shadow:var(--lr-shadow);background:var(--lr-sheen) padding-box,linear-gradient(#08090a,#08090a) padding-box,var(--lr-ring) border-box}.code-block-background,.code-block>div{border-radius:var(--lr-r);background-color:transparent}#sidebar .sticky.pointer-events-none,.code-block [data-component-part=code-block-root],.code-block pre{background:0 0}.code-block code{font-size:.8125rem;line-height:1.7}.accordion,.accordion-group,.callout,.card{border:1px solid transparent;border-radius:var(--lr-r);box-shadow:var(--lr-shadow);background:var(--lr-sheen) padding-box,linear-gradient(var(--lr-face),var(--lr-face)) padding-box,var(--lr-ring) border-box}.card{transition:box-shadow .2s,transform .2s}.card:hover{transform:translateY(-1px)}html.dark .card:hover{border-color:rgba(47,179,220,.45)}.card [data-component-part=card-title]{font-weight:500;letter-spacing:-.01em}#navbar .nav-tabs-item[data-active=true],.card [data-component-part=card-icon]{color:var(--lr-live)}html:not(.dark) #navbar .nav-tabs-item[data-active=true],html:not(.dark) .card [data-component-part=card-icon]{color:#107292}.accordion{margin-bottom:.75rem}.accordion,.accordion-group{overflow:hidden}.callout{padding:1rem 1.25rem}.callout[class*=border-blue],.callout[class*=border-green],.callout[class*=border-red],.callout[class*=border-sky],.callout[class*=border-yellow]{background:var(--lr-sheen) padding-box,linear-gradient(var(--lr-face),var(--lr-face)) padding-box,var(--lr-ring) border-box}@media (prefers-reduced-motion:reduce){.card{transition:none}.card:hover{transform:none}}#content-area table{border-collapse:separate;border-spacing:0;border:1px solid var(--lr-line);border-radius:var(--lr-r);overflow:hidden;box-shadow:var(--lr-shadow)}#content-area thead th{font-family:var(--lr-mono);font-size:.75rem;font-weight:500;letter-spacing:.06em;text-transform:uppercase;color:#8d8d96;background:var(--lr-face-2)}#content-area tbody td,#content-area thead th{border-bottom:1px solid var(--lr-line)}#content-area tbody tr:last-child td{border-bottom:0}#navbar>div.absolute,#navbar>div>div.absolute{background:color-mix(in srgb,var(--lr-face) 38%,transparent);border-bottom-color:var(--lr-line);backdrop-filter:blur(20px) saturate(1.3);-webkit-backdrop-filter:blur(20px) saturate(1.3)}#navbar .nav-tabs-item[data-active=true]::after,#navbar .nav-tabs-item[data-active=true]>div{background:var(--lr-live)}html:not(.dark) #navbar .nav-tabs-item[data-active=true]::after,html:not(.dark) #navbar .nav-tabs-item[data-active=true]>div{background:#107292}#topbar-cta-button a>span.absolute{border-radius:50px;background:var(--lr-live);border:1px solid rgba(255,255,255,.35);box-shadow:inset 0 1px 0 rgba(255,255,255,.55),inset 0 -2px 0 rgba(0,0,0,.18),0 6px 14px -6px rgba(47,179,220,.45)}#topbar-cta-button a{padding-left:1rem;padding-right:.75rem}#topbar-cta-button a .text-white,#topbar-cta-button a svg{color:#000!important}html:not(.dark) #topbar-cta-button a>span.absolute{background:#06232c;border-color:transparent;box-shadow:0 1px 1px rgba(14,15,17,.3)}html:not(.dark) #topbar-cta-button a .text-white,html:not(.dark) #topbar-cta-button a svg{color:#2fb3dc!important}#search-bar-entry{border-radius:50px;border:1px solid transparent;box-shadow:var(--lr-shadow);background:var(--lr-sheen) padding-box,linear-gradient(var(--lr-face-2),var(--lr-face-2)) padding-box,var(--lr-ring) border-box}#search-bar-entry:hover{background:var(--lr-sheen) padding-box,linear-gradient(var(--lr-face-2),var(--lr-face-2)) padding-box,var(--lr-ring-hot) border-box}#sidebar .sidebar-group-header{font-family:var(--lr-mono);font-size:.6875rem;letter-spacing:.08em;text-transform:uppercase;font-weight:500;color:#8d8d96}#sidebar a[aria-current=page]{border-radius:50px;color:var(--lr-live);background:rgba(47,179,220,.08);box-shadow:inset 0 0 0 1px rgba(47,179,220,.2)}html:not(.dark) #sidebar a[aria-current=page]{color:#084f67;background:rgba(16,114,146,.09);box-shadow:inset 0 0 0 1px rgba(16,114,146,.25)}#sidebar a:not([aria-current=page]):hover{border-radius:50px}#table-of-contents .toc-item[data-active=true],#table-of-contents a[data-active=true]{color:var(--lr-live)}html:not(.dark) #table-of-contents a[data-active=true]{color:#107292}#footer{border-top:1px solid var(--lr-line)}#content-area table td,#content-area table th{padding:.75rem 1rem;vertical-align:top}#content-area table td:first-child{font-weight:500}#navbar .nav-tabs-item[data-active=true] img[src*=\"/assets/icons/\"],#sidebar a[aria-current=page] img[src*=\"/assets/icons/\"]{filter:brightness(0) saturate(100%) invert(58%) sepia(96%) saturate(237%) hue-rotate(148deg) brightness(81%) contrast(149%)}html:not(.dark) #navbar .nav-tabs-item[data-active=true] img[src*=\"/assets/icons/\"],html:not(.dark) #sidebar a[aria-current=page] img[src*=\"/assets/icons/\"]{filter:brightness(0) saturate(100%) invert(28%) sepia(90%) saturate(323%) hue-rotate(148deg) brightness(118%) contrast(123%)}"}],"page":{"pagePath":"migrating-from-netlify","slug":"/migrating-from-netlify","pageMetadata":{"title":"Migrating a static site from Netlify","description":"A runbook for moving a Netlify site to Levelrail: build settings, netlify.toml mapping, redirects and headers, deploy contexts, forms, build hooks, a DNS cutover and the features with no equivalent.","og:url":"https://docs.levelrail.com/migrating-from-netlify","href":"/migrating-from-netlify"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"A runbook for moving a Netlify site to Levelrail: build settings, netlify.toml mapping, redirects and headers, deploy contexts, forms, build hooks, a DNS cutover and the features with no equivalent.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,input:`input`,li:`li`,ol:`ol`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Table,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(_components.p,{children:`This is a runbook for moving one Netlify site onto a Levelrail instance you run yourself. It covers only features that exist in this repository today. Where something has no equivalent, it says so. There is no Netlify importer, so every step is manual.`}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[`Some areas are still beta (see `,_jsx(_components.a,{href:`/feature-status`,children:`feature status`}),`), including `,_jsx(_components.a,{href:`/forms`,children:`Forms`}),`. Keep the Netlify site live as your rollback path until the new one has run clean for a while.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`1-inventory-what-netlify-holds`,children:`1. Inventory what Netlify holds`}),`\n`,_jsx(_components.p,{children:`Collect this before you touch Levelrail. Much of it lives in the Netlify dashboard, not in your repo.`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Build command, publish directory and base directory (Site configuration, Build and deploy).`}),`\n`,_jsx(_components.li,{children:`Environment variables, and which deploy contexts they apply to.`}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`netlify.toml`}),`, `,_jsx(_components.code,{children:`_redirects`}),` and `,_jsx(_components.code,{children:`_headers`}),`.`]}),`\n`,_jsx(_components.li,{children:`Forms, with their notification settings.`}),`\n`,_jsx(_components.li,{children:`Build hooks and scheduled functions.`}),`\n`,_jsx(_components.li,{children:`Domains, DNS provider and current TTLs. Note whether the domain uses Netlify DNS.`}),`\n`,_jsx(_components.li,{children:`Use of Functions, Edge Functions, Identity, Blobs, Image CDN and Analytics. Section 5 covers each.`}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`2-create-the-site`,children:`2. Create the site`}),`\n`,_jsxs(_components.ol,{children:[`\n`,_jsxs(_components.li,{children:[`\n`,_jsxs(_components.p,{children:[`Connect the git provider in the dashboard, then check it with `,_jsx(_components.code,{children:`levelrail-cli git-providers`}),`.`]}),`\n`]}),`\n`,_jsxs(_components.li,{children:[`\n`,_jsxs(_components.p,{children:[`Create the app from the Git repository card in the `,_jsx(_components.strong,{children:`New`}),` wizard, or from a spec file:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`9`,language:`yaml`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`yaml`,children:_jsxs(_components.code,{language:`yaml`,numberOfLines:`9`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:`version`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#B5CEA8`,\"--shiki-dark\":`#B5CEA8`},children:`1`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:`services`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` web`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` build`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` type`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:`static`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` baseDirectory`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:`apps/web`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` command`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:`npm run build`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` publish`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:`dist`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` domains`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`: [`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:`www.example.com`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`]`})]}),`\n`]})})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` apps`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` create`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --name`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --file`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` app.yaml`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --service`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` web`}),_jsx(_components.span,{style:{color:`#D7BA7D`,\"--shiki-dark\":`#D7BA7D`},children:` \\\\`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --repo`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` https://github.com/you/site.git`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --ref`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` main`})]}),`\n`]})})}),`\n`]}),`\n`,_jsxs(_components.li,{children:[`\n`,_jsxs(_components.p,{children:[`To change the build later, use the app’s `,_jsx(_components.strong,{children:`Source`}),` tab, or:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` apps`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` git-source`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` set`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --build-type`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` static`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --build-command`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` \"pnpm build\"`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --publish-dir`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` build`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` apps`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` git-source`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` set`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --build-type`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` static`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --infer-build`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`--infer-build`}),` detects the framework and fills whichever of the command and publish directory you leave out.`]}),`\n`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`The build runs in a Node based container. It installs dependencies with the package manager that matches your lockfile, then runs your command. For Hugo, Jekyll or another generator, set `,_jsx(_components.code,{children:`APP_STATIC_BUILD_IMAGE`}),` to an image that has the tool and a shell, or build elsewhere and serve the committed output. The output is capped at `,_jsx(_components.code,{children:`APP_STATIC_BUILD_MAX_MB`}),` (default 2048). See `,_jsx(_components.a,{href:`/pages`,children:`Static pages`}),`.`]}),`\n`,_jsxs(_components.p,{children:[`Leave `,_jsx(_components.code,{children:`domains:`}),` out of the first deploy so nothing competes with Netlify for the real hostname. Check the result with `,_jsx(_components.code,{children:`levelrail-cli static-sites list`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`3-map-netlify-toml`,children:`3. Map netlify.toml`}),`\n`,_jsxs(_components.p,{children:[`Only the keys below map. Everything else in `,_jsx(_components.code,{children:`netlify.toml`}),` is ignored.`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`netlify.toml`}),_jsx(_components.th,{children:`Levelrail`}),_jsx(_components.th,{children:`Notes`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`build.command`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`build.command`})}),_jsx(_components.td,{children:`Static services only`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`build.publish`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`build.publish`})}),_jsx(_components.td,{children:`Relative to the base directory`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`build.base`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`build.baseDirectory`})}),_jsx(_components.td,{children:`The project root inside the repo`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`build.environment`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`build.args`})}),_jsx(_components.td,{children:`Build time variables for a static build. They end up in public output, so no secrets`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`[context.deploy-preview]`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`contexts: deploy-preview`})}),_jsxs(_components.td,{children:[`Env, `,_jsx(_components.code,{children:`build.command`}),`, `,_jsx(_components.code,{children:`build.publish`}),`, `,_jsx(_components.code,{children:`build.args`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`[context.branch-deploy]`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`contexts: branch-deploy`})}),_jsx(_components.td,{children:`Same fields`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`[context.branch-name]`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`contexts: \"branch:\u003cpattern>\"`})}),_jsxs(_components.td,{children:[`An exact name or a glob such as `,_jsx(_components.code,{children:`release/*`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`[context.production]`})}),_jsx(_components.td,{children:`The app’s own settings`}),_jsx(_components.td,{children:`Production takes no overrides`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`[[redirects]]`})}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`routes:`}),` in `,_jsx(_components.code,{children:`app.yaml`}),`, or `,_jsx(_components.code,{children:`_redirects`})]}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`from`}),`, `,_jsx(_components.code,{children:`to`}),`, `,_jsx(_components.code,{children:`status`}),`, `,_jsx(_components.code,{children:`force`}),`, `,_jsx(_components.code,{children:`query`}),`. Conditions are not supported`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`[[headers]]`})}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`headers:`}),` in `,_jsx(_components.code,{children:`app.yaml`}),`, or `,_jsx(_components.code,{children:`_headers`})]}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`path`}),`, `,_jsx(_components.code,{children:`values`}),`, `,_jsx(_components.code,{children:`remove`})]})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[_jsx(_components.code,{children:`[[redirects]]`}),` with a proxy `,_jsx(_components.code,{children:`200`}),` to another site`]}),_jsx(_components.td,{children:`Not supported`}),_jsx(_components.td,{children:`Use a path forwarder, see below`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`build.ignore`})}),_jsx(_components.td,{children:`Not supported`}),_jsx(_components.td,{children:`Git source path filters are a different mechanism`})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[_jsx(_components.code,{children:`[functions]`}),`, `,_jsx(_components.code,{children:`[[edge_functions]]`})]}),_jsx(_components.td,{children:`Not supported`}),_jsx(_components.td,{children:`See section 5`})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[_jsx(_components.code,{children:`[[plugins]]`}),` (build plugins)`]}),_jsx(_components.td,{children:`Not supported`}),_jsx(_components.td,{})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`[dev]`})}),_jsx(_components.td,{children:`Not supported`}),_jsx(_components.td,{})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[_jsx(_components.code,{children:`[build.processing]`}),` (asset optimization)`]}),_jsx(_components.td,{children:`Not supported`}),_jsx(_components.td,{})]})]})]}),`\n`,_jsx(Heading,{level:`3`,id:`redirects-and-headers`,children:`Redirects and headers`}),`\n`,_jsxs(_components.p,{children:[`Copy `,_jsx(_components.code,{children:`_redirects`}),` and `,_jsx(_components.code,{children:`_headers`}),` into the published directory unchanged. They are compiled at deploy time and removed from the served files. Single page app fallback, clean URLs, trailing slash and the 404 page are set under `,_jsx(_components.code,{children:`static:`}),` in `,_jsx(_components.code,{children:`app.yaml`}),`:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`5`,language:`yaml`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`yaml`,children:_jsxs(_components.code,{language:`yaml`,numberOfLines:`5`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:`static`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` spa`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:`true`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` cleanUrls`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:`true`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` trailingSlash`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:`remove`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` notFound`}),_jsx(_components.span,{style:{color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:`404.html`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Check the files before you deploy, and test a URL against the live rules:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` static-sites`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` routes`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` lint`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --redirects`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` ./public/_redirects`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --headers`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` ./public/_headers`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` static-sites`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` routes`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` test`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` /old`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --request-query`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` \"ref=x\"`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Differences to expect:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`A rule that is not forced is skipped when a file exists at that path. Add `,_jsx(_components.code,{children:`!`}),` (or `,_jsx(_components.code,{children:`force: true`}),`) where you relied on a redirect winning over a file.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`Country=`}),`, `,_jsx(_components.code,{children:`Language=`}),`, `,_jsx(_components.code,{children:`Role=`}),` and `,_jsx(_components.code,{children:`Cookie=`}),` conditions are skipped and reported. These need an edge runtime.`]}),`\n`,_jsxs(_components.li,{children:[`A `,_jsx(_components.code,{children:`200`}),` rewrite must point at a path on the same site. Proxying to another origin is not available in static routing. A `,_jsx(_components.a,{href:`/domain-traffic-controls#path-forwarders`,children:`path forwarder`}),` on the domain can send a path to an external URL or another app.`]}),`\n`,_jsxs(_components.li,{children:[`Limits apply: 1000 rules, 100 header blocks and 256 KiB per file by default. See `,_jsx(_components.a,{href:`/static-routing`,children:`Static routing`}),`.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`4-settings-around-the-site`,children:`4. Settings around the site`}),`\n`,_jsx(Heading,{level:`3`,id:`environment-variables-and-deploy-contexts`,children:`Environment variables and deploy contexts`}),`\n`,_jsxs(_components.p,{children:[`Static build env goes in `,_jsx(_components.code,{children:`build.args`}),`. For different values on previews and branches, set a context:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` apps`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` contexts`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` set`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --context`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` deploy-preview`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --env`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` API_URL=https://staging.example.com`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` apps`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` contexts`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` set`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --context`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` \"branch:release/*\"`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --build-command`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` \"npm run build:rc\"`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --publish`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` dist`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` apps`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` contexts`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` show`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --context`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` branch-deploy`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --branch`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` release/1.2`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Context env is stored as plain values. Put secrets in branch env overrides (`,_jsx(_components.code,{children:`apps branch-env set ... --secret`}),`) or app secrets. Production deploys use the app’s own settings only. See `,_jsx(_components.a,{href:`/deploy-contexts`,children:`Deploy contexts`}),`.`]}),`\n`,_jsx(Heading,{level:`3`,id:`branch-deploys`,children:`Branch deploys`}),`\n`,_jsx(_components.p,{children:`Off by default. Turn them on for the branches you want:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` apps`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` branch-deploys`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` set`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --mode`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` patterns`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --branch`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` staging`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --branch`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` \"feature/*\"`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Each branch gets a stable URL and rebuilds on every push. Removal happens when the branch is deleted or after `,_jsx(_components.code,{children:`APP_BRANCH_DEPLOY_TTL`}),` (default 336h). At most 10 live branch deploys per app by default.`]}),`\n`,_jsx(Heading,{level:`3`,id:`deploy-previews`,children:`Deploy previews`}),`\n`,_jsxs(_components.p,{children:[`Pull request previews are a separate feature, enabled per app with `,_jsx(_components.code,{children:`levelrail-cli apps previews enable my-site`}),`. They need a pull request and a primary domain on the control plane. See `,_jsx(_components.a,{href:`/preview-environments`,children:`Preview environments`}),`. The docs do not say whether pull request previews cover static sites specifically, so try one on a test pull request before relying on it. Branch deploys are documented for static sites.`]}),`\n`,_jsx(Heading,{level:`3`,id:`forms`,children:`Forms`}),`\n`,_jsx(_components.p,{children:`Netlify finds forms in your HTML. Levelrail does not. Create each form and paste its snippet:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` forms`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` create`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` contact`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --redirect-url`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` https://example.com/thanks`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` forms`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` snippet`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` contact`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`The endpoint takes URL encoded, multipart and JSON posts. The `,_jsx(_components.code,{children:`bot-field`}),` input is the default spam trap, so a honeypot named `,_jsx(_components.code,{children:`bot-field`}),` can stay. Submissions have an inbox, search, CSV export and notifications to your existing alert channels. File uploads are not stored, there is no CAPTCHA check, and no confirmation goes back to the visitor. Forms are beta. Export old submissions from Netlify yourself, since there is no importer. See `,_jsx(_components.a,{href:`/forms`,children:`Forms`}),`.`]}),`\n`,_jsx(Heading,{level:`3`,id:`build-hooks`,children:`Build hooks`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` apps`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` build-hooks`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` create`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` my-site`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --name`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` \"cms publish\"`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --branch`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` main`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`The URL is shown once. Point your CMS webhook at it. A POST answers `,_jsx(_components.code,{children:`202`}),` and builds the latest commit on the branch. Triggers are limited to 6 per hook per minute by default. See `,_jsx(_components.a,{href:`/build-hooks`,children:`Build hooks`}),`.`]}),`\n`,_jsx(Heading,{level:`3`,id:`scheduled-functions`,children:`Scheduled functions`}),`\n`,_jsxs(_components.p,{children:[`A Netlify scheduled function runs on a clock. Here the clock calls a path on a container app, and wakes it if it is asleep. A static site has no server to call, so move the handler to a `,_jsx(_components.a,{href:`/functions`,children:`Functions`}),` app or another container app, then:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` jobs`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` create`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` web`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` digest`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --mode`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` http`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --http-path`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` /cron/digest`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --schedule`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` \"@hourly\"`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --retries`}),_jsx(_components.span,{style:{color:`#B5CEA8`,\"--shiki-dark\":`#B5CEA8`},children:` 3`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Add `,_jsx(_components.code,{children:`--background`}),` for work that outlives a request. Http jobs cannot be declared in `,_jsx(_components.code,{children:`app.yaml`}),` yet, and the call carries no custom headers. See `,_jsx(_components.a,{href:`/scheduled-functions`,children:`Scheduled functions`}),`.`]}),`\n`,_jsx(Heading,{level:`3`,id:`lock-deploys`,children:`Lock deploys`}),`\n`,_jsxs(_components.p,{children:[`Netlify’s lock publishing maps to the publish lock. Builds still run, then wait as held builds until you publish them. Manual deploys and rollbacks still go live, as do safety rollbacks. Use the lock control on the app page, `,_jsx(_components.code,{children:`levelrail-cli apps lock \u003capp>`}),` and `,_jsx(_components.code,{children:`levelrail-cli apps unlock \u003capp>`}),`, or `,_jsx(_components.code,{children:`PUT`}),` and `,_jsx(_components.code,{children:`DELETE`}),` on `,_jsx(_components.code,{children:`/api/v1/apps/{name}/publish-lock`}),`. Publish a held build with `,_jsx(_components.code,{children:`levelrail-cli apps publish \u003capp> \u003cdeploy-id>`}),`. See `,_jsx(_components.a,{href:`/publish-lock`,children:`Publish lock`}),`.`]}),`\n`,_jsx(Heading,{level:`3`,id:`split-testing`,children:`Split testing`}),`\n`,_jsxs(_components.p,{children:[`Not available for static sites yet. Split testing works for container apps with a domain, between a branch deploy or a retained release and production. If you used Netlify split tests on a static site, there is no replacement today. See `,_jsx(_components.a,{href:`/split-testing`,children:`Split testing`}),`.`]}),`\n`,_jsx(Heading,{level:`3`,id:`drop-deploy`,children:`Drop deploy`}),`\n`,_jsx(_components.p,{children:`For a site you build by hand, with no git:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes dark-plus dark-plus`,style:{backgroundColor:`#0B0C0E`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#D4D4D4`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` deploy`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` ./site`}),_jsx(_components.span,{style:{color:`#569CD6`,\"--shiki-dark\":`#569CD6`},children:` --static`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#DCDCAA`,\"--shiki-dark\":`#DCDCAA`},children:`levelrail-cli`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` deploy`}),_jsx(_components.span,{style:{color:`#CE9178`,\"--shiki-dark\":`#CE9178`},children:` site.zip`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`No build runs. What you drop is what is served. See `,_jsx(_components.a,{href:`/drop-deploy`,children:`Drop deploy`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`5-where-netlify-is-ahead-and-what-has-no-equivalent`,children:`5. Where Netlify is ahead, and what has no equivalent`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Netlify feature`}),_jsx(_components.th,{children:`On Levelrail`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Netlify Functions`}),_jsxs(_components.td,{children:[_jsx(_components.a,{href:`/functions`,children:`Functions`}),` are container apps that sleep when idle, with a cold start of a container start. Rewrite the handler as a small HTTP server. Not an edge runtime`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Background functions`}),_jsxs(_components.td,{children:[`An http job with `,_jsx(_components.code,{children:`--background`}),`, which the control plane waits on for up to 15 minutes by default`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Edge Functions`}),_jsx(_components.td,{children:`Not offered. A stated non-goal. Logic that depends on the edge runtime has to move into your app or a CDN`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Identity`}),_jsxs(_components.td,{children:[`No equivalent. Use a separate auth service or build login into an app. See `,_jsx(_components.a,{href:`/integrations`,children:`Integrations`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Blobs`}),_jsx(_components.td,{children:`No equivalent. Managed Postgres and Redis exist, and S3-compatible storage is used for backups only`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Image CDN`}),_jsx(_components.td,{children:`Not offered. Pre-generate sizes at build time`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Analytics`}),_jsx(_components.td,{children:`No browser-side analytics. Server-side request rate, errors and latency percentiles per app are built in`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Global CDN and edge network`}),_jsx(_components.td,{children:`Not provided. Files are served from the node or nodes you run`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Automatic form detection`}),_jsx(_components.td,{children:`Not supported. Forms are created explicitly`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Static split testing`}),_jsx(_components.td,{children:`Not supported yet`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Redirect conditions (country, language, role)`}),_jsx(_components.td,{children:`Not supported`})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[`Proxy rewrites to external URLs in `,_jsx(_components.code,{children:`_redirects`})]}),_jsx(_components.td,{children:`Not supported there. Use a path forwarder`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Build plugins`}),_jsx(_components.td,{children:`Not supported`})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`6-domains-and-dns-cutover`,children:`6. Domains and DNS cutover`}),`\n`,_jsxs(_components.ol,{children:[`\n`,_jsx(_components.li,{children:`Lower TTLs first. At least one old TTL period before the cutover, set the TTL on the relevant records to 60 to 300 seconds.`}),`\n`,_jsx(_components.li,{children:`If the domain uses Netlify DNS, recreate the zone at a DNS host you control before you move anything, and copy every record, not only the site’s.`}),`\n`,_jsxs(_components.li,{children:[`Add the domains: `,_jsx(_components.code,{children:`levelrail-cli domains add my-site example.com www.example.com`}),`. Set the redirect between them with `,_jsx(_components.code,{children:`levelrail-cli domains redirect set my-site www.example.com --target https://example.com`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`Get a certificate ready. HTTPS is self-signed until Let’s Encrypt is enabled, and issuance needs the domain to already point at the server. See `,_jsx(_components.a,{href:`/domains-and-ingress#tls-whats-actually-shipped-today`,children:`Domains and ingress`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`Test before DNS changes: `,_jsx(_components.code,{children:`curl --resolve example.com:443:\u003cserver-ip> https://example.com/`}),` (add `,_jsx(_components.code,{children:`-k`}),` while the certificate is self-signed).`]}),`\n`,_jsx(_components.li,{children:`Cut over. Point the A and AAAA records at the Levelrail server. Keep the site on Netlify untouched.`}),`\n`,_jsxs(_components.li,{children:[`Watch `,_jsx(_components.code,{children:`dig +short example.com`}),` from outside your network and the app’s request metrics.`]}),`\n`,_jsxs(_components.li,{children:[`To roll back, restore the old records. Write the old values down first. A bad release on Levelrail is a different case: `,_jsx(_components.code,{children:`levelrail-cli apps deploys rollback-to my-site DEPLOY_ID`}),`, or the Rollback button on the deploy.`]}),`\n`]}),`\n`,_jsx(_components.p,{children:`Raise TTLs again only after a clean run.`}),`\n`,_jsx(Heading,{level:`2`,id:`checklist`,children:`Checklist`}),`\n`,_jsxs(_components.ul,{className:`contains-task-list`,children:[`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,`Build command, publish directory and base directory match Netlify, and the site builds.`]}),`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,_jsx(_components.code,{children:`_redirects`}),` and `,_jsx(_components.code,{children:`_headers`}),` pass `,_jsx(_components.code,{children:`routes lint`}),`, and key URLs behave the same under `,_jsx(_components.code,{children:`routes test`}),`.`]}),`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,`SPA fallback, clean URLs and the 404 page behave as before.`]}),`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,`Env vars are set, with preview and branch values in contexts.`]}),`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,`Branch deploys are on for the branches you use.`]}),`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,`Every form exists, its snippet is in the HTML, and a test post reaches the inbox and a notification channel.`]}),`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,`Build hooks are recreated and the CMS points at the new URLs.`]}),`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,`Scheduled work runs against a container app, and each job ran once.`]}),`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,`Every use of Functions, Edge Functions, Identity, Blobs, Image CDN and Analytics has an answer.`]}),`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,`Domains are added, the certificate is ready, and the TTLs were lowered.`]}),`\n`,_jsxs(_components.li,{className:`task-list-item`,children:[_jsx(_components.input,{type:`checkbox`,disabled:true}),` `,`A rollback to the previous deploy works.`]}),`\n`]}),`\n`,_jsx(_components.p,{children:`Decommission the Netlify site only after this passes.`}),`\n`,_jsx(Heading,{level:`2`,id:`see-also`,children:`See also`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:_jsx(_components.a,{href:`/netlify-alternative`,children:`Netlify alternative`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.a,{href:`/pages`,children:`Static pages`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.a,{href:`/static-routing`,children:`Static site routing rules`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.a,{href:`/domains-and-ingress`,children:`Domains and ingress`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.a,{href:`/migrating-from-vercel`,children:`Migrating from Vercel`})}),`\n`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"codeStyling":"dark","config":{},"pageMetadata":{"title":"Migrating a static site from Netlify","description":"A runbook for moving a Netlify site to Levelrail: build settings, netlify.toml mapping, redirects and headers, deploy contexts, forms, build hooks, a DNS cutover and the features with no equivalent.","og:url":"https://docs.levelrail.com/migrating-from-netlify","href":"/migrating-from-netlify"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"1. Inventory what Netlify holds","slug":"1-inventory-what-netlify-holds","depth":2,"children":[]},{"title":"2. Create the site","slug":"2-create-the-site","depth":2,"children":[]},{"title":"3. Map netlify.toml","slug":"3-map-netlify-toml","depth":2,"children":[{"title":"Redirects and headers","slug":"redirects-and-headers","depth":3,"children":[]}]},{"title":"4. Settings around the site","slug":"4-settings-around-the-site","depth":2,"children":[{"title":"Environment variables and deploy contexts","slug":"environment-variables-and-deploy-contexts","depth":3,"children":[]},{"title":"Branch deploys","slug":"branch-deploys","depth":3,"children":[]},{"title":"Deploy previews","slug":"deploy-previews","depth":3,"children":[]},{"title":"Forms","slug":"forms","depth":3,"children":[]},{"title":"Build hooks","slug":"build-hooks","depth":3,"children":[]},{"title":"Scheduled functions","slug":"scheduled-functions","depth":3,"children":[]},{"title":"Lock deploys","slug":"lock-deploys","depth":3,"children":[]},{"title":"Split testing","slug":"split-testing","depth":3,"children":[]},{"title":"Drop deploy","slug":"drop-deploy","depth":3,"children":[]}]},{"title":"5. Where Netlify is ahead, and what has no equivalent","slug":"5-where-netlify-is-ahead-and-what-has-no-equivalent","depth":2,"children":[]},{"title":"6. Domains and DNS cutover","slug":"6-domains-and-dns-cutover","depth":2,"children":[]},{"title":"Checklist","slug":"checklist","depth":2,"children":[]},{"title":"See also","slug":"see-also","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":["/_mintlify/custom/levelrail/34332f0f5f6180ce/public/deploy.js"],"subdomain":"levelrail","siteName":"Levelrail","title":"Migrating a static site from Netlify - Levelrail","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-10-21T19:18:59.704Z","gitLastModified":"2026-10-21T19:18:12.000Z","originalFileLocation":"migrating-from-netlify.md"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"dark\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"A runbook for moving a Netlify site to Levelrail: build settings, netlify.toml mapping, redirects and headers, deploy contexts, forms, build hooks, a DNS cutover and the features with no equivalent.\">\u003cmeta data-mint-head name=\"keywords\" content=\"self-hosted PaaS, Coolify alternative, Docker deployment platform, self-hosted deploys, Levelrail\">\u003cmeta data-mint-head name=\"application-name\" content=\"Levelrail\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/levelrail/sfnZeja47AHfK8h4/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/levelrail/sfnZeja47AHfK8h4/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/levelrail/sfnZeja47AHfK8h4/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/levelrail/sfnZeja47AHfK8h4/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/levelrail/sfnZeja47AHfK8h4/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/levelrail/sfnZeja47AHfK8h4/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/levelrail/sfnZeja47AHfK8h4/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/levelrail/sfnZeja47AHfK8h4/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/levelrail/sfnZeja47AHfK8h4/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Migrating a static site from Netlify - Levelrail\">\u003cmeta data-mint-head property=\"og:description\" content=\"A runbook for moving a Netlify site to Levelrail: build settings, netlify.toml mapping, redirects and headers, deploy contexts, forms, build hooks, a DNS cutover and the features with no equivalent.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Levelrail Docs\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.levelrail.com/migrating-from-netlify\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://docs.levelrail.com/public/og/default.png\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Migrating a static site from Netlify - Levelrail\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"A runbook for moving a Netlify site to Levelrail: build settings, netlify.toml mapping, redirects and headers, deploy contexts, forms, build hooks, a DNS cutover and the features with no equivalent.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://docs.levelrail.com/public/og/default.png\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/levelrail/sfnZeja47AHfK8h4/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Levelrail\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#107292\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.levelrail.com/migrating-from-netlify\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/migrating-from-netlify.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.levelrail.com/#organization\",\"name\":\"Levelrail\",\"url\":\"https://docs.levelrail.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/levelrail/56HjW70mOjUmjIrm/assets/brand/wordmark-light.svg?fit=max\u0026auto=format\u0026n=56HjW70mOjUmjIrm\u0026q=85\u0026s=cbe02b3c3d17b11f9cc5a18551a30782\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.levelrail.com#website\",\"name\":\"Levelrail\",\"url\":\"https://docs.levelrail.com\",\"publisher\":{\"@id\":\"https://docs.levelrail.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.levelrail.com/migrating-from-netlify#webpage\",\"url\":\"https://docs.levelrail.com/migrating-from-netlify\",\"name\":\"Migrating a static site from Netlify\",\"description\":\"A runbook for moving a Netlify site to Levelrail: build settings, netlify.toml mapping, redirects and headers, deploy contexts, forms, build hooks, a DNS cutover and the features with no equivalent.\",\"dateModified\":\"2026-10-21T19:18:12.000Z\",\"isPartOf\":{\"@id\":\"https://docs.levelrail.com#website\"},\"breadcrumb\":{\"@id\":\"https://docs.levelrail.com/migrating-from-netlify#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.levelrail.com/migrating-from-netlify#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Migrate\",\"item\":\"https://docs.levelrail.com/migrate-to-levelrail\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Migrating a static site from Netlify\",\"item\":\"https://docs.levelrail.com/migrating-from-netlify\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.levelrail.com/migrating-from-netlify#article\",\"headline\":\"Migrating a static site from Netlify\",\"name\":\"Migrating a static site from Netlify\",\"description\":\"A runbook for moving a Netlify site to Levelrail: build settings, netlify.toml mapping, redirects and headers, deploy contexts, forms, build hooks, a DNS cutover and the features with no equivalent.\",\"url\":\"https://docs.levelrail.com/migrating-from-netlify\",\"mainEntityOfPage\":{\"@id\":\"https://docs.levelrail.com/migrating-from-netlify#webpage\"},\"image\":\"https://docs.levelrail.com/public/og/default.png\",\"dateModified\":\"2026-10-21T19:18:12.000Z\",\"publisher\":{\"@id\":\"https://docs.levelrail.com/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.levelrail.com#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Levelrail\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Levelrail","nav":{"global":null,"tabs":[{"tab":"Get started","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/rocket-launch.svg","pages":[{"title":"Get started","href":""}]},{"tab":"How-to guides","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/book-open.svg","groups":[{"group":"Essentials","pages":[{"title":"Troubleshooting","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/lifebuoy.svg","sidebarTitle":"Troubleshooting","href":"/troubleshooting"},{"title":"Doctor","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/lifebuoy.svg","sidebarTitle":"Doctor","href":"/doctor"},{"title":"Email notifications","href":"/email-notifications"},{"title":"Email templates and messages","href":"/email-templates"},{"title":"Alert inbox","href":"/alert-inbox"},{"title":"Event webhooks","href":"/event-webhooks"},{"title":"Attention center","href":"/attention-center"}]},{"group":"Deploying","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/cloud-arrow-up.svg","pages":[{"title":"Deploying and managing apps","sidebarTitle":"Deploying apps","href":"/deploying-apps"},{"title":"Deploy from your computer","sidebarTitle":"Deploy from CLI","href":"/deploy-from-cli"},{"title":"Drop deploy for static sites","sidebarTitle":"Drop deploy","href":"/drop-deploy"},{"title":"Compose apps","href":"/compose-apps"},{"title":"New app wizard","href":"/new-app-wizard"},{"title":"Importing apps","href":"/importing-apps"},{"title":"Static pages","sidebarTitle":"Pages","href":"/pages"},{"title":"Static site routing rules","sidebarTitle":"Static routing","href":"/static-routing"},{"title":"Deploy safety","href":"/deploy-safety"},{"title":"Deploy failures","href":"/deploy-failures"},{"title":"Deploying through git host outages","href":"/git-host-outages"},{"title":"Deploy trace","href":"/deploy-trace"},{"title":"Build performance","href":"/build-performance"},{"title":null,"href":"/build-center"},{"title":"Scheduled deploys","href":"/scheduled-deploys"},{"title":"Scheduled jobs","href":"/scheduled-jobs"},{"title":"Scheduled and background functions","href":"/scheduled-functions"},{"title":"Image auto-update","href":"/image-auto-update"},{"title":"Build hooks","href":"/build-hooks"},{"title":"Sleep when idle","href":"/sleep-when-idle"},{"title":"Functions","href":"/functions"},{"title":"Canary deploys","href":"/canary-deploys"},{"title":"Split testing","href":"/split-testing"},{"title":"Deployments page","href":"/deployments-page"},{"title":"Images","href":"/images"},{"title":"Deploy previews","href":"/deploy-previews"},{"title":"Framework presets","href":"/frameworks"},{"title":"Deploy URLs","href":"/deploy-urls"},{"title":"Deploy links and badge","href":"/deploy-link"},{"title":"Deploy timeline","href":"/deploy-timeline"},{"title":"Preview environments per pull request","sidebarTitle":"Preview environments","href":"/previews"},{"title":"Chat-interactive deploy approvals","sidebarTitle":"Chat approvals","href":"/chat-deploy-approvals"},{"title":"Supply chain visibility","href":"/supply-chain"},{"title":"Secret scanning","href":"/secret-scanning"},{"title":"Deploy status badge","href":"/deploy-status-badge"},{"title":"Dashboard screenshots","href":"/screenshots"},{"title":"Running Levelrail in Docker","href":"/docker"},{"title":"Zero-downtime deploys with Docker: readiness-gated cutover and rollback","href":"/zero-downtime-deploys"},{"title":"Preview environments per pull request on your own servers","href":"/preview-environments"},{"title":"Deploy contexts: env and build settings per kind of deploy","href":"/deploy-contexts"}]},{"group":"Networking and multi-node","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/globe-hemisphere-west.svg","pages":[{"title":"Domains and ingress","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/globe-hemisphere-west.svg","sidebarTitle":"Add a domain and HTTPS","href":"/domains-and-ingress"},{"title":"Standalone domains","href":"/standalone-domains"},{"title":"DNS zones and records","href":"/dns"},{"title":"Diagnose a domain","href":"/domain-diagnosis"},{"title":"Domain traffic controls","href":"/domain-traffic-controls"},{"title":"CDN and caching","href":"/cdn-and-caching"},{"title":"Forms","tag":"Beta","href":"/forms"},{"title":"Firewall","href":"/firewall"},{"title":"Verifying real ACME issuance against a live domain","href":"/acme-verification-runbook"},{"title":"Multi-node: adding and managing nodes","tag":"Beta","href":"/multi-node"},{"title":"Multi-node quickstart","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/hard-drives.svg","tag":"Beta","sidebarTitle":"Add a second server","href":"/multi-node-quickstart"},{"title":"Resource-aware placement","tag":"Beta","href":"/resource-aware-placement"},{"title":"Autoscaling","tag":"Beta","href":"/autoscaling"},{"title":"Node provisioning","href":"/node-provisioning"},{"title":"Multi-cloud provisioning quickstart","href":"/multi-cloud-provisioning"},{"title":"Network topology view","href":"/network-topology"},{"title":"Project topology graph","href":"/service-topology-graph"},{"title":"Build node routing","href":"/build-node-routing"}]},{"group":"Databases and storage","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/database.svg","pages":[{"title":"Managing databases","href":"/managing-databases"},{"title":"Database upgrades","href":"/database-upgrades"},{"title":"Database version catalog","href":"/database-version-catalog"},{"title":"External databases","href":"/external-databases"},{"title":"Connecting apps to databases","href":"/connecting-apps-to-databases"},{"title":"Backup targets, registry credentials, and app volume backups","sidebarTitle":"Backups and storage","href":"/backups-and-storage"},{"title":"Prove your backups work","href":"/prove-your-backups-work"},{"title":"Object storage","href":"/object-storage"},{"title":"Network shares","href":"/network-shares"},{"title":"Log explorer","href":"/log-explorer"},{"title":"Custom dashboards","href":"/custom-dashboards"},{"title":"Tracing","href":"/tracing"},{"title":"Error tracking","href":"/error-tracking"},{"title":"Loki-compatible log API","href":"/loki-api"},{"title":"Log pipelines","href":"/log-pipelines"},{"title":"Log volume budgets","href":"/log-budgets"},{"title":"Data export","href":"/data-export"},{"title":"Audit event schema","href":"/audit-event-schema"},{"title":"Control plane backup and restore","sidebarTitle":"Control plane backup","href":"/control-plane-backup"},{"title":"Archives","href":"/archives"},{"title":"Disaster recovery","href":"/disaster-recovery"},{"title":"Upgrade safely","href":"/upgrade-safely"},{"title":"Database access and network","href":"/database-access"},{"title":"Private networks and allow rules","href":"/private-networks"},{"title":"Database explorer and fleet overview","href":"/database-explorer"},{"title":"Database viewer","href":"/database-viewer"}]},{"group":"CI/CD and automation","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/git-branch.svg","pages":[{"title":"Deploying from GitHub Actions","href":"/github-actions"},{"title":"Pipelines","href":"/pipelines"},{"title":"Pipelines: OIDC federation","href":"/pipelines-oidc"},{"title":"Git integrations","href":"/git-integrations"},{"title":"Git reliability","href":"/git-reliability"},{"title":"Deploy with git push","href":"/git-push-deploy"},{"title":"Import a repository","sidebarTitle":"Import a repo","href":"/import-a-repo"},{"title":"Monorepos","href":"/monorepos"}]},{"group":"Security and access","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/shield-check.svg","pages":[{"title":"Sign-in engine","href":"/auth-engine"},{"title":"Host firewall","href":"/host-firewall"},{"title":"Exposure audit","href":"/exposure-audit"},{"title":"Security center","href":"/security-center"},{"title":"Compliance evidence","href":"/compliance-evidence"},{"title":"Emergency access","href":"/emergency-access"},{"title":"Master key rotation","href":"/master-key-rotation"},{"title":"Secret rotation policies","href":"/secret-rotation"},{"title":"Your account","href":"/account"},{"title":"Access control","href":"/access-control"},{"title":"Teams and roles","href":"/teams-and-roles"},{"title":"Team and project quotas","href":"/team-and-project-quotas"},{"title":"Environments","href":"/environments"},{"title":"Promotions and release notes","href":"/promotions-and-releases"},{"title":"Identity and access: users, roles, and IAM policies","href":"/identity-and-access"},{"title":"SAML single sign-on","href":"/sso-saml"},{"title":"OAuth and OIDC single sign-on","href":"/sso-oidc"},{"title":"SCIM provisioning","href":"/scim"},{"title":"Session policy","href":"/session-policy"},{"title":"Projects, organizations, and environments","href":"/projects-and-organizations"}]},{"group":"Scale and operations","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/chart-line-up.svg","pages":[{"title":"Managing apps at scale","href":"/managing-apps-at-scale"},{"title":"Tags","href":"/tags"},{"title":"Resource console","href":"/resource-console"},{"title":"Command palette","href":"/command-palette"},{"title":"Cost estimate","href":"/cost-estimate"},{"title":"Usage and cost","href":"/usage-and-cost"},{"title":"Cost explorer and budgets","href":"/cost-explorer"},{"title":"Spend caps","href":"/spend-caps"},{"title":"Integrations","href":"/integrations"},{"title":"Cloud integrations","href":"/cloud-integrations"},{"title":"Rolling back the control plane","href":"/rollback"},{"title":"Resilience: what survives a control plane crash","href":"/resilience"},{"title":"Resilience, in short","href":"/resilience-summary"},{"title":"Observability: metrics, logs, and alerts","href":"/observability"},{"title":"Traffic observability: rate, errors and duration","href":"/traffic-observability"},{"title":"Public status page","href":"/status-page"},{"title":"Scheduled maintenance","href":"/scheduled-maintenance"},{"title":"Deploy policies","href":"/deploy-policies"},{"title":"What's new panel","href":"/whats-new-panel"}]},{"group":"AI and agents","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/sparkle.svg","pages":[{"title":"Working with AI agents","href":"/agents"},{"title":"AI control","href":"/ai-control"}]},{"group":"Templates and catalog","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/squares-four.svg","pages":[{"title":"Service template catalog","tag":"Beta","href":"/templates-and-registry"},{"title":"Private registries","href":"/private-registries"},{"title":"Template catalog","href":"/template-catalog"},{"title":"Template status","href":"/templates-status"},{"title":"Starter kit templates","href":"/templates"}]},{"group":"Migrate","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/arrows-left-right.svg","pages":[{"title":"Migrate to Levelrail","sidebarTitle":"Migration overview","href":"/migrate-to-levelrail"},{"title":"Migrate from Coolify","sidebarTitle":"From Coolify","href":"/migrate-from-coolify"},{"title":"Migrate from Dokploy","sidebarTitle":"From Dokploy","href":"/migrate-from-dokploy"},{"title":"Migrate from CapRover","sidebarTitle":"From CapRover","href":"/migrate-from-caprover"},{"title":"Migrate from Dokku","sidebarTitle":"From Dokku","href":"/migrate-from-dokku"},{"title":"Migrate from Docker Compose","sidebarTitle":"From Docker Compose","href":"/migrate-from-docker-compose"},{"title":"Migration reference","sidebarTitle":"Reference","href":"/migration-reference"},{"title":"Migrating from Coolify, Dokploy, or CapRover","sidebarTitle":"Moved: migration guides","href":"/migrating-from-coolify-dokploy-and-caprover"},{"title":"Migrate on the same server","href":"/migrate-same-server"},{"title":"Migrate over SSH","href":"/migrate-over-ssh"},{"title":"Migrating a Next.js app from Vercel","description":"A runbook for moving a Vercel Next.js app to Levelrail: inventory, concept mapping, a DNS cutover and rollback plan, gaps with no equivalent and a shadow run.","href":"/migrating-from-vercel"},{"title":"Migrating a static site from Netlify","href":"/migrating-from-netlify"},{"title":"Migrating a service from Cloud Run","description":"A runbook for moving a Google Cloud Run service to Levelrail: concept mapping, deploying the same image, secrets, Cloud SQL, domains, jobs and what you give up.","href":"/migrating-from-cloud-run"}]},{"group":"Beta and experimental","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/flask.svg","pages":[{"title":"AI assistant integration: levelrail-mcp","tag":"Beta","href":"/ai-assistant"},{"title":"MCP tools, token budget and write mode","tag":"Beta","href":"/mcp"},{"title":"MCP recipes","tag":"Beta","href":"/mcp-recipes"},{"title":"In-app AI assistant chat","tag":"Experimental","href":"/ai-assistant-chat"},{"title":"AI models","tag":"Experimental","href":"/ai-models"},{"title":"Load balancing across replicas","tag":"Experimental","href":"/load-balancing"},{"title":"Platform as code","tag":"Experimental","href":"/platform-as-code"},{"title":"Resource advisor (beta)","tag":"Beta","href":"/resource-advisor"},{"title":"Feature flags","tag":"Beta","href":"/feature-flags"},{"title":"Log archive","tag":"Beta","href":"/log-archive"},{"title":"Automatic cleanup","tag":"Beta","href":"/janitor"},{"title":"Unified search","tag":"Beta","href":"/search"}]}]},{"tab":"Reference","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/code.svg","pages":[{"title":"Reference","href":"/app-spec-reference"}]},{"tab":"Concepts","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/lightbulb.svg","pages":[{"title":"Concepts","href":"/product-tour"}]},{"tab":"Self-host gallery","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/squares-four.svg","pages":[{"title":"Self-host gallery","href":"/self-host"}]},{"tab":"Appendix","icon":"https://mintlify.s3.us-west-1.amazonaws.com/levelrail/assets/icons/books.svg","pages":[{"title":"Appendix","href":"/roadmap"}]}]},"title":"Migrating a static site from Netlify - Levelrail","buildId":"dpl_22zLaHtK6fmmSqApXTTP5tC7UBZn","deploymentId":"dpl_22zLaHtK6fmmSqApXTTP5tC7UBZn","dynamics":[],"chunks":[]}