# Secrets Configuration

import File from '@site/src/components/File'
import Intro from '@site/src/components/Intro'

<Intro>
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.
</Intro>

## 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`, `!store.get`, and `atmos.Store`
against a `secret: true` store are errors ("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`/`azure-kv`/`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 title="atmos.yaml">
```yaml
stores:
  # Regular store (machine outputs) — accessed with !store
  terraform-outputs:
    kind: aws/ssm
    options:
      region: us-east-1

  # AWS SSM Parameter Store secret store (SecureString) — accessed with !secret
  ssm-secrets:
    kind: aws/ssm
    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:
    kind: aws/asm
    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:
    kind: azure/keyvault
    secret: true
    identity: azure/prod-subscription
    options:
      vault_url: https://myvault.vault.azure.net/

  # GCP Secret Manager
  gcp-secrets:
    kind: gcp/secretmanager
    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
```
</File>

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

<dl>
  <dt>`secret`</dt>
  <dd>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.</dd>

  <dt>`kind`</dt>
  <dd>Optional `cloud/thing` backend selector (e.g. `aws/ssm`, `aws/asm`, `hashicorp/vault`). Equivalent to the legacy `type`; when both are set, `kind` wins.</dd>

  <dt>`identity`</dt>
  <dd>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.</dd>
</dl>

#### 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:

<dl>
  <dt>`mode`</dt>
  <dd>`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).</dd>

  <dt>`token`</dt>
  <dd>Service-account token. Falls back to the `OP_SERVICE_ACCOUNT_TOKEN` environment variable.</dd>

  <dt>`connect_host` / `connect_token`</dt>
  <dd>1Password Connect server URL and API token. Fall back to `OP_CONNECT_HOST` / `OP_CONNECT_TOKEN`.</dd>

  <dt>`vault`</dt>
  <dd>Optional default vault. When set, vault-relative references (`Datadog/api_key`) are expanded to `op://<vault>/Datadog/api_key`.</dd>
</dl>

:::note Binary size
The native Service Account integration embeds the 1Password SDK core (~9&nbsp;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 (`sops/aws-kms`,
`sops/gcp-kms`, `sops/azure-kv`) authenticate to the cloud KMS using an Atmos
[`identity`](#sops-identity) — the same identity model as Track-1 stores and `terraform plan` — so you
do **not** need to wrap commands in `atmos auth exec` or export cloud credentials.
[`atmos secret keygen`](/cli/commands/secret/keygen) generates the age key and writes it to whichever
sink `spec.age_key` selects (KMS kinds bring externally-managed keys, so they are not generated).

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 title="atmos.yaml">
```yaml
secrets:
  providers:
    dev-sops:
      kind: sops/age          # or: sops/aws-kms, sops/gcp-kms, sops/azure-kv, sops/gpg
      # identity: aws/prod-admin        # KMS kinds only; auth identity for the cloud KMS (see below)
      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
```
</File>

The SOPS provider `spec` accepts:

<dl>
  <dt>`file`</dt>
  <dd>Path to the encrypted file (a Go template with `{{ .atmos_stack }}`/`{{ .atmos_component }}` in scope). Relative to the directory Atmos runs in.</dd>

  <dt>`age_key`</dt>
  <dd>
    Where the age **private key** lives (`sops/age` only; KMS kinds ignore it). An object:

    <dl>
      <dt>`store`</dt>
      <dd>`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.</dd>
      <dt>`path`</dt>
      <dd>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.</dd>
      <dt>`value`</dt>
      <dd>Optional inline key material (highest precedence). Best populated from a YAML function — `!env`, `!exec`, or `!store.get` — rather than committed plaintext.</dd>
    </dl>

    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`.
  </dd>

  <dt>`age_key_file`</dt>
  <dd>Back-compat shorthand for `age_key: { store: file, path: <this> }` — a path to the age private key file (`~`/`$ENV` expanded).</dd>

  <dt>`age_recipients`</dt>
  <dd>Optional age **public** recipients used when creating a fresh file. When omitted, recipients come from the matching creation rule in the nearest `.sops.yaml`.</dd>

  <dt>`recipients_file`</dt>
  <dd>Where `atmos secret keygen` records this vault's public recipient. Defaults to `.sops.yaml` (a creation rule) at the Atmos base path.</dd>
</dl>

#### Cloud KMS identity {#sops-identity}

The cloud-KMS kinds (`sops/aws-kms`, `sops/gcp-kms`, `sops/azure-kv`) wrap the SOPS data key with a
cloud KMS, so encrypt/decrypt require cloud credentials. Atmos obtains them from an
[auth identity](/cli/configuration/auth/identities) — the same way it does for `terraform plan` and
Track-1 stores — and injects them into the in-process KMS calls. No `atmos auth exec` wrapper or
ambient cloud credentials are needed. The cloud is inferred from the file's master keys at runtime; the
`identity` is the only knob (`sops/age` and `sops/gpg` use local keys and ignore it).

<dl>
  <dt>`identity`</dt>
  <dd>Optional auth identity (a provider-level field, sibling to `kind`) used to authenticate to the cloud KMS for KMS-backed kinds. Resolved with the following precedence (highest first):</dd>
</dl>

1. The provider's own `identity` (`secrets.providers.<name>.identity`).
2. The `--identity` flag / `ATMOS_IDENTITY` environment variable on the `atmos secret` command.
3. The stack/component **effective identity** (the same identity `terraform plan` would use for that component).

If none resolves, Atmos falls back to the ambient cloud credential chain (environment, shared config,
instance metadata), preserving prior behavior.

:::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 title="stacks/deploy/dev.yaml">
```yaml
secrets:
  providers:
    dev-sops:
      kind: sops/age
      spec:
        file: secrets/dev.enc.yaml
```
</File>

…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 title="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
```
</File>

## 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 title="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
```
</File>

Each declaration accepts:

<dl>
  <dt>`description`</dt>
  <dd>Human-readable description of the secret.</dd>

  <dt>`store`</dt>
  <dd>Name of a `secret: true` store (track 1) this secret resolves from. Set exactly one of `store` or `sops`.</dd>

  <dt>`sops`</dt>
  <dd>Name of a `secrets.providers` SOPS provider (track 2) this secret resolves from. Set exactly one of `store` or `sops`.</dd>

  <dt>`reference`</dt>
  <dd>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.</dd>

  <dt>`required`</dt>
  <dd>When `true`, [`atmos secret validate`](/cli/commands/secret/validate) fails if the secret is not initialized.</dd>

  <dt>`scope`</dt>
  <dd>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).</dd>
</dl>

### 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:

<dl>
  <dt>`instance` (default)</dt>
  <dd>One value per component instance. Path: `{prefix}/{stack}/{component}/{NAME}`.</dd>

  <dt>`stack`</dt>
  <dd>One value per stack, shared by every component in it. Path: `{prefix}/{stack}/{NAME}`. Derived automatically for declarations in a top-level `secrets:` block.</dd>

  <dt>`global`</dt>
  <dd>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.</dd>
</dl>

### 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 title="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
```
</File>

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/component#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/component#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)
