# Environment variables and secrets for web app deploys in 2026

Last updated: 2026-11-10

Keep every secret out of your repository and store it in your host's secret settings, marked as a secret so nobody can read it back after saving. Split the rest into two groups: values your framework bakes into browser code at build time, which are public by definition, and values your server reads when it runs, which can stay private. Then give each environment its own values and know how to rotate a key in minutes.

Five of the seven hosts compared here offer write-only secrets: **Vercel**, **Netlify**, **Cloudflare**, **Railway** and **Eurobase** all hide a secret once it's saved. What differs is when a change takes effect and whether the platform stops a build that leaks a secret into public files.

## Secret handling at a glance

| Platform | Can you read a secret back? | When a change takes effect | Leak check on builds |
|---|---|---|---|
| Eurobase | No, a secret can't be read again after saving | A live Worker redeploys on save, containers ask to restart, HTTP functions get it at once, other apps on the next deployment | A Workers build fails if it writes a build secret into its output, and the value is hidden in build logs |
| Vercel | No for the Secret type, yes for Config values | New deployments only | Secret values of 32 characters or more are replaced with [REDACTED] in build logs |
| Netlify | No, once a variable is flagged as containing secret values | Not stated | Secret scanning fails the build if a secret value appears in repository code or build output |
| Cloudflare | No, the value is hidden in Wrangler and the dashboard | `wrangler secret put` creates and deploys a new version immediately | Not documented |
| Railway | No for sealed variables, which never show in the UI or API | Not stated | Not documented |
| Render | Not documented | You pick: rebuild and deploy, deploy the existing build, or save only | Not documented |
| Heroku | Yes, `heroku config` lists values | Every change restarts the app and creates a release | Not documented |

Behavior is taken from each provider's own documentation, checked on 10 November 2026.

## What to check before you trust a host with a key

- **Write-only storage.** If anyone with dashboard access can copy a production database password, that's one more place it can leak from. Prefer a secret type that hides the value after saving.
- **Build values versus runtime values.** A host may let you pick whether a value reaches the build, the running app, or both. Database passwords rarely need to reach the build.
- **What triggers a redeploy.** Some platforms apply a new value only to new deployments, others restart the app on every change. You need to know this before you rotate a key under pressure.
- **Leak detection.** A build that copies a secret into a JavaScript bundle publishes it to every visitor. Scanning that fails the build catches this before it goes live.
- **Separate values per environment.** Preview and staging builds shouldn't hold production credentials, especially when they build code from pull requests.
- **Who and what can change values.** Check whether coding agents, CI tokens and team members can read or overwrite secrets, and whether changes are logged.

## Keep secrets out of Git

The Twelve-Factor App gives a simple test: your codebase should be safe to make open source at any moment without exposing a single credential. That means no keys in config files, no `.env` file in the repository and no tokens hard-coded as constants.

1. Add `.env*` to `.gitignore` before you create the first one. Cloudflare's docs say the same for its `.dev.vars` files.
2. Commit a `.env.example` with names and placeholder values, so a new teammate knows what to fill in.
3. Turn on secret scanning. GitHub runs it for free on public repositories. Private repositories in a GitHub organization need GitHub Secret Protection on Team or Enterprise Cloud.
4. If a key ever reaches a commit, treat it as stolen. Deleting the commit doesn't help once it has been pushed. Revoke it at the provider and issue a new one.

## Know which values end up in the browser

This is where most leaks happen. Frameworks read environment variables in two places: on the server, where values stay private, and in the browser bundle, where every visitor can read them.

- **Next.js** keeps variables server-only by default. A name starting with `NEXT_PUBLIC_` is inlined into the JavaScript sent to the browser during `next build`, and its value is frozen at build time, so changing it later on the host has no effect until you rebuild.
- **Vite** exposes variables starting with `VITE_` in client code after bundling, and tells you to leave the prefix off anything that must stay private.
- **Other frameworks** have their own rules for what reaches the browser. Check your framework's documentation before you add a variable.

The rule that follows: never put a secret under a public prefix. A Supabase publishable key or a public analytics ID is fine there. A Stripe secret key, a database URL or a Supabase secret key isn't.

## Give each environment its own values

Production, preview and local development should never share a payment key or a database password. Vercel lets you assign Secret values per environment or per Preview branch. Netlify sets a value per deploy context, so a function can get a production key in production and a non-production key in Deploy Previews. Cloudflare reads `.dev.vars` or `.env` for local development and supports one file per Cloudflare environment.

On a host without preview deployments, the same idea applies with separate projects: one for production, one for staging, each with its own secrets.

## Rotate without guessing

OWASP's secrets management cheat sheet makes a point worth repeating: restarting your app doesn't revoke a stolen credential. It stays valid until the service that issued it expires or revokes it. A clean rotation looks like this:

