# Netlify environment variables: build vs runtime

> Netlify variables have scopes and deploy contexts: builds, functions and runtime each see different values. What netlify.toml can't do, and where Crunu fits.

URL: https://crunu.com/environment-variables/netlify
Language: en
Updated: 2026-10-30

On Netlify, an environment variable has a value per deploy context and, on Pro and Enterprise plans, a set of scopes that decide where it's available: the build, serverless and edge functions, runtime features like forms and signed redirects, and post-processing such as snippet injection. Values set in the Netlify UI, CLI or API can reach functions. Values declared in `netlify.toml` can't, and they override the UI for the same key during the build. Every deploy is pinned to the values that existed when it was created, so a change needs a new deploy. This page follows the [Netlify environment variables overview](https://docs.netlify.com/build/environment-variables/overview/), last updated October 3, 2026 and checked October 30, 2026.

## Scopes: who sees a variable

| Scope               | Where the value is available                                               |
| ------------------- | -------------------------------------------------------------------------- |
| **Builds**          | the build step, build plugins and framework and function builds            |
| **Functions**       | serverless functions, edge functions and on-demand builders while they run |
| **Runtime**         | forms and signed proxy redirects                                           |
| **Post processing** | snippet injection into served pages                                        |

*Figure: Diagram of how Netlify loads environment variables. No env files are read by default; values come from the environment of the process. Some values are inlined at build time and the rest are read when the app starts. There is no public prefix: nothing reaches the browser unless your code sends it.*

By default a variable applies to all scopes. Narrowing scopes is the main tool for keeping a value where it belongs. A deploy key for a private package needs only **Builds**. A payment provider's secret key used by a checkout function needs only **Functions**. Nothing secret should have **Post processing**, and Netlify enforces that for values flagged as secret.

## Deploy contexts: which value

A variable can have one value for all contexts or a different value for each: **Production**, **Deploy Previews**, **Branch deploys** (with per-branch overrides and wildcard prefixes such as `release/*`), **Preview server** and **Local development** for the Netlify CLI. A common setup gives Deploy Previews a non-production API key, so code from a pull request never runs with production credentials.

During builds, Netlify sets read-only variables you can use to tell contexts apart: `CONTEXT` (`production`, `deploy-preview`, `branch-deploy` or `dev`), `BRANCH`, `COMMIT_REF`, `URL` and `DEPLOY_PRIME_URL`. Only `URL`, `SITE_NAME` and `SITE_ID` are available to functions at runtime.

## netlify.toml versus the UI

`netlify.toml` can declare environment variables too, and two rules about them cause most confusion:

- **They don't reach functions.** The [functions documentation](https://docs.netlify.com/build/functions/environment-variables/) says it twice: variables declared in `netlify.toml` are not available to serverless functions.
- **They override the UI during builds.** A value in `netlify.toml` beats a variable with the same key set in the UI, CLI or API.

Netlify recommends the UI, CLI or API where possible, so sensitive values don't end up in your repository. Treat `netlify.toml` as a place for non-secret build settings that you're happy to review in pull requests.

## Functions get deploy-time values

The functions guide is explicit: each deploy, including Deploy Previews and branch deploys, uses the environment variable values set at the time of deployment, and to apply updated values you create a new deploy. Rotating a key used by a function is therefore two steps: change the variable, then deploy. Functions read values with `process.env`. In Lambda compatibility mode, AWS's limit applies and all variables of a function together can't exceed 4 KB. Narrowing scopes helps stay under it, and the newer functions format removes the limit.

## Secret values and scanning

Flagging a variable as **Contains secret values** makes it write-only. After saving, nobody can read it back through the UI, CLI or API, outside the `dev` context, and the flag can't be removed. Secret values must be set for explicit contexts and scopes and can't use post processing.

[Secret scanning](https://docs.netlify.com/build/environment-variables/secrets-controller/) then checks your repository and build output for those values, in plain, base64 and URL-encoded form, and fails the build before publishing if it finds one. Smart detection adds a scan for things that look like secrets without any configuration.

## Local development

The **Local development** context exists for the Netlify CLI, which gives your local dev server the values set for that context. It's handy, and it has one documented limit: variables in the Local development context can't be marked as secret. Whatever you put there is readable by anyone on the team who can see the site's variables, and it's copied onto every laptop that runs the CLI. Keep real secrets out of that context. Use throwaway development credentials, or let developers get their values another way, as described next, so a local run never needs anything stronger than a development key.

## Where crunu run fits on Netlify

Netlify Functions run on Netlify's platform, and Crunu has no way to run inside them and no built-in sync into Netlify's variables. So the split is clear: function secrets stay in Netlify, scoped to **Functions** and flagged as secret. Crunu covers the build and local development, which is where most framework values are read anyway.

1. Create one Crunu service token per deploy context, each granted a single environment, such as `site/production` for Production and `site/preview` for Deploy Previews.
2. Store them as the Netlify variable `CRUNU_TOKEN` with a different value per context, the **Builds** scope only, and **Contains secret values** checked. Add `CRUNU_SERVER`, your Crunu server address, as an ordinary variable.
3. In the build command, install `crunu` with the install script's `--user-install` mode, then run `crunu run -- npm run build`.
4. Locally, run `crunu run --env site/development -- npm run dev` after `crunu login`.

Without `--env`, `crunu run` uses every environment granted to the token, which is exactly one here, so the same build command builds previews with preview values and production with production values. A pull request can't reach the production token, because Deploy Previews get the preview token.

Some honest limits. Netlify stores the tokens, so Netlify's build system can decrypt the environments they were granted, and for those environments the promise that the server can't read your values doesn't extend to Netlify. A token's lease is a window of inactivity from 1 hour to 30 days that each sign-in extends, so a site that isn't built for longer than the lease fails its next build until an owner or admin resumes the token. Scopes need a Pro or Enterprise plan. On other plans every variable applies to all scopes, so the token would also be visible to your functions. And Netlify's secret scanning knows only the values it stores. A value that `crunu run` passes into the build isn't on that list, so if a build writes it into a public file, only smart detection might notice. Values your framework inlines into client code, such as [Vite's VITE\_ variables](https://crunu.com/environment-variables/vite), are public by design, so keep secrets out of those names entirely.

## Related reading

- [Vite](https://crunu.com/environment-variables/vite), [Astro](https://crunu.com/environment-variables/astro) and [Nuxt](https://crunu.com/environment-variables/nuxt) for what frameworks do with build-time values.
- [CI/CD secrets with OIDC](https://crunu.com/guides/ci-cd-secrets-with-oidc) for pipelines that build outside Netlify.
- [Service tokens](https://crunu.com/docs/concepts/service-tokens) and [crunu run](https://crunu.com/docs/cli/run).
- [Masked CI variables still leak](https://crunu.com/blog/masked-ci-variables-still-leak).

## FAQ

### Are Netlify build variables available to functions at runtime?

Only variables set through the Netlify UI, CLI or API with a scope that includes Functions. Variables in netlify.toml are build-only, and a value exported inside the build command doesn't become a function variable.

### Do I need to redeploy after changing a Netlify environment variable?

Yes. Each deploy uses the values that were set when it was created. Create a new deploy to apply updated values to functions and builds.

### Can crunu run inside Netlify Functions?

No. Functions run on Netlify's serverless platform, where you can't run the crunu agent or wrap the function process. Function values come from Netlify's own store. Use crunu run around the build and for local development.

### How do I keep a Crunu service token away from my functions?

Give the CRUNU_TOKEN variable only the Builds scope and flag it as containing secret values. Scopes are available on Pro and Enterprise plans. On other plans, a variable applies to all scopes, including functions.

## Sources

- [Netlify Docs: Environment variables overview](https://docs.netlify.com/build/environment-variables/overview/) (accessed 2026-10-30)
- [Netlify Docs: Build environment variables](https://docs.netlify.com/build/configure-builds/environment-variables/) (accessed 2026-10-30)
- [Netlify Docs: Environment variables and functions](https://docs.netlify.com/build/functions/environment-variables/) (accessed 2026-10-30)
- [Netlify Docs: Secrets Controller](https://docs.netlify.com/build/environment-variables/secrets-controller/) (accessed 2026-10-30)
