# 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. To set an environment variable or a secret for your app, add it on the project's **Variables** tab, or run `ox vars set KEY`. The command asks for the value, so it never lands in your shell history. Saving redeploys the app with the new value. Variables stay out of your repo. ox provides some keys itself, such as `DATABASE_URL`, 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 shows as `[redacted]`, unless it is a path, an address or a common setting word. ## Set an environment variable or secret In the dashboard, open the project and pick the **Variables** tab. Type the key and the value, and press **Add variable**. 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: Set a variable: ```sh ox vars set shop STRIPE_KEY ox vars set shop --from-file .env.production ``` ox refuses `KEY=value` on the command line for the same reason. In a script, pipe the value in. With one key, ox reads the raw value from standard input. With several keys, it reads `KEY=value` lines. The [CLI reference](https://console.t12.sorv.dev/docs/cli#variables-and-services) lists every `ox vars` flag. A save redeploys the app so the new value goes live. Add `--no-deploy` to save now and use the value at the next deploy. Add `--wait` to follow the redeploy. Other changes: ```sh ox vars unset shop OLD_KEY ox vars import shop .env ox vars pull shop ``` `import` shows the changes and asks before it saves. `pull` writes your values to `.env.local`, or to the file you give with `-o`. It writes a key you marked not needed as `unset KEY`, so `import` reads it back the same way. It will not write over a file that exists unless you add `--force`. ## List variables and where they come from List variables: ```sh ox vars shop ox vars shop --reveal ``` The list shows each key and where it comes from: yours, provided by ox, or still needed. Values show as `********`. `--reveal` shows them, and your audit log records it. The dashboard hides values the same way. The eye shows one value and hides it again after 30 seconds, and your audit log records that too. The copy button beside it copies the value without showing it. On a staging copy or a preview, a key that holds production's value reads same as production. A key that holds another value reads overridden. A value made only of references to a service or to a key ox provides, such as `${postgres.password}`, resolves to the copy's own value, so it reads overridden. ## Use one variable inside another Write `${NAME}` inside a value, and ox fills it in when it deploys: A reference: ```text CELERY_BROKER_URL=${REDIS_URL} API_URL=${PUBLIC_URL}/api ``` Write the app's own address this way, never as a literal: `ALLOWED_HOSTS=${PUBLIC_HOST}`, `CORS_ALLOWED_ORIGINS=${PUBLIC_URL}`, `FRONTEND_URL=${PUBLIC_URL}`. Production, staging and every preview then fill in their own address, the build included, and a new domain needs no edit. When ox copies production's values into a staging copy or a preview, it turns production's own address in a value into one of these references for you. You 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: Reference errors: ```text 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 instead ``` `PORT` differs per process and `OX_RELEASE` per release, so you cannot reference them. 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`, `PUBLIC_SCHEME`, `PUBLIC_PORT` | For an app or a static site that is not internal. The scheme and port (`https` and `443`, or `http` and `80`) are the URL's own, for an app that asks for its address in parts. | | `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. | | `_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. To use your own database, remove the service from ox.toml. The [config reference](https://console.t12.sorv.dev/docs/config#variables) has the full rules. ## A service's usual names Many apps read their database or cache from separate keys such as `DB_HOST`, `POSTGRES_PASSWORD` or `REDIS_PORT`, instead of the one URL ox provides. When your `.env.example` lists one of these and the project has one service of that kind, ox sets the key from the service. The review then never asks you for a value ox already has. The review, `ox check` and the list here show such keys as *from postgres*. - The names are a fixed list that real apps read: `DB_*` and `DATABASE_*` (host, port, user, password, database), `DB_URL`, `POSTGRES_*`, libpq's `PG*` and `REDIS_*`. `DB_HOST` counts only when the project has one postgres or one mysql, not both. For a name that is not on the list, set it yourself with `${postgres.host}`, `${redis.password}` and the like. - Your own value always wins, an empty one too. These keys are not locked like `DATABASE_URL`: set one and the next deploy uses yours. If you set the host or the URL, ox leaves the rest of that group to you. - Once the example lists one name of a group, ox sets all of its names, because an app reads the one its own code uses. - A project that already runs keeps what its app reads: only the keys it would be asked are set. ## 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 `true` stays, 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 of yours that carries no secret stays visible, because it often explains a failure: a path like `/var/lib/data`, a folder your `[storage] keep` lists like `data/uploads`, an address like `127.0.0.1` or `localhost:6379`, and a common setting word like `disable`, `production` or `warning`. Any other word stays hidden, because a password is often a plain word, and so do a hostname and a web address, whose path can be the secret. ## Variables at build time and run time A variable you set is there both when the app builds and when it runs. Your install, build and migrate steps get the same variables your app gets, including the keys ox provides, except `PORT`. A change goes live at the next deploy. ox sets no `CI` variable, just as a Docker build sets none. ## 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**. Pick a version, press **Compare with now**, then **Restore this version**. From a terminal: Go back to an older version: ```sh ox vars history shop ox vars restore shop 12 ``` The restore shows what it changes and asks first. `--yes` skips the question. A [rollback](https://console.t12.sorv.dev/docs/operate/rollback#variables) 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: Keep two keys in production: ```sh ox vars production-only shop STRIPE_SECRET_KEY SMTP_PASSWORD ``` What the mark does: - New staging copies start without these keys. - New previews take the staging copy's own value of each key when it has one, and run without the key when it has none. A preview is made on a push, so this is how an app that needs the key at start still runs. - `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. When a copy would lose it, the lock asks first and names those copies. - Turning the mark off copies the key into new copies again. It does not put it back in the copies that lost it. - A key a test account names is not left empty on staging. Staging gets a value ox makes in its place: a `tester@` address or a random password. The copy's Variables tab names the keys it does not get. Set the copy's 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. Turning on tested deploys marks production's live keys this way by itself when it makes staging, and the Tests page says how. A mark does not undo two things: - 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 that holds an older value of production's, from before production changed it, keeps it, and a staging copy passes it to new previews. Unset it on that copy. If removing a key from a copy 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](https://console.t12.sorv.dev/docs/troubleshooting#build-failed) shows the whole message. - `STRIPE_KEY=… on the command line would stay in your shell history`: run `ox vars set shop STRIPE_KEY` and 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-deploy` or **Save, deploy later**. The Variables tab then says **Saved, not live until the next deploy**; press its **Deploy latest**, or run `ox deploy`, to make it live. Last updated 2026-10-08. The page as HTML: https://console.t12.sorv.dev/docs/operate/variables