Introducing Secrets Management in Atmos
Atmos now has first-class secrets management: you declare the secrets each component depends on, provision their values per environment, and reference them at runtime with a single YAML function.
The Idea
One of the best things about platforms like Vercel and Heroku is how little ceremony it takes to manage an application's settings. A recurring problem with infrastructure is the opposite: environment secrets aren't managed consistently because they're never declared anywhere. Nothing tells you which secrets an environment expects, so they get forgotten, and you find out at deploy time.
Atmos closes that gap. You declare the secrets a component depends on right alongside its configuration, and then provision the missing values for each environment. Declarations live in git; values live in the backend you choose. Secrets are declared on the component, but because they're merged through your normal stack imports and inheritance, you can put shared declarations in a catalog or a stack manifest and let every component that imports it pick them up — you write a secret down once, where it belongs, and it lands everywhere it's needed.
Declare, Provision, Resolve
Declare a secret under a component's secrets.vars, then reference it in your component vars with the
!secret YAML function:
components:
terraform:
api:
secrets:
vars:
DATADOG_API_KEY:
store: app-secrets # a `secret: true` store
required: true
vars:
datadog_api_key: !secret DATADOG_API_KEY
Manage the values with a small, familiar CLI. Every operation is scoped to a stack and a component:
atmos secret set DATADOG_API_KEY --stack=prod --component=api
atmos secret list --stack=prod --component=api
atmos secret validate --stack=prod --component=api
atmos secret init walks a component's declarations and provisions the ones that are missing, so a new
environment is a single command rather than a checklist. The full set ships: init, set (alias add),
get, delete (alias rm), list, pull, push, import, and validate.
Pick the Backend That Fits
Different teams and environments need different kinds of secret storage, so Atmos supports a range of backends. You declare a secret once and keep its value wherever makes sense:
- 1Password — reach for it during local development, or run 1Password Connect to serve secrets to services inside your VPCs.
- GitHub Actions secrets — manage the secrets your CI already uses, directly from Atmos.
- Amazon Secrets Manager and AWS SSM Parameter Store (SecureString).
- SOPS — opaque, git-committed encrypted files, with no dependency on an external secret store.
- Azure Key Vault and GCP Secret Manager for the other major clouds.
- HashiCorp Vault, Redis, Artifactory, and your machine's native keychain.
Any store becomes a secret backend by setting secret: true, and the dedicated secret managers
(1Password, keychain, GitHub Actions) are treated that way automatically.
A Word on SOPS
SOPS is worth calling out. It works the same on your laptop as it does in CI or automation, with no
external secret store to stand up. The encrypted file is committed to git as an opaque blob, which means
you can see exactly when a secret changed in your history — you get a built-in audit trail and a place that
documents which secrets exist, without ever exposing their values. It's a clean answer when you want
managed secrets without another running service. SOPS supports age as the simplest path to get started.
Masking Comes Along for Free
When Atmos retrieves a secret's value, that value is added to the masking dictionary, so anywhere the string shows up in output it's masked. That lowers the risk of running tasks in automation that need sensitive integration secrets.
Read-only commands don't even need access to the backend. atmos describe and the atmos list family
resolve !secret to a masked placeholder without contacting the store, so you can diff or review a stack
— production included — on a laptop or in CI with no cloud access:
# No credentials needed — the secret renders masked
atmos describe component api --stack=prod
# Reveal the real value (requires access to the secret backend)
atmos describe component api --stack=prod --mask=false
One thing to know: atmos secret exec and atmos secret shell inject real values into the child process
so your tool can use them, and the child process's own output is not masked. Masking applies to what Atmos
itself prints.
One Workflow Instead of Three Tools
Historically this took a stack of tools: one to handle identity and authentication, one to write values into the secret store, and one to pass those values into your process at runtime. Atmos brings all three together.
In the cloud, reading a secret first means authenticating as the right identity — and Atmos already does that, with SSO, OIDC, and assumed roles. So it handles both halves of the problem: it acquires the credentials and retrieves the secrets. Most tools do one or the other, leaving you to bolt a separate identity tool onto a separate secrets tool.
When a component consumes a secret, Atmos injects it automatically — terraform plan/apply just works,
with no wrapper and no atmos secret exec -- atmos …. For everything else, atmos secret exec and
atmos secret shell resolve a component's declared secrets and run any command — a script, a local
server, a one-off CLI — with them set in the environment. That makes this just as useful inside your own
developer workflow as it is for infrastructure.
Secrets Never Touch Disk
There's a subtle trap here. Atmos hands variables to Terraform through a generated varfile
(*.terraform.tfvars.json) — and a naive approach would write your resolved secrets into that file in
plaintext, leaving them orphaned on disk long after the run finishes. Masking the output doesn't help if
the value is sitting in a file.
So Atmos doesn't do that. Any variable whose value contains a secret — whether it is the secret, or just
embeds one inside a larger string like postgres://user:••••••@host/db, or buries it in a nested map — is
kept out of the varfile entirely and injected at runtime as a TF_VAR_ environment variable instead. The
value lives only for the lifetime of the Terraform process; nothing is left behind on disk. Detection
reuses the same masking dictionary every secret is already registered in, so it works even when you run
with --mask=false.
The two commands where a human might want a secret materialized take an explicit opt-in:
# Export secrets into the interactive shell as TF_VAR_* (off by default)
atmos terraform shell vpc --stack=prod --with-secrets
# Include secret values in a generated varfile (off by default)
atmos terraform generate varfile vpc --stack=prod --with-secrets
Without --with-secrets, terraform shell won't expose secrets to the subshell, and
generate varfile writes a varfile with the secret variables omitted (and tells you it did).
None of this is Terraform-specific. Declaring the secrets an environment depends on, provisioning them per environment, and masking them everywhere is just good practice for any workflow that touches credentials — and now it's built in.
How to Use It
- Configure a
secret: truestore inatmos.yaml. - Declare your secrets under a component's
secrets.vars. - Provision values with
atmos secret set(orinitto be prompted for missing ones). - Reference them with
!secret NAMEin your component vars. - Verify with
atmos secret listand gate CI withatmos secret validate.
See the secrets configuration guide, the
atmos secret command reference, and the
!secret YAML function to get started.
Try It
The sops-secrets example below is fully self-contained — it manages age-encrypted secrets with no cloud
credentials. Give it a spin with the bundled atmos test command to watch the whole lifecycle: set, get,
list, validate, and masked-without-credentials inspection.
Get Involved
This is one of our most-requested features, and one we deliberately took our time on — the cost of getting secrets wrong is high, and we wanted to get it right. It's marked experimental while we gather feedback. Try it out and let us know what backends and workflows you'd like to see next on GitHub.
