Architecture Note: Netlify Deploy Previews – Design, Trust Boundaries, and Operational Checks
An overview of how Netlify Deploy Previews are built, isolated, monitored, and cleaned up, with concrete configuration and verification steps.
10 Jun 2026, 17:22 UTC

Requirements
Netlify Deploy Previews must satisfy four core requirements:
- Automatically create a unique, publicly accessible URL for each pull request.
- Build the site from the exact branch and commit referenced by the PR.
- Isolate the preview environment from production so that production secrets are not available.
- Automatically remove the preview when the PR is closed, merged, or updated.
Smallest Suitable Design
The minimal implementation that meets the requirements consists of:
- A webhook registered with the Git provider that notifies Netlify when a PR is opened, synchronized, or closed.
- Upon receiving the webhook, Netlify spins up a lightweight CI step that:
- Clones the linked repository.
- Checks out the PR branch at the specific commit SHA.
- Runs the build command defined in the site’s
netlify.tomlor dashboard settings. - Uploads the resulting static assets to a temporary site whose subdomain follows the pattern
preview--<site-id>--<pr-number>.netlify.app. - Stores metadata (PR number, commit SHA, deploy ID) in Netlify’s internal database for later lookup and cleanup.
- When the PR is closed or a new sync arrives, Netlify looks up the stored metadata and deletes the temporary site, returning a 404 for its URL.
Trust and Data Boundaries
Previews run on the same isolated container infrastructure used for production builds, but with a deliberately restricted environment:
- Filesystem: Read‑only except for a temporary cache directory (
/opt/buildhome/cache) that is wiped after each build. - Environment variables: Only the variables explicitly defined in the site settings (or
netlify.toml) are injected. Production‑only variables such asAPI_KEYare deliberately omitted unless they are also marked as preview‑safe. - Network: Outbound traffic is limited to HTTPS endpoints; direct access to internal Netlify services or metadata APIs is blocked, reducing the risk of data exfiltration.
- Secrets: Because the preview inherits the same build plugins as production, any plugin that unconditionally reads a secret or calls an external API could leak data. The platform does not plugin‑level sandboxing beyond the filesystem and network limits.
Operational Checks
After the asset upload, Netlify performs a health check:
- Edge nodes attempt to fetch the root document (
/index.html) from the newly deployed preview. - If the fetch succeeds with a 2xx status, the preview is marked ready and the PR status check updates to “success”.
- If the build step exits with a non‑zero code, the deploy is marked failed and the PR check shows an error with a link to the deploy log.
- If the build succeeds but the edge node cannot serve assets (e.g., missing
_headersfile causing a 502), Netlify automatically retries the deploy up to three times before marking it failed.
Operators can verify the isolation of environment variables by inspecting the deploy log:
# Example log snippet (viewable in Netlify Dashboard → Deploys → Preview deploy)
1:00:00 PM: $ echo $API_KEY
1:00:00 PM: (empty line)
An empty line indicates that API_KEY was not injected into the preview build environment.
Failure Modes
Common failure modes and their observable symptoms:
| Failure Mode | Symptom | Typical Cause |
|---|---|---|
| Build step failure | PR check shows “Error”; deploy log contains non‑zero exit code and error output. | Missing dependencies, syntax errors, or a plugin that expects a production‑only secret. |
| Asset upload success but edge node 502 | Preview URL returns 502 Bad Gateway; Netlify automatically retries. | Misconfigured redirects, missing _headers or _redirects files, or a corrupt cache directory. |
| Preview returns 404 after PR close | Expected behavior; the preview has been cleaned up. | PR closed or merged; Netlify removed the temporary site. |
| Preview accessible before PR is public | Anyone who guesses the URL can view the site. | Predictable subdomain pattern; no authentication layer on previews. |
When the Design Would Change
Certain conditions would prompt a revision of the minimal design:
- Need for private previews: If an organization requires that preview URLs not be guessable, Netlify would need to add a random token or authentication layer to the subdomain, moving away from the deterministic
preview--<site-id>--<pr-number>pattern. - Different trust boundaries: Should a plugin require access to a production secret that cannot be safely omitted, the platform might introduce a separate secret‑injection step that provisions a preview‑specific vault, increasing complexity.
- Build‑time performance constraints: For large monorepos where cloning the full repository for each PR is costly, Netlify could adopt a shallow‑clone or caching strategy, altering the CI step.
- Regulatory data‑residency rules: If certain data must never leave a specific geographic region, the preview infrastructure would need to be region‑locked, affecting the choice of edge nodes and potentially the upload endpoint.
Practical Verification Steps
To confirm that a Deploy Preview behaves as designed, follow these steps:
- Open a pull request against the
mainbranch in a repository connected to Netlify. - In the GitHub/GitLab/Bitbucket PR page, locate the “Deploy Preview” check under the “Checks” tab and click its URL (e.g.,
https://preview--123abc-netlify--pr-45.netlify.app). - Observe that the page loads the site built from the PR’s commit SHA.
- In the Netlify Dashboard, open the deploy log for that preview and verify that production environment variables (e.g.,
process.env.API_KEY) are undefined or absent. - Close or merge the pull request. After a few minutes, revisit the preview URL; it should return a 404, and the preview should no longer appear in the site’s “Deploy Previews” list.
Where to run: The steps above are performed in the browser and the Netlify Dashboard; no CLI is required. If you prefer the CLI, you can trigger a preview manually with:
# Requires Netlify CLI installed and a valid NETLIFY_AUTH_TOKEN netlify deploy --build --dir=dist --alias=preview--$SITE_ID--$PR_NUMBERReplace
$SITE_IDwith your Netlify site ID and$PR_NUMBERwith the pull‑request number. The command must be run on a machine with read access to the repository; it will clone the repo, check out the branch, run the build, and upload the assets. Risks include accidental exposure of secrets if the build command logs environment variables.Limitations to keep in mind:
- Previews are publicly accessible by default; never commit secrets or sensitive data to the repository.
- Because previews reuse the same build plugins as production, any plugin that assumes a production‑only context may fail or leak data.
- The preview URL pattern is predictable; if privacy is required, additional measures (e.g., a custom domain with authentication) must be added outside Netlify’s native preview feature.
By adhering to the minimal design described above, teams obtain reliable, automatically cleaned‑up preview environments while maintaining clear trust boundaries and observable operational checks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.