Skip to main content

Environment Variables & Secrets

Anything sensitive — database passwords, API keys, signed tokens — belongs in secrets, not in gws.json. GetWebstack stores them encrypted server-side, scopes them per service / worktree / profile, and injects them into pods at deploy time. Plaintext values are write-only — once stored, no UI or API call ever returns them.

This page covers managing them as a team. For the full CLI reference, see gws secret.

UI vs. CLI​

Both surfaces hit the same API and the same encrypted store:

ActionWeb UICLI
Create / updateProject → Secrets → Newgws secret set NAME
ListProject → Secrets tabgws secret list
DeleteProject → Secrets → row menugws secret delete NAME
Required permissioncan_change_secrets (write); can_read (list)Same

You can mix freely — set a secret in the UI, list it from the CLI, edit it back in the UI. They see the same data.

Scope: where a secret applies​

Every secret is keyed on (name, scope, service, worktree, profile). That tuple lets you ship the same DATABASE_URL everywhere by default, then override it for one service or one worktree without copy-paste:

DATABASE_URL global → applies to every service
DATABASE_URL service=api → overrides global for `api`
DATABASE_URL service=api worktree=feature-payments → only on that branch's namespace
DATABASE_URL service=api profile=ci → only when running with --profile ci

Most specific match wins. If nothing matches, the global value is used.

Types​

TypeWhat it isWhen to use
env (default)Container env varConnection strings, API keys, feature flags
fileMounted into the pod at --mount-pathTLS certs, JSON service account keys, .env files
registryDocker-registry credential pairPulling private images (e.g. ghcr.io)

Adding a secret from the UI​

  1. Open the project's Secrets tab.
  2. Click New secret.
  3. Pick the type (env, file, registry).
  4. Set the scope — global, or pin to a service / worktree / profile.
  5. Enter the value (or upload a file). Save.
  6. Redeploy affected services so the new value is picked up — gws up or Redeploy in the UI.

The values field is write-only and clears on save. To rotate, set a new value with the same name + scope; the old value is overwritten.

Adding a secret from the CLI​

# Interactive (recommended — value never hits shell history or `ps`)
gws secret set DATABASE_URL --service api

# CI / AI flows — read from stdin
echo "$DATABASE_URL" | gws secret set DATABASE_URL --service api --stdin

# Worktree-scoped override
gws secret set DATABASE_URL --service api --worktree feature-payments --stdin

# File-mounted
gws secret set TLS_CERT --type file --mount-path /etc/tls/cert.pem --stdin < cert.pem

# Private registry credentials
echo "$GHCR_PAT" | gws secret set ghcr-pull \
--type registry --registry-hostname ghcr.io --username my-org --stdin

See gws secret for every option.

Listing & auditing​

Listing never reveals values — only metadata (name, scope, type, service, worktree, profile, last updated):

gws secret list # all secrets in the project
gws secret list --service api # service-scoped only
gws secret list --json # machine-readable

The UI shows the same metadata in a sortable table, plus the user/token that last touched each secret — handy for audit.

Team workflows​

A few patterns that tend to come up:

Shared dev secrets, isolated worktree overrides​

Set the dev defaults globally; let each developer override on their own worktree without affecting anyone else:

# Owner sets the team-wide default
gws secret set STRIPE_KEY --service api --stdin < dev_key.txt

# A developer overrides for their feature branch
gws secret set STRIPE_KEY --service api --worktree feature-billing --stdin < my_test_key.txt

Production secrets gated to one team​

  1. Create a prod-secrets team with only can_change_secrets on the production project.
  2. Add the on-call rotation to that team.
  3. Everyone else stays read-only on prod and can't see (or set) values.

Rotating a leaked credential​

  1. Set the new value (gws secret set NAME --stdin) — same name + scope overwrites.
  2. Trigger a redeploy so pods pick up the new value.
  3. Revoke the old credential at the source.

What ends up in gws.json?​

Nothing sensitive. gws.json references secrets by name (e.g. via envFrom in a service's manifests/ folder) — it never holds values. This makes the file safe to commit.

See also​