# Secrets Configuration

Atmos secrets provide a GitOps-friendly workflow for managing human-provisioned configuration secrets (API keys,
tokens, passwords). Secrets are **declared** in stack config (committed to git) and their **values** live in a
cloud secret backend or a SOPS-encrypted file. They are provisioned with the [`atmos secret`](/cli/commands/secret/usage)
CLI and resolved at runtime with the [`!secret`](/functions/yaml/secret) YAML function.

## Stores vs Secrets

Atmos exposes two user-facing concepts that share a single backend layer (the store registry):

- **[Stores](/cli/configuration/stores)** — the shared backend layer for external data (e.g. Terraform
  outputs), accessed with `!store`.
- **Secrets** — human-managed configuration, declared explicitly and accessed only with `!secret` and the
  `atmos secret` CLI.

A store becomes a **secret backend** by setting `secret: true`. `!store` against a `secret: true` store is an
error ("use `!secret`"), which makes the declarative registry mandatory-by-construction.

## Backend Architecture (Two Tracks)

| | Track 1 — store-backed | Track 2 — non-store |
|---|---|---|
| Backends | AWS SSM, AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, GCP Secret Manager, 1Password | SOPS (`age`/`aws-kms`/`gcp-kms`/`gpg`) |
| Shape | Remote key-value store | Git-committed encrypted file |
| Config | `stores:` entry with `secret: true` | `secrets.providers:` entry |

### Track 1 — Secret stores (`secret: true`)

Configure secret stores in `atmos.yaml` alongside regular stores. A `secret: true` store always writes the
sensitive at-rest variant (e.g. SSM `SecureString`).

**File:** `atmos.yaml`

```yaml
stores:
  # Regular store (machine outputs) — accessed with !store
  terraform-outputs:
    type: aws-ssm-parameter-store
    options:
      region: us-east-1

  # AWS SSM Parameter Store secret store (SecureString) — accessed with !secret
  ssm-secrets:
    type: aws-ssm-parameter-store
    secret: true
    identity: aws/prod-admin
    options:
      region: us-east-1
      prefix: /atmos/secrets

  # AWS Secrets Manager — structured/JSON secrets, rotation, larger values
  asm-secrets:
    type: aws-secrets-manager
    secret: true
    identity: aws/prod-secrets
    options:
      region: us-east-1
      prefix: atmos/secrets

  # HashiCorp Vault (KV v2)
  vault-secrets:
    type: hashicorp-vault
    secret: true
    options:
      url: https://vault.example.com
      mount: secret

  # Azure Key Vault
  azure-secrets:
    type: azure-key-vault
    secret: true
    identity: azure/prod-subscription
    options:
      vault_url: https://myvault.vault.azure.net/

  # GCP Secret Manager
  gcp-secrets:
    type: google-secret-manager
    secret: true
    options:
      project_id: my-project

  # 1Password (`secret: true` is implied) — reference-based, full CRUD
  op:
    type: onepassword
    options:
      mode: auto            # auto | connect | service-account
```

Each secret store accepts the same options as its non-secret counterpart, plus:

- **`secret`**
  Set to 
  `true`
   to make the store a secret backend. Required for 
  `!secret`
   access; blocks 
  `!store`
   access. Only backends that encrypt values at rest may be marked secret — plaintext backends (Redis, Artifactory) are rejected at startup with an error.
- **`kind`**
  Optional 
  `cloud/thing`
   backend selector (e.g. 
  `aws/ssm`
  , 
  `aws/asm`
  , 
  `hashicorp/vault`
  ). Equivalent to the legacy 
  `type`
  ; when both are set, 
  `kind`
   wins.
- **`identity`**
  Optional auth identity (resolved via 
  [`auth`](/cli/configuration/auth/identities)
  ) used to authenticate to the backend. When omitted and the secret is resolved within a component scope, the component's effective identity is inherited.

#### 1Password

The 1Password store differs from the other Track-1 backends in three ways:

