VariablesVariables & Secrets

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 api

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

lizard secrets set NODE_ENV=production --global

Listing, 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 stdin

secrets 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

ScopeCommandStored asVisible to
Service (default)lizard secrets set K=v --service <svc>secrets.services[<svc>]that service only
Project (global)lizard secrets set K=v --globalsecrets.sharedevery 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.

Each service already gets one DATABASE_URL and one REDIS_URL from the project’s Postgres and Redis addons (running or paused), at the lowest precedence. With several addons of one type, which one you get is not defined. 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 worker

If 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 vars

So 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. A PORT variable you set moves routing only on a service whose port was never set. A service created by lizard up, or given a port with --port, lizard port, containerPort or the dashboard, keeps that port, and Lizard overwrites your PORT with it. To change the port, run lizard port <n> and redeploy. See which port Lizard routes to.

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_* or NEXT_PUBLIC_* value with lizard secrets set or in a service’s settings starts a rebuild of the affected running services right away (every running service for --global). An edit on the dashboard’s project Secrets page only restarts them; run lizard redeploy after it.
  • Lizard bakes these values in as ENV lines for GitHub services (the Dockerfile lizardpack generates, or the repo’s root Dockerfile) and for Node services built from buildCommand / startCommand. A service with a custom dockerfilePath, or one uploaded with lizard up without those commands, gets no Lizard variables at build time; no service gets build args except COMMIT_SHA. For public values in those cases, commit them in .env.production or write them as ENV lines in your Dockerfile. For a Node app uploaded without its own Dockerfile, setting a build command also works.

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    # addon keys and each service's LIZARD_* / PORT keys

--ref does not list another service’s own variables and secrets. References to them work; check the keys with lizard secrets list --service <target>.

If 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.js

Values 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.js

See what lizard run injects.

See also

Updated