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:
lizard secrets set DATABASE_URL=postgres://… LOG_LEVEL=info
lizard secrets set API_KEY=sk_live_… --service apiAdd --global to write to project scope (every service in the project sees it):
lizard secrets set NODE_ENV=production --globalListing, deleting, importing
lizard secrets list # current scope
lizard secrets list --show # reveal values
lizard secrets delete OLD_KEY ANOTHER_KEY # variadic; add -y in scripts
cat .env | lizard secrets import # KEY=value lines from stdinsecrets import reads plain KEY=value lines and keeps quotes as part of the value. secrets delete asks for confirmation; pass -y when it runs outside a terminal.
See the lizard secrets command reference 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.
Every running Postgres and Redis addon’s DATABASE_URL and REDIS_URL already reach every service in the project, at the lowest precedence. To pick a specific addon or use its private URL, bind it per consumer with a reference instead of --global:
lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service api
lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service workerIf the addon’s values change, each consumer picks up the new value on its next deploy or variable change.
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 varsSo app secrets override project secrets, and platform variables (PORT, LIZARD_SERVICE_NAME, LIZARD_PROJECT_ID, LIZARD_PUBLIC_DOMAIN, LIZARD_PRIVATE_DOMAIN) are applied last and cannot be shadowed. PORT is set only when the service has an HTTP port. Setting PORT yourself changes the port Lizard routes to on the next deploy, unless the port was set with lizard port.
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:
- Changing a
VITE_*orNEXT_PUBLIC_*value starts a rebuild of the affected running services right away (every running service for--global). - Lizard bakes these values in as
ENVlines for GitHub services (the Dockerfile lizardpack generates, or the repo’s root Dockerfile) and for Node services built frombuildCommand/startCommand. A service with a customdockerfilePath, or one uploaded withlizard upwithout those commands, gets no Lizard variables at build time; no service gets build args exceptCOMMIT_SHA. For public values in those cases, commit them in.env.production, set a build command, or write them into your Dockerfile.
See Build Pipeline → rebuild triggers.
Verifying
Shell access to services (lizard ssh) is turned off, so check the values another way:
lizard logs --build --service api # warns about every empty ${{…}} reference
lizard secrets list --service api --show # stored values
lizard secrets list --service api --ref # reference templates you can useIf a value you expected isn’t there, see Troubleshooting → a secret or reference isn’t showing up.
Local development
Run a command locally with the service’s project and service secrets injected:
lizard run --service api -- node scripts/seed.jsValues arrive as stored: ${{…}} references stay literal text, and platform variables (PORT, LIZARD_*) are not injected. For a script that needs the database, run it with the addon’s real connection values:
lizard run --service postgres -- node scripts/seed.jsSee also
- Cross-Service References — read one service’s values from another without copy-pasting credentials.
lizard secrets— the full command reference.
Updated