Skip to main content

atmos secret

The atmos secret command group provides a GitOps-friendly, multi-cloud workflow for managing secrets. Secrets are declared in your stack configuration (committed to git) and their values are stored in a cloud secret backend (AWS SSM, AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, GCP Secret Manager) or a SOPS-encrypted file. Declared secrets are resolved at runtime with the !secret YAML function and are automatically masked in all output.

atmos secret --help

Usage

atmos secret <subcommand> [flags]

Subcommands

init
Provision declared secrets, prompting for any that are missing.
set (alias add)
Set a declared secret's value (create or update).
get
Retrieve a declared secret's value.
delete (alias rm)
Remove a declared secret's value from its backend.
list
List declared secrets and their initialization status.
pull
Download declared secrets to a local file for development.
push
Upload secret values from a local file (all keys must be declared).
import
Import secret values from a file, skipping undeclared keys.
validate
Validate that all required declared secrets are initialized.
exec
Run a command with declared secrets injected as environment variables.
shell
Launch an interactive shell with declared secrets in the environment.

How It Works

  1. Configure a secret backend in atmos.yaml. A store becomes a secret backend by setting secret: true:

    stores:
    app-secrets:
    type: aws-secrets-manager
    secret: true
    identity: aws/prod-secrets
    options:
    region: us-east-1
    prefix: atmos/secrets
  2. Declare secrets in your stack/component config under secrets.vars:

    components:
    terraform:
    api:
    secrets:
    vars:
    DATADOG_API_KEY:
    description: "Datadog API key"
    store: app-secrets
    required: true
    vars:
    datadog_api_key: !secret DATADOG_API_KEY
  3. Provision values with the CLI:

    atmos secret set DATADOG_API_KEY --stack=prod --component=api
    atmos secret list --stack=prod --component=api

Secret scopes

Every secret has an explicit scope that controls where its value is stored. Two scopes are available (a cross-stack global scope is planned):

  • stack — declared at the top level of a stack (a secrets: block that is a sibling of vars:). Stored once per stack and shared by every component instance. Rotate it once and every instance sees the new value.
  • instance (default) — declared under a component. Stored per component instance.

Scope is derived from where the secret is declared, not written by hand:

# stack manifest (top level): SHARED_TOKEN is stack-scoped — one value for the whole stack
secrets:
vars:
SHARED_TOKEN: { sops: default }

components:
terraform:
api:
secrets:
vars:
# Re-declaring SHARED_TOKEN under a component pulls it to INSTANCE scope for `api` only,
# which now owns its own value. Other components keep using the shared stack value.
SHARED_TOKEN: { sops: default }
# API_KEY is declared only here → always instance-scoped.
API_KEY: { sops: default }

The rule is one-way: a stack secret can be pulled down to instance scope by re-declaring it under a component, but an instance-declared secret can never become stack-scoped. The override is opt-in — an instance only carries its own value if it declares the secret; setting an instance value for a secret that is still stack-scoped at that component is a hard error (declare it under the component first, or omit --component to set the shared stack value).

Each backend maps scope to its native primitive and rejects scopes it can't represent. For SOPS, the file is derived from scope under spec.path (default secrets/): stack → secrets/<stack>.enc.yaml, instance → secrets/<stack>.<instance>.enc.yaml. A changed SOPS file is treated as an automatic dependency by atmos describe affected, so rotating a secret marks the components that consume it.

Interactive prompts

When --stack or --component is missing and you are in an interactive terminal, Atmos prompts you to choose one (the same built-in selector used elsewhere). atmos secret set and atmos secret init with no arguments walk you through stack → component → secret. In CI / non-interactive shells these prompts are disabled and a missing required flag returns the standard error, so scripts are unaffected.

Masking and Inspection

atmos secret get, pull, and push always retrieve real values to do their job; the values are masked in display output unless you pass --mask=false.

Inspection commands (describe and the list family) resolve !secret to <MASKED> without contacting the backend when masking is enabled (the default) — so you can inspect a stack's shape with no cloud credentials. Pass --mask=false to retrieve and reveal the real values.

See Also