1. Create a second key at the provider, such as Stripe or your database.
2. Save it on your host and wait until the change is live: a new deployment, a restart or a redeploy, depending on the platform.
3. Check your logs for errors.
4. Revoke the old key at the provider.

Vercel documents a rotation flow meant to avoid downtime. Heroku restarts the app on every config change. Render lets you redeploy the existing build with new values, so you don't wait for a full rebuild.

## How Eurobase handles variables and secrets

Eurobase deploys web apps from Git to European infrastructure run by Scaleway, a French provider. It's a business registered in Warsaw, Poland. In a project, open **Variables**, enter a name and value, choose **Secret** or **Variable**, and choose what reads it: **Build**, **App deployments**, functions or containers. Every value is stored encrypted, a secret can't be read back after you save it, and a resource can hold up to 64 values.

- Apps on V8 isolates read project secrets at runtime in `env`, and in `process.env` when Node.js compatibility is on. A secret that shares a name with a binding is refused at cold start with `503 secret-binding-conflict`.
- A build secret must be a single line of at least 16 characters. It's hidden in build logs, and a build that writes it into the output fails. Public names such as `NEXT_PUBLIC_` or `VITE_` go in Build as variables, not secrets.
- Container apps get build variables during the build, but read their secrets only when they run.
- Coding agents connected through the MCP server can set public variables, never secrets. When a project needs a secret, the agent tells you its name and you add the value yourself.

- **Best for:** teams that want build and runtime values separated, and secrets kept away from coding agents.
- **Price:** free to start; paid workspaces cost €10 a month on Starter or €20 a month on Pro, + VAT. Nothing is billed by usage today: past a limit, new work is refused, apps on V8 isolates and Functions pause until the monthly reset, and containers and static sites keep serving.
- **What it lacks:** preview deployments, so there are no per-preview values; secrets in container builds; and Dockerfile builds.

Moving from Vercel? The [migration guide](/docs/guides/migrate-from-vercel) starts by exporting your variables with `vercel env pull`. For the wider data protection picture, read the [GDPR hosting checklist](/i/gdpr-hosting-checklist), and if an agent writes your code, see [hosting for AI-built apps](/i/hosting-for-ai-built-apps).

## Frequently asked questions

### Is it safe to commit a .env file?

No. Anything in Git history can be read by everyone with repository access, and by everyone if the repository is ever made public. Commit a `.env.example` with placeholder values and keep the real file local.

### Why is my secret visible in the browser?

Because it was given a public prefix such as `NEXT_PUBLIC_` or `VITE_`, or because client code imported it. Those values are copied into the JavaScript bundle at build time. Rename the variable, read it only in server code, rotate the key and rebuild.

### Do I need a separate secrets manager?

For a single web app on a modern host, the host's write-only secret storage is usually enough. A dedicated manager starts to pay off when many services share credentials, when you need audit trails across systems, or when you want short-lived credentials created on demand, which OWASP recommends where possible.

### Why didn't my app pick up a changed variable?

On many platforms a change applies only to new deployments or after a restart. Values baked in at build time, like `NEXT_PUBLIC_` variables in Next.js, need a full rebuild. Check your host's behavior in the table above.

### Should a coding agent see my secrets?

It's safer if it doesn't. Give the agent the names it needs, add the values yourself in the host's dashboard, and let the agent manage only public variables.

## Sources

- [The Twelve-Factor App: Config](https://12factor.net/config)
- [OWASP Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html)
- [GitHub: About secret scanning](https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning)
- [Next.js: Environment variables](https://nextjs.org/docs/app/guides/environment-variables)
- [Vite: Env variables and modes](https://vite.dev/guide/env-and-mode)
- [Vercel: Environment variables](https://vercel.com/docs/environment-variables) and [Config and Secret variables](https://vercel.com/docs/environment-variables/sensitive-environment-variables)
- [Netlify: Environment variables overview](https://docs.netlify.com/build/environment-variables/overview/) and [Secrets controller](https://docs.netlify.com/build/environment-variables/secrets-controller/)
- [Cloudflare Workers: Secrets](https://developers.cloudflare.com/workers/configuration/secrets/)
- [Railway: Variables](https://docs.railway.com/guides/variables)
- [Render: Environment variables and secrets](https://render.com/docs/configure-environment-variables)
- [Heroku: Configuration and config vars](https://devcenter.heroku.com/articles/config-vars)
- Eurobase: [Secrets](/docs/getting-started#secrets), [Workers variables and bindings](/docs/workers#bindings), [Containers build](/docs/containers#build), [AI agents](/docs/agents), [Supabase](/docs/supabase), [Migrate from Vercel](/docs/guides/migrate-from-vercel)
