Skip to content

Environment variables & secrets

Projects carry configuration in two places: environment variables (returned in API responses, editable) and secrets (stored encrypted, hidden by default and revealed one at a time). Both are delivered to your build and running app the same way for every project type.

envsecrets
Shape[{ name, value }][{ name, value }]
Stored asplain listbase64 of a JSON array ([base64(JSON([...]))])
Returned in API responses?YesNames only — values need an explicit reveal
On the deploy wireplaintext (envData for containers)encrypted (iv:cipher)
Use fornon-sensitive config, public keysAPI keys, passwords, private tokens

There is no separate build-time / runtime split in the schema. env is a single list surfaced in responses, plus a separate secrets list stored base64-encoded. Whether a variable reaches the browser depends on the framework and the project type, not on a schema flag — see the inlining rule below.

Secrets are encrypted at rest and ride the deploy (and cronjob) queue encrypted as iv:cipher; env rides plaintext.

Project and list responses carry secret names only — a value never rides an ordinary API response. The settings page shows each secret masked, with an eye icon that fetches that one value on demand. Every reveal is written to your audit log with the secret’s name and the user who read it, so you can always account for who has seen a given key.

Revealing is per-secret and deliberate: there is no endpoint that dumps every value at once.

The critical NEXT_PUBLIC_* / build-time inlining rule

Section titled “The critical NEXT_PUBLIC_* / build-time inlining rule”

For static and SPA builds (Vite, Next.js, Create React App), only variables the framework exposes at build time reach the client bundle. For Next.js that means variables prefixed NEXT_PUBLIC_*; for Vite, VITE_*; for Create React App, REACT_APP_*.

Two consequences follow:

  1. Runtime-only secrets are not baked into a static build. A static site has no server to read environment variables at request time, so anything not inlined at build time simply isn’t available to the shipped app.
  2. Anything inlined is public. A variable the framework inlines at build time is compiled into the JavaScript that every visitor downloads.

For container apps, env and secrets are available to the running server process at runtime, so a backend can read a secret without exposing it to the client.

Both flow to the container build image (via entrypoint.sh) and to the running app the same way for all project types. On the deploy wire, env rides as envData (plaintext) and secrets ride encrypted. The same delivery path is used for cronjobs: env plaintext, secrets encrypted.

You set env and secrets on the project’s settings page, on the create form, or via the Projects API. Changing them is applied on the next deploy.

The Deploy to PandaStack button prompts for variables declared in pandastack.json. Note that variables declared this way are submitted as plaintext env (visible and editable on the settings page afterward), not secrets — so declare only non-sensitive config in pandastack.json, and add real secrets manually after deploy.

Some environment variable names are reserved by the platform (system variables the runtime sets). PandaStack guards these against override — you cannot clobber a reserved/system variable with your own value.

PORT, HOST, and HOSTNAME are different — for container apps, PandaStack sets sensible defaults (PORT=8080, HOST/HOSTNAME=0.0.0.0) but lets you override them. If your app hard-codes a port instead of reading PORT, set a PORT env var here to match it (e.g. PORT=18000) and the deploy will route to that port. See Listening on a port.

Connecting a third-party service under Integrations and linking it to a project injects that service’s variables into the project’s env automatically. For example, linking the Stripe integration writes STRIPE_SECRET_KEY (and, from metadata, STRIPE_PUBLISHABLE_KEY / STRIPE_WEBHOOK_SECRET); linking Sentry writes SENTRY_DSN and related vars. Linking triggers a best-effort redeploy so the new variables take effect. Disconnecting or unlinking strips exactly those injected variable names and leaves your own vars untouched. See Integrations.