Create Atmos Stacks
In the previous steps, we configured the Terraform components and started the local sandbox.
Next step is to create and configure Atmos stacks — the YAML configuration that tells Atmos how each component should be provisioned in each environment. This is the heart of the tutorial, and it's where most of the Atmos design patterns show up.
Create Catalog for Components
Atmos supports the Configuration Catalog pattern to define default settings for Atmos components.
All the common default settings for each component go into a separate file under the stacks/catalog directory, which is then imported into the
parent Atmos stacks. This keeps the stack configurations DRY by reusing the component config that's common to every environment.
Refer to Stack Imports for more details on Atmos imports.
kms-key
The KMS key encrypts every other resource, so it's the root of the dependency graph. Notice there is no per-component identity binding: every component runs
under the default identity (local-aws, declared with default: true in atmos.yaml), so it reaches the local
sandbox automatically. To run a component under a different identity you'd use the --identity flag or a component-level auth section — not a settings field.
s3-bucket
The bucket is server-side encrypted with the KMS key. Two patterns appear here for the first time:
dependencies.componentsdeclares that the bucket depends onkms-key, so Atmos deploys the key first (see Component Dependencies).- Stack name templates build predictable resource coordinates from
namespace,tenant,environment, andstage, so the full graph can be planned before first-run state exists.
It also declares validation (covered in Configure Validation) and a hooks block that publishes the
bucket's coordinate to the config/ssm store after apply (covered under service discovery below).
dynamodb-table
The DynamoDB table has no dependencies of its own; it just publishes its table name to the config/ssm store after apply.
sns-topic
The SNS topic is KMS-encrypted with the same deterministic key alias and depends on kms-key.
sqs-queue
The queue subscribes to the SNS topic. It uses the stack-derived topic ARN and depends on sns-topic, so the graph still deploys the topic first.
app-config
The app-config component ties everything together. It depends on all five resources, uses stack-derived coordinates for the values it publishes, and
pulls two secrets with the !secret function. Keeping the coordinates template-based
lets atmos terraform deploy --all build the full graph on the first run, before any Terraform state or store values exist.
Two ways components share data
This example deliberately shows two cross-component patterns, and it's worth pausing on the distinction:
-
Templates plus dependencies — stack templates produce predictable names and ARNs, while
dependencies.componentscontrols apply/destroy order. This is the primary path in the quick start because it supports first-runatmos terraform deploy --all. -
Stores — components also write their actual outputs to a store after apply using a hook. Operators can inspect those published coordinates, and larger stacks can use
!storewhen consumers should discover values that are not predictable from naming conventions.
Group the catalog with catalog/backend
Rather than importing all six catalog files everywhere, the example collects them (plus the emulator) into a single catalog/backend manifest. Importing
catalog/backend once brings the whole backend into a stack.
Atmos Top-level Stacks
When executing the CLI commands, Atmos does not use the stack file names and their filesystem locations to search for the stack
where the component is defined. Instead, Atmos uses the context variables (namespace, tenant, environment, stage) to search for the stack. The
stack config file names can be anything, and they can be in any folder or sub-folder in the stacks directory.
For example, when executing the atmos terraform apply app-config -s plat-ue2-dev
command, the Atmos stack plat-ue2-dev is specified by the -s flag. Atmos evaluates name_template: "{{ .vars.tenant }}-{{ .vars.environment }}-{{ .vars.stage }}"
(see Configure CLI) for each top-level stack. The stack whose variables render to plat-ue2-dev is the match.
Then Atmos searches the top-level stack manifests (in the stacks directory) where tenant: plat, environment: ue2 and stage: dev are defined
(inline or via imports).
We use a hierarchical layout that follows the way AWS thinks about infrastructure (organization → OU/tenant → account/stage → region). This works very well as you grow to dozens or hundreds of accounts and regions, and it's the recommended starting point even for a small example like this one.
Create the following filesystem layout (the final layout for this Quick Start guide):
Configure Region Mixins
Mixins are a special kind of "import". It's simply a convention we recommend to distribute reusable snippets of configuration that alter behavior in some deliberate way. Mixins are not handled in any special way — they are technically identical to all other imports.
Reach for a mixin when a snippet is genuinely cross-cutting — reused across many stacks regardless of where they sit in the hierarchy. The region is
the textbook case: every account (dev, staging, prod) deploys into the same set of regions, so the region context belongs in one reusable place.
Earlier versions of this example also had mixins/tenant/* and mixins/stage/* files that each set a single context variable (tenant: plat,
stage: dev). That added an extra file to chase for no real reuse. A value that belongs to exactly one layer of the hierarchy should live in that
layer's _defaults.yaml, not in a one-line mixin. So tenant now lives in the tenant's _defaults.yaml, and stage lives in each account's
_defaults.yaml. Component settings stay in the top-level regional stacks that actually deploy those components.
In stacks/mixins/region/us-east-2.yaml, add the following config:
In stacks/mixins/region/us-west-2.yaml, add the following config:
The region mixin defines the global context variables region and environment, which Atmos uses when searching for a component in a stack. The mixin
gets imported into the top-level stacks, so we don't repeat the region context in every account — keeping the configuration DRY. This is the
multi-region modeling pattern.
Configure Defaults for Organization, OU and accounts
The _defaults.yaml stack manifests contain the default settings for the Organization(s), Organizational Units, and accounts. This is the
_defaults.yaml design pattern.
The _defaults.yaml stack manifests are not imported into other Atmos manifests automatically.
You need to explicitly import them using imports.
In stacks/orgs/acme/_defaults.yaml, define the namespace for the entire acme Organization (the real file also sets common tags and template
settings):
In stacks/orgs/acme/plat/_defaults.yaml, configure the plat OU (tenant). The tenant context variable lives right here in the tenant's
_defaults.yaml (no separate one-line mixin to chase). Defaults files should hold context and truly common settings; deployable component catalogs are
imported by the top-level stacks that actually run them.
When Atmos processes this stack config, it imports and deep-merges all the variables defined in the imported files and inline. Since the backend catalog
is not imported at this layer, helper stacks such as global-region can carry context without accidentally defining regional application resources.
In stacks/orgs/acme/plat/dev/_defaults.yaml, configure the dev account context. Keep this file focused on context and truly common defaults:
Configure the prod account the same way:
Add the staging account defaults the same way.
Configure Top-level Stacks
After we've configured the catalog, the region mixins, and the defaults for the Organization, OU and accounts, the final step is to configure the Atmos root (top-level) stacks. Each deployable regional leaf stack imports the backend catalog, its account defaults, and a region mixin. It also carries the component settings for that stage and region.
This keeps component definitions in the stacks that actually deploy them, while defaults stay focused on shared context. This layering is the organizational hierarchy pattern in action.
In stacks/orgs/acme/plat/dev/us-east-2.yaml, define the dev backend in us-east-2. This is where you can see, at a glance, what makes dev
different from the catalog defaults — same services, deliberately cheaper and more ephemeral settings:
Similarly, create the top-level Atmos stack for the dev account in us-west-2 with the same dev component settings and a different region mixin:
Repeat the pattern for the staging and prod accounts, importing the matching _defaults, region mixin, and component settings. For example,
stacks/orgs/acme/plat/prod/us-east-2.yaml uses the opposite, hardened choices:
These per-account, per-region leaf files are the top-level stacks Atmos resolves when you pass -s plat-ue2-dev, -s plat-uw2-prod, and so on.
The global-region stacks intentionally omit catalog/backend; they define context only, so regional resources like sns-topic and sqs-queue are not
accidentally instantiated in plat-gbl-*.
This is exactly what the regional top-level stack files do above: the catalog defines the defaults once, and each stage overrides only what differs via
Component Inheritance. dev turns versioning_enabled off while prod keeps it on —
which is exactly the difference the OPA policy enforces for prod. You can override per stage (as here) or
per region, at any layer of the hierarchy.
Next: run scans on every plan and publish outputs on every apply → Configure Hooks →