Skip to main content
Percher is getting started, with a limited number of registrations each day. Create an account.

Environment variables

Set, unset, list, encryption

Ask your agent
Set up the secret keys and settings my app needs.Read the guide at percher.app/docs/env-vars
Make one of my settings available when the app is built.Read the guide at percher.app/docs/env-vars

How it works, with exact details

Environment variables are how you give your app secrets and settings — API keys, database URLs — without putting them in your code. Percher encrypts them at rest (AES-256-GCM) and injects them when your app starts. Changes take effect on the next deploy — run percher publish to apply immediately.

bunx percher env set STRIPE_KEY=sk_live_...
bunx percher env set --from-env-file .env   # every KEY=VALUE line at once
bunx percher env list          # values are masked
bunx percher env unset STRIPE_KEY
Import a whole .env file at once

--from-env-file sets every KEY=VALUE line from a .env-format file in one call (comments, quotes, and export prefixes handled; the file is only read when you pass the flag). The dashboard env editor has the same flow — "Paste a .env file" previews what will be set, with conflict badges, before anything is saved.

Different values for preview and live (deploy scopes)

Unscoped vars (the default) apply everywhere. --scope preview applies a var only to preview deploys and overrides the unscoped value there — --scope live does the reverse. Point previews at a staging database with percher env set DATABASE_URL=... --scope preview and live keeps its own value. Unset is scope-specific, and the env-requirement gate resolves against the deploy type, so a preview-only key never satisfies a live deploy. Three keys can't be scoped: SITE_PASSWORD (the login gate reads one password per app) and the PocketBase admin credentials (platform-managed).

Expose a key at build time (NEXT_PUBLIC_*, [build] pass_env)

Keys matching a public prefix (NEXT_PUBLIC_*, VITE_*, …) are forwarded to the build automatically and baked into the client bundle — the env editor marks them "build + runtime". Everything else is only available at runtime, which is correct for secrets but breaks frameworks that expect a non-prefix value at build time. Opt those in per-key via [build] pass_env in percher.toml:

# percher.toml
[build]
pass_env = [
  "NEXT_PUBLIC_API_URL",
  "VITE_POCKETBASE_URL",
  "EXPO_PUBLIC_API_URL",
]

# values still come from the env store, not the TOML
bunx percher env set NEXT_PUBLIC_API_URL=https://api.example.com

For Dockerfile-based projects (runtime = "docker") Percher does not mount build secrets yet: pass_env values arrive as --build-arg. A value read through ARG is recorded in that stage's build history and cache; ENV puts it in the image. Keep it in a build stage that is not the final stage and never ENV it, or use runtime node/bun/python, where Percher mounts non-public keys as BuildKit secrets and never writes them into the image. A build step that prints or saves a value still ships it.

Call external APIs from your app (outbound HTTPS)

Apps run on an internal Docker network and reach the public internet through a forward HTTP proxy that Percher injects automatically:

HTTP_PROXY=http://egress-proxy:8888
HTTPS_PROXY=http://egress-proxy:8888
NO_PROXY=localhost,127.0.0.1,.local
NODE_USE_ENV_PROXY=1

Bun and Node 22.21+ / 24+ use the proxy automatically — fetch("https://api.exa.ai/...") just works. Python (requests/httpx/urllib) and Go (http.DefaultTransport) also honor HTTPS_PROXY natively. For Node 20 (and the 21 / 23 lines) wire up undici manually at app startup:

import { ProxyAgent, setGlobalDispatcher } from "undici";
if (process.env.HTTPS_PROXY) {
  setGlobalDispatcher(new ProxyAgent(process.env.HTTPS_PROXY));
}

Direct outbound is blocked by design — apps can't scan internal hosts or hit cloud metadata. If a smoke probe sees ENETUNREACH on 1.1.1.1:443, that's the symptom of the runtime not using the proxy, not a missing-egress problem.

There is no host allowlist, but proxied HTTPS is port 443 only — a URL with an explicit non-standard port (https://host:8443) gets a 403 straight from the proxy. Plain http:// fetches are unrestricted by port.