# Variables & Secrets

Lizard stores configuration as **secrets**. They come in two scopes — **service** (the default) and **project** (`--global`) — and merge into your app's environment in a defined precedence order. There is no workspace-level global scope.

## Setting variables

`lizard secrets set` is variadic — set one or many at once. By default it writes to the **linked service**:

```bash
lizard secrets set DATABASE_URL=postgres://… LOG_LEVEL=info
lizard secrets set API_KEY=sk_live_… --service api
```

Add `--global` to write to **project** scope (every service in the project sees it):

```bash
lizard secrets set NODE_ENV=production --global
```

## Listing, deleting, importing

```bash
lizard secrets list                       # current scope
lizard secrets list --show                # reveal values
lizard secrets delete OLD_KEY ANOTHER_KEY # variadic
cat .env | lizard secrets import          # import dotenv from stdin
```

See the [`lizard secrets` command reference](https://lizard.build/docs/cli/secrets) for every flag.

## Scoping

| Scope | Command | Stored as | Visible to |
|-------|---------|-----------|------------|
| **Service** (default) | `lizard secrets set K=v --service <svc>` | `secrets.services[<svc>]` | that service only |
| **Project** (global) | `lizard secrets set K=v --global` | `secrets.shared` | every service in the project |

**Default to service scope.** A compromised service can read its own environment — broader scope means more credentials exposed for no reason. Reserve `--global` for non-secret, provably-public values like `LOG_LEVEL`, `NODE_ENV`, feature flags, or a frontend `SENTRY_DSN`. If you're unsure whether a value is a secret, treat it as one and scope it to the service.

For shared resources like a database, **don't** put the DSN in `--global` — bind it per consumer with a [reference](https://lizard.build/docs/variables/references):

```bash
lizard secrets set DATABASE_URL='${{postgres.DATABASE_URL}}' --service api
lizard secrets set DATABASE_URL='${{postgres.DATABASE_URL}}' --service worker
```

Rotation still happens once on the addon; every reference updates.

## Precedence

When the same key is defined in multiple places, the **last writer wins**:

```
addon-issued env  <  project secrets  <  project env  <  app env  <  app secrets  <  platform vars
```

So **app secrets override project secrets**, and **platform variables** (`PORT`, `LIZARD_SERVICE_NAME`, `LIZARD_PROJECT_ID`, `LIZARD_PUBLIC_DOMAIN`) are applied last and cannot be shadowed.

## Build-time vs. runtime

Most variable changes apply with **no rebuild** — the service restarts to pick them up, which takes a few seconds. The exceptions are build-time values baked into the image:

- `VITE_*` and `NEXT_PUBLIC_*` changes **force a rebuild** on the next deploy.

See [Build Pipeline → rebuild triggers](https://lizard.build/docs/concepts/build-pipeline#what-triggers-a-rebuild).

## Verifying

Confirm what a running service actually sees:

```bash
lizard ssh --service api -- env
```

If a value you expected isn't there, see [Troubleshooting → a secret or reference isn't showing up](https://lizard.build/docs/variables/troubleshooting/reference-not-applied).

## Local development

Run a command **locally** with the service's project + service secrets injected (handy for scripts and migrations):

```bash
lizard run --service api -- node scripts/seed.js
```

See [`lizard run` vs. `lizard ssh`](https://lizard.build/docs/cli/run#run-vs-ssh).

## See also

- [Cross-Service References](https://lizard.build/docs/variables/references) — read one service's values from another without copy-pasting credentials.
- [`lizard secrets`](https://lizard.build/docs/cli/secrets) — the full command reference.
