Netlify Atomic Deploys: How Immutable Builds Enable Safe Previews and Instant Rollback
Netlify's atomic deploy model makes every build an immutable snapshot. Publishing swaps a pointer for instant rollback and automatic PR previews, but previews are public by default and rollback does not touch databases. Includes a worked netlify.toml and verification checklist.
23 Oct 2025, 12:03 UTC

The Problem and the Takeaway
Teams need to review unmerged code in a real environment without risking production, and they need to undo a bad release in seconds. Netlify solves both with an atomic deploy model: every build produces an immutable, addressable deploy, and publishing merely swaps a pointer. Previews get their own URLs automatically; rollback is a single click that repoints the production domain to a previous deploy. The catch: previews are public by default, rollback doesn't touch databases or external services, and misconfigured monorepos or environment variables can leak secrets or waste build minutes.
How Immutable Deploys Work
When Netlify finishes a build, it writes the output directory e.g. dist to a new, read-only filesystem snapshot and assigns it a unique deploy ID. That deploy is served at a permanent URL like 65f1a2b3c4d5e6f7g8h9--my-site.netlify.app. The production domain my-site.netlify.app or a custom domain is just a DNS CNAME that Netlify's edge layer points to whichever deploy is currently published. No files are copied or overwritten on publish, only the pointer changes. This means:
- There is no partial-upload state; visitors never see a half-deployed site.
- Every successful build in history remains addressable and can be republished instantly.
- Rollback is a metadata operation, not a rebuild.
Worked Configuration: Context-Aware Builds in netlify.toml
The standard way to keep previews from calling production APIs is to scope environment variables per deploy context. Below is a minimal netlify.toml that builds a Vite/React app, publishes the dist folder, and points previews at a staging backend.
# netlify.toml
[build]
publish = "dist"
command = "npm run build"
# Production uses the real API
[context.production.environment]
API_URL = "https://api.example.com"
# Deploy previews (PRs) use staging
[context.deploy-preview.environment]
API_URL = "https://staging-api.example.com"
# Branch deploys (e.g., staging branch) can have their own target
[context.branch-deploy.environment]
API_URL = "https://staging-api.example.com"
# Optional: override for a specific long-lived branch
[context.staging.environment]
API_URL = "https://staging-api.example.com"
ENABLE_FEATURE_FLAGS = "true"
Place this file at the repository root or at the base directory if you use a monorepo. During a preview build, Netlify injects API_URL=https://staging-api.example.com into the build environment; the same codebase builds against a different backend without code changes. Verify it works by adding console.log(process.env.API_URL) to your build script and checking the deploy log for the preview.
Deploy Previews: Automatic, Public, and Controllable
When you connect a Git provider GitHub, GitLab, Bitbucket, Azure DevOps, Netlify creates a deploy preview for every pull/merge request. The preview URL appears in the PR checks and looks like deploy-preview-42--my-site.netlify.app. Branch deploys work similarly for named branches e.g. staging--my-site.netlify.app.
Default visibility: preview URLs are public. Anyone with the link can view unmerged work. Protection options depend on your plan:
- Password protection Pro and above: set a site-wide password in Site Configuration Access Control.
- Visitor access / SSO Business/Enterprise: restrict previews to team members via SAML/OIDC.
- Disable previews per repo: in the Netlify UI, uncheck Deploy previews for sensitive repositories.
Choose the least permissive option that fits your workflow. If you handle PII or unreleased features, enable password protection at minimum.
Instant Rollback: What It Does and Doesn't Do
In the Netlify dashboard Deploys, every successful deploy shows a Publish deploy button. Clicking it repoints the production domain to that deploy's immutable snapshot. Propagation across Netlify's edge typically takes under 30 seconds. This is free and does not consume build minutes.
Rollback does NOT revert:
- Database migrations or data changes
- Netlify Functions environment variables
- Edge cache entries
- DNS records or custom domain configuration
- External services
Treat rollback as a frontend-only safety net. Pair it with backward-compatible API changes and feature flags for full safety.
Common Mistakes and Limits
1. Environment Variable Leakage
Variables set in the Netlify UI Site Configuration Environment Variables apply to all contexts unless you scope them. A secret like STRIPE_SECRET_KEY added in the UI will be injected into preview builds, exposing it in client bundles if your build tool doesn't strip it. Fix: scope variables in the UI to Production only or move secrets to netlify.toml under [context.production.environment] and keep previews on a separate key.
2. Build-Minute Exhaustion
Every PR build consumes minutes. A team of 10 opening 3 PRs/day with 5-minute builds burns about 450 minutes/month. The free tier includes 300 minutes; Pro includes 2,000. Check current pricing before adopting preview-heavy workflows. Mitigation: set ignore commands in netlify.toml to skip builds for documentation-only changes, or use build plugins that cancel early on unchanged paths.
3. Monorepo Base Directory Misconfiguration
In a monorepo e.g. apps/web, apps/api, packages/ui, you must set the base directory in the Netlify UI Site Settings Build & Deploy Base Directory to apps/web. Without it, Netlify watches the repo root and triggers a web build on any change. With it set correctly, Netlify only builds when files under apps/web/** change. Verify by pushing a change to packages/ui and confirming no web deploy starts.
4. Plugin Assumptions About Production
Build plugins run in preview builds too. A plugin that reads process.env.PRODUCTION_ONLY_VAR will crash in previews if that var isn't scoped. Make plugins resilient to missing vars or scope the plugin itself via [context.deploy-preview.build] overrides.
Practical Verification Checklist
- Preview creation: Open a PR against the connected repo. Confirm a deploy preview URL appears in the PR checks and serves the branch's build.
- Context scoping: Add a [context.deploy-preview.environment] variable in netlify.toml, push, and verify the value appears in the preview build log.
- Rollback timing: Publish a trivial change, then use Publish deploy on the previous deploy. Measure propagation with curl -I https://your-site.netlify.app from multiple regions. Expect under 30 s.
- Monorepo isolation: Push a change outside the base directory. Confirm no deploy triggers.
- Plan limits: Review current build-minute, bandwidth, and password-protection availability on your plan tier before scaling preview usage.
Version Assumptions
This article reflects Netlify's behavior as of the 2024-2025 platform build image ubuntu-22.04 or later, Node 18+/20+. The netlify.toml context syntax [context.deploy-preview], [context.branch-deploy], named branch contexts has been stable since 2020. Always verify against Netlify's file-based configuration docs for your account's plan tier, as some features are plan-gated.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.