- **`secret: true` is implied.** 1Password is a secret manager, so a `type: onepassword` store is always a secret backend — you never write `secret: true`, and it cannot be used as a non-secret `!store` backend.
- **Reference-based addressing.** Instead of composing a key from the stack/component/key, each declared secret carries an explicit `op://vault/item/field` [secret reference](https://developer.1password.com/docs/cli/secret-reference-syntax/) via the [`reference`](#declaring-secrets) field. References support Go templating (`{{ .atmos_stack }}`, `{{ .atmos_component }}`) and [sprig](https://masterminds.github.io/sprig/) functions, mirroring the SOPS `spec.file` pattern.
- **Full CRUD.** `atmos secret get/set/delete/list/validate` all work. `set` writes the value to the field the reference points to, creating the item if it does not exist (created items use the **API Credential** category with a Concealed value field). `delete` removes the field, and deletes the item once its last field is removed. Service accounts cannot write to the built-in Private/Personal/Employee vault — use a shared named vault.

No `op` CLI is required. Authentication auto-selects between two backends so the same config works in local dev and CI:

- **`mode`**
  `auto`
   (default), 
  `connect`
  , or 
  `service-account`
  . In 
  `auto`
  , 
  [1Password Connect](https://developer.1password.com/docs/connect/)
   is used when its host and token are present (the typical CI/cloud setup), otherwise a 
  [Service Account](https://developer.1password.com/docs/service-accounts/)
   is used (typical local dev).
- **`token`**
  Service-account token. Falls back to the 
  `OP_SERVICE_ACCOUNT_TOKEN`
   environment variable.
- **`connect_host` / `connect_token`**
  1Password Connect server URL and API token. Fall back to 
  `OP_CONNECT_HOST`
   / 
  `OP_CONNECT_TOKEN`
  .
- **`vault`**
  Optional default vault. When set, vault-relative references (
  `Datadog/api_key`
  ) are expanded to 
  `op://<vault>/Datadog/api_key`
  .

:::note Binary size
The native Service Account integration embeds the 1Password SDK core (~9 MB) in the Atmos binary. It is pure-Go (no `op` CLI, no cgo). Connect alone adds negligible size.
:::

### Track 2 — SOPS providers

SOPS is a git-committed encrypted file edited imperatively, so it is configured under `secrets.providers`
(not `stores`). `atmos secret set/get/delete` against a SOPS provider decrypts the file, mutates the key,
and re-encrypts in place. Atmos does this **in-process** with the getsops/sops Go SDK — **no `sops` binary
is required**. For `sops/age` you need an age key. It can be declared via `spec.age_key` — from a
**file** (the default) or a **store** such as the OS `keychain` — or supplied via the
`SOPS_AGE_KEY_FILE`/`SOPS_AGE_KEY` environment variables; the KMS-backed kinds use your cloud
credentials. [`atmos secret keygen`](/cli/commands/secret/keygen) generates the key and writes it to
whichever sink `spec.age_key` selects.

The `file` path is a **Go template** with `{{ .atmos_stack }}` and `{{ .atmos_component }}` in scope (use
`{{ .atmos_component }}` to keep each component's secrets in its own file). You can define a SOPS provider
globally in `atmos.yaml`:

**File:** `atmos.yaml`

```yaml
secrets:
  providers:
    dev-sops:
      kind: sops/age          # or: sops/aws-kms, sops/gcp-kms, sops/gpg
      spec:
        file: secrets/{{ .atmos_stack }}.enc.yaml
        # age_key:                        # optional; where the age PRIVATE key lives (read + keygen sink)
        #   store: keychain               #   "file" (default) | a configured store name (e.g. keychain)
        #   path: dev-sops                #   optional; file path (file mode) or store key (default: vault name)
        # age_recipients: age1...         # optional; otherwise read from the matching .sops.yaml creation rule
```

The SOPS provider `spec` accepts:

- **`file`**
  Path to the encrypted file (a Go template with 
  `{{ .atmos_stack }}`
  /
  `{{ .atmos_component }}`
   in scope). Relative to the directory Atmos runs in.
- **`age_key`**

  Where the age **private key** lives (`sops/age` only; KMS kinds ignore it). An object:
  - **`store`**
    `file`
     (default) — a local key file — or the name of a configured 
    [store](/cli/configuration/stores)
     such as the OS 
    `keychain`
    . Reading and 
    [`atmos secret keygen`](/cli/commands/secret/keygen)
     writing both use this backend.
  - **`path`**
    Location within the backend: the key file path (file mode; supports 
    `~`
     and 
    `$ENV`
     expansion) or the store key (store mode). Optional — defaults to the 
    **sops default keys file**
     in file mode and the 
    **vault (provider) name**
     in store mode.
  - **`value`**
    Optional inline key material (highest precedence). Best populated from a YAML function — 
    `!env`
    , 
    `!exec`
    , or 
    `!store.get`
     — rather than committed plaintext.
  Precedence: `value` → `store` → `file` → `SOPS_AGE_KEY_FILE`/`SOPS_AGE_KEY`. A bare string (`age_key: <material>`) is treated as `value`. Takes precedence over `SOPS_AGE_KEY_FILE`/`SOPS_AGE_KEY`.
- **`age_key_file`**
  Back-compat shorthand for 
  `age_key: { store: file, path: <this> }`
   — a path to the age private key file (
  `~`
  /
  `$ENV`
   expanded).
- **`age_recipients`**
  Optional age 
  **public**
   recipients used when creating a fresh file. When omitted, recipients come from the matching creation rule in the nearest 
  `.sops.yaml`
  .
- **`recipients_file`**
  Where 
  `atmos secret keygen`
   records this vault's public recipient. Defaults to 
  `.sops.yaml`
   (a creation rule) at the Atmos base path.

:::tip Keep the key in the OS keychain
Configure a `keychain` store and point `age_key.store` at it to keep the private key out of files and environment variables entirely:

```yaml
stores:
  keychain:
    type: keychain

secrets:
  providers:
    dev-sops:
      kind: sops/age
      spec:
        file: secrets/dev.enc.yaml
        age_key:
          store: keychain
```

`atmos secret keygen dev-sops` writes the generated private key into the keychain; decryption reads it back automatically — no `SOPS_AGE_KEY_FILE` needed.
:::

…or scope it to a **stack** by declaring a global `secrets.providers` block at the top of a stack
manifest (it merges into every component in that stack, like global `vars`):

**File:** `stacks/deploy/dev.yaml`

```yaml
secrets:
  providers:
    dev-sops:
      kind: sops/age
      spec:
        file: secrets/dev.enc.yaml
```

…or scope it to a single **component** by declaring `secrets.providers` alongside `secrets.vars`.
A stack/component-scoped provider takes precedence over an `atmos.yaml`-level provider of the same name:

**File:** `stacks/deploy/dev.yaml`

```yaml
components:
  terraform:
    api:
      secrets:
        providers:
          dev-sops:
            kind: sops/age
            spec:
              file: secrets/dev.enc.yaml
        vars:
          GITHUB_APP_KEY:
            sops: dev-sops
```

## Declaring Secrets

Secrets are declared under a component's `secrets.vars`. Inheritance follows the standard Atmos stack
hierarchy. A "global" secret is simply a shared declaration imported wherever it is needed.

**File:** `stacks/prod/api.yaml`

```yaml
components:
  terraform:
    api:
      secrets:
        vars:
          DATADOG_API_KEY:
            description: "Datadog API key for monitoring"
            store: ssm-secrets       # references a `secret: true` store (track 1)
            required: true
          GITHUB_APP_KEY:
            description: "GitHub App private key for CI"
            sops: dev-sops           # references a SOPS provider (track 2)
            required: true
          DB_PASSWORD:
            description: "Database password from 1Password"
            store: op                # a 1Password store (track 1)
            reference: "op://{{ .atmos_stack }}/postgres/password"
            required: true
      vars:
        datadog_api_key: !secret DATADOG_API_KEY
        db_password: !secret DB_PASSWORD
```

Each declaration accepts:

- **`description`**
  Human-readable description of the secret.
- **`store`**
  Name of a 
  `secret: true`
   store (track 1) this secret resolves from. Set exactly one of 
  `store`
   or 
  `sops`
  .
- **`sops`**
  Name of a 
  `secrets.providers`
   SOPS provider (track 2) this secret resolves from. Set exactly one of 
  `store`
   or 
  `sops`
  .
- **`reference`**
  Optional backend-specific address for reference-based stores (1Password). For a 1Password store this is an 
  `op://vault/item/field`
   reference and may contain Go-template vars (
  `{{ .atmos_stack }}`
  , 
  `{{ .atmos_component }}`
  ). Name-keyed backends (AWS, Azure, GCP, Vault, SOPS) ignore it.
- **`required`**
  When 
  `true`
  , 
  [`atmos secret validate`](/cli/commands/secret/validate)
   fails if the secret is not initialized.
- **`scope`**
  Storage scope: 
  `instance`
   (default), 
  `stack`
  , or 
  `global`
  . Normally derived from declaration position — a top-level 
  `secrets:`
   block implies 
  `stack`
  , a component-level block implies 
  `instance`
   — and an explicit value must match the position. The exception is 
  `global`
  , which is honored at either position. See 
  [Secret scopes](#secret-scopes)
  .

### Secret scopes

A declaration's `scope` controls where its value is stored — and therefore who shares it. Store-backed
secrets compose their backend path from the scope:

- **`instance` (default)**
  One value per component instance. Path: 
  `{prefix}/{stack}/{component}/{NAME}`
  .
- **`stack`**
  One value per stack, shared by every component in it. Path: 
  `{prefix}/{stack}/{NAME}`
  . Derived automatically for declarations in a top-level 
  `secrets:`
   block.
- **`global`**
  One value shared by every stack and component that resolves through the same store. Path: 
  `{prefix}/{NAME}`
  . Sharing is bounded by the store's backend (account/project/prefix), which remains the isolation boundary.

### Shared (global) secrets

Declare a secret once in a catalog fragment with `scope: global`, import the fragment wherever the
secret is consumed, and every consumer computes the **same** storage path. Rotate it once with
[`atmos secret set`](/cli/commands/secret/set) and every stack sees the new value.

**File:** `stacks/catalog/secrets/shared.yaml`

```yaml
components:
  terraform:
    api:
      secrets:
        vars:
          SHARED_CLIENT_SECRET:
            description: "OAuth client secret shared by all environments"
            store: gcp-secrets
            scope: global        # path: {prefix}/SHARED_CLIENT_SECRET — same from everywhere
            required: true
```

This composes with the store's [`identity`](#track-1--secret-stores-secret-true) for a centralized secrets
account: the store points at the central backend, its identity grants access from any environment,
and `scope: global` makes every consumer resolve the same value.

:::warning One value, many consumers
`atmos secret set` and `atmos secret delete` on a `stack`- or `global`-scoped secret affect **every**
consumer of that value, not just the (stack, component) you ran the command from.
:::

### Migrating from `!store`

Marking an existing store `secret: true` switches its access from `!store` to `!secret` — but values
written by legacy `!store <store> <stack> <component> <key>` usage live at hand-built paths the new
computed coordinates don't match. Adopt each value once with
[`atmos secret import`](/cli/commands/secret/import) (like `terraform import`, it copies — the source
value is never modified or deleted), then delete the old `!store` line:

```yaml
# Before: imperative addressing — "atmos" and "shared" are raw path segments, not a real stack/component.
vars:
  client_secret: !store app-secrets atmos shared client_secret
```

```shell
# One-time adoption: copy the value to the declaration's computed coordinate.
atmos secret import SHARED_CLIENT_SECRET \
  --from-stack=atmos --from-component=shared --from-key=client_secret \
  --stack=prod --component=api
```

```yaml
# After: declarative — the legacy path appears nowhere in stack config.
secrets:
  vars:
    SHARED_CLIENT_SECRET:
      store: app-secrets
      scope: global
env:
  CLIENT_SECRET: !secret SHARED_CLIENT_SECRET
```

### Organizing declarations with `!include`

The `vars` map can be pulled in from a file for organization, and a shared file is how "global" declarations
are reused:

```yaml
components:
  terraform:
    api:
      secrets:
        vars: !include secrets/api.yaml
```

## Consuming secrets in commands and shells

Besides resolving secrets into stack config with [`!secret`](/functions/yaml/secret), you can inject a component's declared secrets straight into a subprocess environment:

- [`atmos secret exec`](/cli/commands/secret/exec) runs a command with the declared secrets set as environment variables (named after each declaration).
- [`atmos secret shell`](/cli/commands/secret/shell) launches an interactive shell with the same environment.

Both layer secrets on top of the current environment and skip uninitialized secrets with a warning. Values are passed to the child process in cleartext and are not masked in its output.

### Passing secrets to components

When a component runs (Terraform, Helmfile, Packer, Ansible, or a [custom component type](/cli/configuration/commands#custom-component-types)), reference secrets with `!secret` in the component's **`env` section**. Resolved values are injected into the tool's subprocess environment and — for Helmfile, Packer, and Ansible — kept out of the on-disk variables file, which is written in plaintext. Avoid putting secrets in `vars` for those component types. Each component guide shows the secure pattern and how to read the variable in the underlying tool:

- [Helmfile](/stacks/components/helmfile#passing-secrets-securely) — read with `requiredEnv` in `helmfile.yaml`.
- [Packer](/stacks/components/packer#passing-secrets-securely) — read with the `env()` function in the template.
- [Ansible](/stacks/components/ansible#passing-secrets-securely) — read with the `env` lookup in the playbook.
- [Custom components](/cli/configuration/commands#passing-secrets-to-custom-components) — read as `$VAR` in the command steps.

## See Also

- [`atmos secret` command](/cli/commands/secret/usage)
- [`atmos secret exec`](/cli/commands/secret/exec) / [`atmos secret shell`](/cli/commands/secret/shell)
- [`!secret` YAML function](/functions/yaml/secret)
- [Stores configuration](/cli/configuration/stores)
- [Auth identities](/cli/configuration/auth/identities)
