gws secret
gws secret stores values that don't belong in gws.json — database passwords, API keys, tokens — encrypted in the API and injected into pods on deploy.
The project is resolved from the git remotes of the current directory; if there is no remote registered with the API, the command exits with ENO_PROJECT_KEY and tells you to run gws config import first. All subcommands require authentication (gws login).
Usage
gws secret <subcommand> [name] [options]
| Subcommand | Description |
|---|---|
set | Create or update a secret (interactive prompt by default) |
list | List secret metadata for the current project (values are never returned) |
delete | Delete a secret by name + scope |
set
Create or update a secret. Upserts on the key (name, scope, service, worktree, profile) — running set twice with the same scope updates the existing value rather than creating a duplicate.
The default value source is an interactive TTY prompt so the secret never appears in shell history or ps. --stdin is the no-history path for scripts and CI; --value works but emits a stderr warning.
Usage
# Recommended — interactive prompt
gws secret set DATABASE_URL --service api
# CI / AI-friendly — read from stdin
echo "$DATABASE_URL" | gws secret set DATABASE_URL --service api --stdin
# One-off, value visible in history (warned)
gws secret set FEATURE_FLAG --value enabled
# Worktree-scoped override
gws secret set DATABASE_URL --service api --worktree feature-payments --stdin
# File-mounted secret
gws secret set TLS_CERT --type file --mount-path /etc/tls/cert.pem --stdin
# Image-pull credential for a private registry
echo "$REGISTRY_PASSWORD" | gws secret set ghcr-pull \
--type registry --registry-hostname ghcr.io --username myorg --stdin
Output:
✓ Created secret "DATABASE_URL"
(Or ✓ Updated secret "DATABASE_URL" when an existing secret with the same scope is overwritten.)
Arguments and options
| Argument / option | Description |
|---|---|
<name> | Secret name. Required. |
--service <name> | Scope the secret to one service. Validated against the API's service list — unknown service names are rejected. Omit for a project-wide (global) secret. |
--worktree <name> | Scope the secret to one worktree. Defaults to unscoped. |
--profile <name> | Scope the secret to a specific configuration profile. Defaults to unscoped. |
--type <env|file|registry> | Storage shape. env (default) injects the value as a container env var. file mounts it into the pod (requires --mount-path). registry is a Docker-registry credential pair (requires --username and --registry-hostname). |
--stdin | Read the value from stdin. Use this for scripts, CI, and AI flows. |
--value <value> | Pass the value inline. Discouraged — visible in shell history and ps. Emits a stderr warning. |
--mount-path <path> | Path inside the pod to mount the secret at. Required for --type file. |
--username <name> | Registry username. Required for --type registry. |
--registry-hostname <host> | Registry hostname (e.g. ghcr.io). Required for --type registry. |
If you provide both --value and --stdin, --value wins. An empty value is rejected with ECONFIG_INVALID.
list
List secret metadata for the current project. Plaintext values are never returned — only name, scope, type, service, worktree, and profile.
Usage
gws secret list # all secrets
gws secret list --service api # only service-scoped secrets for `api`
gws secret list --worktree feature-payments
gws secret list --json # machine-readable output
Output:
NAME TYPE SCOPE SERVICE WORKTREE PROFILE
------------ ---- ------- ------- ---------------- -------
DATABASE_URL env service api - -
DATABASE_URL env service api feature-payments -
TLS_CERT file global - - -
With --json, the same information is emitted as a JSON array of secret-metadata objects, suitable for piping into other tools.
Arguments and options
| Argument / option | Description |
|---|---|
--service <name> | Filter to secrets scoped to a specific service. |
--worktree <name> | Filter to secrets scoped to a specific worktree. |
--profile <name> | Filter to secrets scoped to a specific profile. |
--json | Emit a JSON array of metadata objects instead of the table. |
When no secrets match, (no secrets) is printed (or [] with --json).
delete
Delete a secret. Resolves by name + scope and deletes by API ID; the scope flags must match the ones the secret was created with.
Usage
gws secret delete <name>
gws secret delete <name> --service api
gws secret delete <name> --service api --worktree feature-payments
Output:
✓ Deleted secret "DATABASE_URL"
If no secret matches the supplied name + scope, the command exits with ENO_PROJECT_KEY and the message secret "<name>" not found.
Arguments and options
| Argument / option | Description |
|---|---|
<name> | Secret name. Required. |
--service <name> | Service scope of the secret to delete. Omit for a global secret. |
--worktree <name> | Worktree scope of the secret to delete. |
--profile <name> | Profile scope of the secret to delete. |
Why interactive-by-default?
Secret leakage via shell history was the most common foot-gun in the wizard era. The interactive prompt is the default; --value exists for parity but warns; --stdin is the no-history path that AI agents and CI should use.
If you need to migrate a .env file in bulk, the AI's /gws-setup skill loops over each line and calls gws secret set <KEY> --service <svc> --stdin; you don't need to script it yourself.
See also
- Zero-Config Setup with AI
gws config— for non-secret configurationgws up— secrets are injected at deploy time