Environment variables and secrets for your app
Set environment variables and secrets in the dashboard or with ox vars, reference one in another, see the keys ox provides and hides, restore old ones.
Last updated 2026-10-08
On this page
- Set an environment variable or secret
- List variables and where they come from
- Use one variable inside another
- Keys ox provides: PORT, DATABASE_URL and more
- Which secrets are hidden in logs
- Variables at build time and run time
- Variable history and restoring an old version
- Keep a secret out of staging and previews
- If a variable does not work
To set an environment variable or a secret for your app, add it on the project's Variables tab, or run ox vars set <project> KEY, which asks for the value so it never lands in your shell history. Saving redeploys the app with the new value. ox keeps variables out of your repo, provides some keys such as DATABASE_URL itself, and hides secret values in logs.
ox vars lists each key with where it comes from, and hides the values. A deploy gives your app your variables and the ones ox provides, like DATABASE_URL and PORT. In the logs, a value of yours 6 characters or longer, unless it is a path, shows as [redacted].Set an environment variable or secret
In the dashboard, open the project and pick the Variables tab. Type the key and the value, press Add variable, and then choose Save & deploy or Save, deploy later. To paste many at once, use Edit as .env.
From a terminal, give only the key. ox asks for the value and does not show it as you type, so it never lands in your shell history:
ox vars set shop STRIPE_KEY
ox vars set shop --from-file .env.productionWriting KEY=value on the command line is refused for that reason (every ox vars flag is in the CLI reference). In a script, pipe the value in: one key reads the raw value from standard input, and several keys read KEY=value lines.
A save redeploys the app so the new value goes live. Add --no-deploy to save now and use it at the next deploy, and --wait to follow the redeploy.
ox vars unset shop OLD_KEY
ox vars import shop .env
ox vars pull shopimport shows the changes and asks before it saves. pull writes your values to .env.local, or the file you give with -o; it will not write over a file that exists unless you add --force.
List variables and where they come from
ox vars shop
ox vars shop --revealThe list shows each key and where it comes from: yours, provided by ox, or still needed. Values show as ********. --reveal shows them, and that is written to your audit log. The dashboard hides values the same way until you click to reveal them.
Use one variable inside another
Write ${NAME} inside a value, and ox fills it in when it deploys:
CELERY_BROKER_URL=${REDIS_URL}
API_URL=${PUBLIC_URL}/apiYou can name your own keys and the keys ox provides. ${postgres.host} names one part of a service: host, port, user, password, database or url. Write $${ for a plain ${. A bare $NAME is left as it is.
ox checks references when you save, and refuses the save with one of these:
references form a cycle: A -> B -> A
${X} is not set (neither provided by ox nor one of your variables)
${PORT} is set per process and cannot be referenced; use $PORT in the command insteadPORT and OX_RELEASE differ per process and per release, so they cannot be referenced. Before the first deploy, a reference to a key that does not exist yet is allowed.
Keys ox provides: PORT, DATABASE_URL and more
| Key | When |
|---|---|
PORT, HOST | Always: where your app must listen. |
OX_ENV, OX_PROJECT, OX_RELEASE, OX_DATA_DIR | Always. |
PUBLIC_URL, PUBLIC_HOST | For an app or a static site that is not internal. |
DATABASE_URL | With a postgres service. |
REDIS_URL | With a redis service. |
MYSQL_URL, QDRANT_URL, NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD | With that service. |
<WORKER>_URL | For a worker that listens on a port. |
HF_HOME | With [models]. |
When a service entry has a name other than its type, its keys get the name in front, like ANALYTICS_DATABASE_URL. You cannot set a key ox provides yourself; to use your own database, remove the service from ox.toml. The config reference has the full rules.
Which secrets are hidden in logs
In run logs and in your app's logs, ox replaces these with [redacted]:
- Every value of yours that is 6 characters or longer, and each line of a value with many lines. A shorter value like
truestays, or it would break up every line it matched. - Values ox provides that hold a secret, like a database password or a
DATABASE_URL. - The credentials ox uses itself, such as for git and your S3 bucket.
A value that is a path, like /var/lib/data, stays visible, because a path carries no secret and often explains a failure.
Variables at build time and run time
ox does not split them. Your install, build and migrate steps get the same variables your app gets, including the keys ox provides, except PORT. They also get CI=true. So a variable you set is there both when the app builds and when it runs, and a change goes live at the next deploy.
Variable history and restoring an old version
Every save is a new version, and ox keeps the last 20, plus any version a kept release uses. In the dashboard, open History, then Compare with now and Restore this version. From a terminal:
ox vars history shop
ox vars restore shop 12The restore shows what it changes and asks first; --yes skips the question. A rollback can bring back a release's variables too.
Keep a secret out of staging and previews
A staging copy and every preview start with production's variables. A live key, such as a payment provider's or the one that sends email, should stay in production. Mark it production only: on production's Variables tab, click the lock on its row. From a terminal:
ox vars production-only shop STRIPE_SECRET_KEY SMTP_PASSWORDNew staging copies and previews then start without these keys, and ox vars sync never takes them. A copy that still holds production's value loses it and redeploys; a copy that set its own value, such as a test key, keeps it. The copy's Variables tab names the keys it does not get, so set its own value there if the app needs one. The mark holds names only, never a value. --off copies the keys into new copies again and changes no copy that exists.
Two things a mark does not undo. Restoring a copy's older variables, or rolling it back with its variables, brings back what that version held, the key included; mark the key again to take it out. A copy whose value is an older value of production's, from before production changed it, keeps it; unset it on that copy. If a copy's removal fails, the answer names the copy and why; fix that, then mark the key again, which checks every copy once more.
If a variable does not work
- The deploy says
set these before deploying: SECRET_KEY: set that key, then deploy again. Build failed shows the whole message. STRIPE_KEY=… on the command line would stay in your shell history: runox vars set shop STRIPE_KEYand type the value when asked.DATABASE_URL: provided by ox; remove your value: delete your own value, or remove the service from ox.toml to use your own.- The app still sees the old value: you saved with
--no-deployor Save, deploy later. Deploy to make it live.