# Secret environment variables

A secret variable holds a credential, such as an API key, a token or a password. Your app receives it as an ordinary environment variable, but once it is saved nobody can read the value back: not in the console, the HTTP API or the `nimbus` CLI. You can only replace it or delete it. Keep your own copy in a password manager.

Secret values are never stored in the platform's own database, and the audit log records which keys changed, never their values.

## Plain and secret variables

| | Plain variable | Secret variable |
|---|---|---|
| Console (**Resources → Environment Variables**) | Value shown | Masked, with a **Secret** badge |
| `GET /api/v1/apps/:id/env` | `value` | `"value": null, "secret": true` |
| `nimbus env` | Value shown | Masked |
| `nimbus env get KEY` | The full value | Refused |
| Claude connector | Value, unless it looks like a credential | Masked |
| Delivered to web, workers, release command and one-off runs | Yes | Yes |

A key is either plain or secret, never both. Your code reads both kinds the same way, with no changes needed.

## Adding and changing secrets

On the app's **Resources** tab, under **Environment Variables → Secrets**:

- **Add secret**: enter a **Key** (such as `API_KEY`) and a **Value**, then **Save secret**.
- **Replace value**: set a new value for an existing key. The old value can't be recovered.
- **Delete secret**: removes the key from the app.
- **Make a plain variable secret**: in the environment editor, the lock icon next to a plain variable (**Convert … to a write-only secret**) moves its value into secrets. From then on it is write-only.

From the CLI, read the value from standard input so it doesn't end up in your shell history:

```sh
printf %s "$STRIPE_KEY" | nimbus env set STRIPE_KEY=- --secret
nimbus env unset STRIPE_KEY
```

The HTTP API sets, replaces or promotes a secret with `PUT /api/v1/apps/:id/env/secrets/:key`, and removes any variable with `DELETE /api/v1/apps/:id/env/:key`.

### Database URLs and other managed secrets

[Dependencies](dependencies.md) inject their credentials as managed secrets: a managed database's `DATABASE_URL` and `DATABASE_PASSWORD`, a bucket's `S3_SECRET_ACCESS_KEY`, the AI gateway's API key. They show a **Managed** badge and can't be replaced or deleted by hand; rename or detach them from the dependency instead. A managed database's `DATABASE_URL` contains its password, so it is listed with no value. To see it, open the database's page under **Storage → Databases** and show the password.

## When a change takes effect

- On an app with running instances, saving or deleting a secret restarts the app (the console asks you to confirm first). On an app that has never been deployed, the change is picked up by the first deploy. Saving several secrets restarts once per secret.
- The restart is a new release, and the change is applied only once that release is healthy. The API answers with the release it created and an `applies` field (`now` or `next_deploy`).
- `nimbus env set` and `nimbus env unset` follow the restart and end with `Applied: <app> is running with the change.`, or, if the new instances never became ready, `Not applied: the restart failed (<reason>).` with a non-zero exit while the previous release keeps serving. `--no-wait` returns at once with the release ID.
- A failed change is not rolled back: the value is saved, and the next change or deploy tries again.

## Staging and preview environments

An app's secrets belong to production only. Staging and preview environments never receive them. Each environment has its own secrets, set per environment (`nimbus env set KEY=- --secret --env staging`), and starts with a copy of the app's **Defaults for other environments**, such as test-mode API keys. You'll find that list, collapsed, inside the Secrets section. Changing a default later leaves existing environments as they are, and changing an environment's secret restarts only that environment.

## Limits

- Secrets are not for static sites. A static site has no runtime, so its variables are only available at build time, and anything the bundler inlines ships to the browser as public text.
- Secret values are hidden from the console, API and CLI, but your own app can read them. Anyone who can run commands in your app (for example with `nimbus run`) can print them.
