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