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:
| Action | Web UI | CLI |
|---|---|---|
| Create / update | Project → Secrets → New | gws secret set NAME |
| List | Project → Secrets tab | gws secret list |
| Delete | Project → Secrets → row menu | gws secret delete NAME |
| Required permission | can_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
| Type | What it is | When to use |
|---|---|---|
env (default) | Container env var | Connection strings, API keys, feature flags |
file | Mounted into the pod at --mount-path | TLS certs, JSON service account keys, .env files |
registry | Docker-registry credential pair | Pulling private images (e.g. ghcr.io) |
Adding a secret from the UI
- Open the project's Secrets tab.
- Click New secret.
- Pick the type (
env,file,registry). - Set the scope — global, or pin to a service / worktree / profile.
- Enter the value (or upload a file). Save.
- Redeploy affected services so the new value is picked up —
gws upor 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
- Create a
prod-secretsteam with onlycan_change_secretson the production project. - Add the on-call rotation to that team.
- Everyone else stays read-only on prod and can't see (or set) values.
Rotating a leaked credential
- Set the new value (
gws secret set NAME --stdin) — same name + scope overwrites. - Trigger a redeploy so pods pick up the new value.
- 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
gws secret— full CLI reference- Teams & permissions —
can_change_secretslives there - Service tokens — let CI rotate secrets without a human