Environment variables

Last updated

Environment variables are the main way to configure an app and the correct place for secrets. Set them on the app's Env Vars panel.

Saving restarts the app

Changing a variable recreates the container. That's a brief interruption, and it clears anything held in memory — sessions, caches, counters, queued work.

So batch your edits rather than saving five times in a row.

They are runtime-only

The image build does not receive them. Anything read at build time gets nothing, which matters for frameworks that inline configuration into a bundle:

  • NEXT_PUBLIC_* (Next.js), VITE_* (Vite) and similar are baked in when the image is built. Setting them on the app has no effect on what the browser receives, and there's no error — the value is simply undefined.

If you need build-time values, build the image in CI where you can supply them, then push the image. See Configuration.

Secrets

This is the right place for tokens, API keys and passwords — not source code, and not the Dockerfile. ENV in a Dockerfile is stored in an image layer and readable by anyone who can pull the image, without running it.

If a secret has already been committed or built into an image, rotate it. Removing it from a later commit doesn't un-publish it.

Read required secrets at startup and exit clearly if they're missing:

TOKEN = os.environ.get("BOT_TOKEN")
if not TOKEN:
    sys.exit("BOT_TOKEN is not set - set it as an environment variable on the app")

Rules and limits

  • DF_ is reserved. The platform injects variables with that prefix — including your app's own public URLs — and will reject your own names that use it.
  • Values can be large, and newlines are allowed, so a multi-line private key or certificate works as a single variable.
  • = is not allowed in names, and control characters other than newlines and tabs are rejected in values.

Useful things to make configurable

Anything you'd otherwise redeploy to change:

  • Intervals and limits — INTERVAL_SECONDS, MAX_TOKENS, rate-limit ceilings
  • Feature toggles, including a kill switch your handler checks
  • Paths for data, so the same code runs locally and here — DB_PATH=/data/app.db
  • Log level, so verbose output is one setting away rather than a deploy away

Some frameworks need PYTHONUNBUFFERED=1 (or an equivalent) to make output appear in logs promptly.

  • Config files — for tools that must read a file from a path