Import Stack Configurations
Imports let you split stack configurations across multiple files and reuse them. Each import is deep-merged on top of previous imports, building up the final configuration.
Use Cases
- DRY Configuration: Reduce duplication by sharing common settings across stacks.
- Blueprints: Define reusable baselines that any stack can import as a starting point.
- Service Catalogs: Provide golden-path configurations that teams can compose into architectures.
Overusing imports can make configurations harder to understand. We recommend limiting import depth to maintain clarity. Review our best practices for practical guidance.
Configuration
Define an import section at the top of any stack configuration:
import:
- catalog/file1 # First import "file1" from the catalog
- catalog/file2 # Second import "file2" from the catalog, deep merging on top of the first import
- catalog/file3 # Third import "file3" from the catalog, deep merging on top of the preceding imports
The base path for imports is specified in the atmos.yaml in the stacks.base_path section.
If no file extension is used, Atmos appends .yaml automatically.
Automatic Template File Detection
When importing files without an extension, Atmos searches for template versions automatically. The search order is:
.yaml.yml.yaml.tmpl.yml.tmpl
If both a .yaml and a .yaml.tmpl version exist, Atmos uses the template version. Template files are skipped during atmos validate stacks since they contain placeholders that are invalid YAML before rendering.
Import Path Resolution
Atmos supports two types of import paths:
Base-Relative Paths (Default)
Most imports use paths relative to the stacks.base_path configured in atmos.yaml:
catalog/vpc/defaultsmixins/region/us-east-2orgs/acme/_defaults
These paths are resolved from the base stacks directory, regardless of where the importing file is located.
File-Relative Paths
Imports starting with . or .. are relative to the current file's directory:
./_defaults- imports from the same directory as the current file../shared/_defaults- imports from a siblingshareddirectory
This is useful when you want to import files that are co-located with the current configuration.
Remote Imports
Atmos supports importing stack configurations from remote sources using go-getter URL schemes. This enables sharing configurations across repositories, teams, or organizations.
A remote import pulls stack configuration (the YAML manifests) — it does not pull the component source code (the Terraform/OpenTofu/Helmfile root modules) those manifests reference.
If a remote catalog defines a component, e.g.:
# Imported remotely from the hub repo
components:
terraform:
vpc:
metadata:
component: vpc # references components/terraform/vpc — the CODE, not in this import
vars:
cidr_block: 10.0.0.0/16
then the configuration resolves and commands like atmos describe, atmos list, and atmos validate work — but atmos terraform plan/apply vpc fails with "component not found" unless the components/terraform/vpc directory actually exists in the consuming repository. The remote import does not materialize it.
To pull the component code from the same (or another) remote repo, give the component a source so Atmos vendors the root module just-in-time at plan/apply time:
components:
terraform:
vpc:
metadata:
component: vpc
source:
uri: github.com/my-org/hub//components/terraform/vpc?ref=v1.2.3
vars:
cidr_block: 10.0.0.0/16
Alternatively, vendor the component into the repo ahead of time with atmos vendor pull. In short: remote imports + remote components are two separate mechanisms — importing the config does not bring the code; you must provision the source (or vendor it) explicitly.
Supported Schemes
git::- Git Repositories- Import from Git repositories over HTTPS or SSH.
github.com/- GitHub Shorthand- Shorthand syntax for GitHub repositories.
s3::- Amazon S3- Import from S3 buckets using AWS credentials.
gcs::- Google Cloud Storage- Import from GCS buckets using Google Cloud credentials.
https:///http://- HTTP(S)- Import from any HTTP(S) URL.
file://- Local Files- Import from absolute local file paths.
Git Repository Examples
Import from a Git repository:
import:
# HTTPS with specific ref (branch, tag, or commit)
- git::https://github.com/acme/infrastructure.git//stacks/catalog/vpc?ref=v1.2.0
# SSH authentication
- git::git@github.com:acme/infrastructure.git//stacks/catalog/eks?ref=main
# GitHub shorthand
- github.com/acme/infrastructure//stacks/catalog/rds?ref=v2.0.0
The // separates the repository URL from the path within the repository. The ?ref= parameter specifies the Git ref (branch, tag, or commit SHA).
S3 Examples
Import from Amazon S3:
import:
# Basic S3 import
- s3::https://s3.amazonaws.com/acme-configs/stacks/catalog/vpc.yaml
# With region specified
- s3::https://s3-us-west-2.amazonaws.com/acme-configs/stacks/catalog/eks.yaml
S3 imports use your AWS credentials from the environment or AWS config files.
HTTP(S) Examples
Import from any HTTP(S) URL:
import:
# Direct HTTPS import
- https://raw.githubusercontent.com/acme/configs/main/stacks/catalog/vpc.yaml
# From internal artifact server
- https://artifacts.internal.acme.com/stacks/v1.0.0/defaults.yaml
Nested Imports in Remote Files
Remote files can import other stack files. By default, nested imports inside a remote file resolve from your local stacks.base_path. This preserves the existing behavior and supports remote files that intentionally expect the consuming repository to provide local extension points.
Use the map form with nested_imports: remote when the remote file should behave as part of a self-contained remote stack library:
import:
- path: git::https://github.com/acme/infrastructure.git//stacks/orgs/acme/_defaults.yaml?ref=v1.2.0
nested_imports: remote
In this mode, if the remote file imports catalog/_defaults, Atmos resolves it from the remote source's stack base path, for example stacks/catalog/_defaults.yaml in the same Git source.
The supported values are:
local- resolve nested imports from the consuming repository'sstacks.base_path(default)remote- resolve nested imports from the remote source's stack base path
nested_imports: remote is supported for Git/go-getter sources where Atmos can infer the remote stack base path from the import path. Plain string imports and map imports without nested_imports use local.
Caching Remote Imports
When stacks import a catalog from a remote Git repository, Atmos clones each unique source repository at most once per invocation and resolves every subdirectory import from that single shared clone. This happens automatically, with no configuration, and a single run always sees one consistent snapshot of each source. Shallow clones (depth=1) are used by default.
By default the clone is refreshed once per invocation, so mutable refs (e.g. ?ref=main) always stay current. To reuse the clone across invocations — for example, with a warm CI cache — set a ttl:
import:
- path: "git::https://github.com/acme/infrastructure.git//stacks/catalog/vpc?ref=main"
ttl: 5m
Set a default for every import in atmos.yaml:
imports:
ttl: 5m
Cached sources live under the XDG cache directory (~/.cache/atmos/stack-imports/, honoring XDG_CACHE_HOME). In CI, cache that directory between runs so a fresh clone within the ttl window is skipped:
- uses: actions/cache@v4
with:
path: ~/.cache/atmos/stack-imports
key: atmos-stack-imports-${{ runner.os }}
ttl accepts durations like 0s (always re-fetch), 5m, 1h, 7d, or keywords like daily. Pin to an immutable ?ref=<tag> (or commit SHA) with a longer ttl for maximum reuse; keep ttl short for mutable refs like main so the catalog stays fresh.
Best Practices for Remote Imports
-
Pin Versions: Always use
?ref=with a specific tag or commit SHA for Git imports to ensure reproducible builds. -
Cache Considerations: Remote imports are cached locally. Use
atmos vendor pullto refresh cached imports. -
Authentication: Configure appropriate credentials for private repositories or buckets via environment variables or credential files.
-
Fallback to Local: Consider vendoring critical remote imports locally using
atmos vendor pullfor offline access and faster builds.
For more details on go-getter URL formats, see the go-getter documentation.
Conventions
We recommend placing all baseline "imports" in the stacks/catalog folder, however, they can exist anywhere.
Use mixins for reusable snippets of configurations that alter the behavior of Stacks in some way.
The _defaults.yaml Pattern
Many Atmos projects use _defaults.yaml as a naming convention for default configurations at each level of the hierarchy. This is purely a convention—Atmos has no special handling for these files. They must be explicitly imported like any other file.
The underscore prefix ensures they:
- Sort to the top of directory listings (lexicographic sorting)
- Are visually distinct from actual stack configurations
- Are excluded from stack discovery (via
excluded_pathsconfiguration)
The _defaults.yaml pattern is a common convention, not an Atmos feature. These files are only excluded from stack discovery because they match the pattern in excluded_paths configuration. They must always be explicitly imported to take effect.
For a complete explanation of this pattern, see the _defaults.yaml Design Pattern documentation.
Imports Schema
The import section accepts a list of strings (simple paths) or a list of objects (when using templates or feature flags).
Imports without Templates
For a list of paths to the imported files, just provide a list of strings like this:
import:
- mixins/region/us-east-2
- orgs/cp/tenant1/test1/_defaults
- catalog/terraform/top-level-component1
- catalog/terraform/test-component
- catalog/terraform/vpc
- catalog/helmfile/echo-server
Imports with Templates
Sometimes you may want to import files that use Go templates. Templates can be used with or without providing a context - files with .yaml.tmpl or .yml.tmpl extensions are always processed as Go templates.
Files with the .yaml.tmpl or .yml.tmpl extension are always processed as Go templates, regardless of whether context is provided.
This allows you to use template functions that don't require context (like {{ now }}, {{ env "VAR" }}, {{ uuidv4 }}, etc.) even without providing context variables.
If you don't want a file to be processed as a template, use the .yaml or .yml extension instead.
The skip_templates_processing flag can be used to explicitly skip template processing for any imported file.
Templating must be enabled in atmos.yaml for Atmos to process the imported files as Go templates.
For example, here we import a file with a template and provide a context to passing two variables.
import:
- path: "catalog/something.yaml.tmpl" # Path to the imported file with the required .tmpl extension for Go templates
context:
foo: bar
baz: qux
skip_templates_processing: false
ignore_missing_template_values: false
skip_if_missing: false
- path: "catalog/something.yaml.tmpl"
context: {}
skip_templates_processing: false
ignore_missing_template_values: true
skip_if_missing: true
You can also use templates without providing any context variables. This is useful for including dynamic values that don't depend on context:
import:
# This template file uses functions that don't require context
- path: "catalog/metadata.yaml.tmpl"
# No context needed - the template can use functions like:
# {{ now | date "2006-01-02" }}
# {{ env "BUILD_NUMBER" }}
# {{ uuidv4 }}
# {{ randAlphaNum 10 }}
Example template file (catalog/metadata.yaml.tmpl) without context:
metadata:
generated_at: {{ now | date "2006-01-02T15:04:05Z07:00" }}
build_number: {{ env "BUILD_NUMBER" | default "local" }}
deployment_id: {{ uuidv4 }}
version: "1.0.0"
The import section supports the following fields:
path- (string) required- The path to the imported file
context- (map)- An optional freeform map of context variables that are applied as template variables to the imported file (if the imported file is a Go template)
skip_templates_processing- (boolean)- Skip template processing for the imported file. Can be used if the imported file uses
Gotemplates that should not be interpreted by atmos. For example, sometimes configurations for components may pass Go template strings not intended for atmos. ignore_missing_template_values- (boolean)- Process templates but skip any values missing from the
contextinstead of throwing an error. Useful when the imported file contains Go templates for external systems (e.g. Datadog) that Atmos should not evaluate. Unlikeskip_templates_processingwhich skips all template processing, this option still evaluates templates that have matching context values. To apply this globally to all imports without setting it on each one, use thetemplates.settings.ignore_missing_template_valuessetting inatmos.yaml. skip_if_missing- (boolean)- Skip the import without error if the file does not exist.
nested_imports- (string)- Controls how imports inside the imported file are resolved. Use
localto resolve nested imports from the consuming repository's stack base path, orremoteto resolve them from the remote source's stack base path. Defaults tolocal. ttl- (string)- Cache duration for the cloned source repository of a remote (Git) import. When set, the clone is reused across Atmos invocations until it expires (e.g. with a warm CI cache), skipping the re-clone. Within a single invocation a source repo is always cloned at most once regardless of
ttl. When unset, the source is re-cloned once per invocation. Accepts durations like0s,5m,1h,7d, or keywords likedaily. Set a default for all imports with the top-levelimports.ttlsetting inatmos.yaml. See Caching Remote Imports.
A combination of the two formats is also supported:
import:
- mixins/region/us-east-2
- orgs/cp/tenant1/test1/_defaults
- path: "<path_to_atmos_manifest1>"
- path: "<path_to_atmos_manifest2>"
context: {}
skip_templates_processing: false
ignore_missing_template_values: true
Templated Imports
Atmos supports Go templates and Sprig functions in imported files. This lets you parameterize a configuration and reuse it with different values via the import context.
For example, we can define the following configuration for EKS Atmos components in the catalog/terraform/eks_cluster.yaml.tmpl template file:
# Imports can also be parameterized using `Go` templates
import: []
components:
terraform:
"eks-{{ .flavor }}/cluster":
metadata:
component: "test/test-component"
vars:
enabled: "{{ .enabled }}"
name: "eks-{{ .flavor }}"
service_1_name: "{{ .service_1_name }}"
service_2_name: "{{ .service_2_name }}"
tags:
flavor: "{{ .flavor }}"
Since Go processes files ending in .yaml.tmpl text files with templates, we can parameterize the Atmos component name eks-{{ .flavor }}/cluster and any values in any sections (vars, locals, settings, env, backend, etc.), and even the import section in the imported file (if the file imports other configurations).
Then we can import the template into a top-level stack multiple times providing different context variables to each import:
import:
- path: "mixins/region/us-west-2"
- path: "orgs/cp/tenant1/test1/_defaults"
# This import with the provided context will dynamically generate
# a new Atmos component `eks-blue/cluster` in the current stack
- path: "catalog/terraform/eks_cluster.yaml.tmpl"
context:
flavor: "blue"
enabled: true
service_1_name: "blue-service-1"
service_2_name: "blue-service-2"
# This import with the provided context will dynamically generate
# a new Atmos component `eks-green/cluster` in the current stack
- path: "catalog/terraform/eks_cluster.yaml.tmpl"
context:
flavor: "green"
enabled: false
service_1_name: "green-service-1"
service_2_name: "green-service-2"
Now we can execute the following Atmos commands to describe and provision the dynamically generated EKS components into the stack:
atmos describe component eks-blue/cluster -s tenant1-uw2-test-1
atmos describe component eks-green/cluster -s tenant1-uw2-test-1
atmos terraform apply eks-blue/cluster -s tenant1-uw2-test-1
atmos terraform apply eks-green/cluster -s tenant1-uw2-test-1
All the parameterized variables get their values from the context:
vars:
enabled: true
environment: uw2
name: eks-blue
namespace: cp
region: us-west-2
service_1_name: blue-service-1
service_2_name: blue-service-2
stage: test-1
tags:
flavor: blue
tenant: tenant1
vars:
enabled: true
environment: uw2
name: eks-green
namespace: cp
region: us-west-2
service_1_name: green-service-1
service_2_name: green-service-2
stage: test-1
tags:
flavor: green
tenant: tenant1
Hierarchical Imports with Context
Atmos supports hierarchical imports with context. This will allow you to parameterize the entire chain of stack configurations and dynamically generate components in stacks.
For example, let's create the configuration stacks/catalog/terraform/eks_cluster_hierarchical.yaml.tmpl with the following content:
import:
# Use `region.yaml.tmpl` `Go` template and provide `context` for it.
# This can also be done by using `Go` templates in the import path itself.
# - path: "mixins/region/{{ .region }}"
- path: "mixins/region/region.yaml.tmpl"
# `Go` templates in `context`
context:
region: "{{ .region }}"
environment: "{{ .environment }}"
# `Go` templates in the import path
- path: "orgs/cp/{{ .tenant }}/{{ .stage }}/_defaults"
components:
terraform:
# Parameterize Atmos component name
"eks-{{ .flavor }}/cluster":
metadata:
component: "test/test-component"
vars:
# Parameterize variables
enabled: "{{ .enabled }}"
name: "eks-{{ .flavor }}"
service_1_name: "{{ .service_1_name }}"
service_2_name: "{{ .service_2_name }}"
tags:
flavor: "{{ .flavor }}"
Then we can import the template into a top-level stack multiple times providing different context variables to each import and to the imports for
the entire inheritance chain (which catalog/terraform/eks_cluster_hierarchical.yaml.tmpl imports itself):
import:
# This import with the provided hierarchical context will dynamically generate
# a new Atmos component `eks-blue/cluster` in the `tenant1-uw1-test1` stack
- path: "catalog/terraform/eks_cluster_hierarchical.yaml.tmpl"
context:
# Context variables for the EKS component
flavor: "blue"
enabled: true
service_1_name: "blue-service-1"
service_2_name: "blue-service-2"
# Context variables for the hierarchical imports
# `catalog/terraform/eks_cluster_hierarchical.yaml.tmpl` imports other parameterized configurations
tenant: "tenant1"
region: "us-west-1"
environment: "uw1"
stage: "test1"
# This import with the provided hierarchical context will dynamically generate
# a new Atmos component `eks-green/cluster` in the `tenant1-uw1-test1` stack
- path: "catalog/terraform/eks_cluster_hierarchical.yaml.tmpl"
context:
# Context variables for the EKS component
flavor: "green"
enabled: false
service_1_name: "green-service-1"
service_2_name: "green-service-2"
# Context variables for the hierarchical imports
# `catalog/terraform/eks_cluster_hierarchical.yaml.tmpl` imports other parameterized configurations
tenant: "tenant1"
region: "us-west-1"
environment: "uw1"
stage: "test1"
When processing hierarchical imports, Atmos:
- Processes imports in the order they are listed
- Passes the parent's
contextto each imported file - If a child file defines its own
context, merges it with the parent — child values win on conflicts - Repeats this recursively through the entire import chain
We are now able to dynamically generate the components eks-blue/cluster and eks-green/cluster in the stack tenant1-uw1-test1 and can
execute the following Atmos commands to provision the components into the stack:
atmos terraform apply eks-blue/cluster -s tenant1-uw1-test-1
atmos terraform apply eks-green/cluster -s tenant1-uw1-test-1
All the parameterized variables get their values from the hierarchical context settings:
vars:
enabled: true
environment: uw1
name: eks-blue
namespace: cp
region: us-west-1
service_1_name: blue-service-1
service_2_name: blue-service-2
stage: test-1
tags:
flavor: blue
tenant: tenant1
Templated imports are powerful but make configurations harder to read and debug. Prefer plain imports and inheritance when possible. Reserve templates for cases where the duplication would be excessive — such as generating many similar components from a single definition.
Referencing Earlier Imports in Import Paths
Import paths are rendered as Go templates, and the template data includes the
settings, vars, and env defined by imports listed earlier in the same manifest
(and any explicit context on the import). This lets a later import reference a value
that an earlier import established — for both local paths and the ?ref= of a
remote import.
A common use case is keeping dev and prod in one repository while pinning everything
prod consumes to an immutable Git ref, driven by a single variable defined once per
stage:
settings:
context:
# Define the pinned version once for the whole prod tree (a tag in prod, a branch in dev).
deployment_repo_version: "v1.2.3"
import:
# The Git ref is pinned from the variable defined in the imported `_defaults` above.
- "github.com/my-org/my-repo//stacks/catalog/customer?ref={{ .settings.context.deployment_repo_version }}"
components:
terraform:
customer/base:
source:
uri: github.com/my-org/my-repo//components/terraform/customer
# The same variable also pins the component source version.
version: "{{ .settings.context.deployment_repo_version }}"
import:
- ../_defaults # establishes settings.context.deployment_repo_version
- catalog_prod/customer # uses it in the templated `?ref=`
Because imports are evaluated in order, the value must be defined by an import that appears before the import that references it.
- Requires templating enabled
- Import-path templating only runs when
templates.settings.enabledistrue. When disabled,{{ ... }}in an import path is left literal. skip_templates_processing- Leaves
{{ ... }}in the path literal (the import is not rendered). - Missing values
- If a referenced value is not defined by an earlier import (or the import's
context), Atmos fails with an error. Setignore_missing_template_values: trueon the import (ortemplates.settings.ignore_missing_template_valuesglobally) to leave unresolved values as-is instead.
Only the import path sees values from earlier imports. The content of an imported
file is still templated with its own context (and component-level values are resolved
later, when the component is processed), so patterns like version: "{{ .settings.context.deployment_repo_version }}"
above continue to resolve at component time.
Conditional Variables in Templates
Use Sprig functions like hasKey to conditionally include variables based on the provided context:
components:
terraform:
eks/iam-role/{{ .app_name }}/{{ .service_environment }}:
metadata:
component: eks/iam-role
settings:
spacelift:
workspace_enabled: true
vars:
enabled: {{ .enabled }}
tags:
Service: {{ .app_name }}
service_account_name: {{ .app_name }}
service_account_namespace: {{ .service_account_namespace }}
{{ if hasKey . "iam_managed_policy_arns" }}
iam_managed_policy_arns:
{{ range $i, $iam_managed_policy_arn := .iam_managed_policy_arns }}
- '{{ $iam_managed_policy_arn }}'
{{ end }}
{{- end }}
{{ if hasKey . "iam_source_policy_documents" }}
iam_source_policy_documents:
{{ range $i, $iam_source_policy_document := .iam_source_policy_documents }}
- '{{ $iam_source_policy_document }}'
{{ end }}
{{- end }}
The iam_managed_policy_arns and iam_source_policy_documents variables will be included in the component configuration only if the
provided context object has the iam_managed_policy_arns and iam_source_policy_documents fields.
Summary
Imports with context let you parameterize entire configuration files and generate component variations from a single template — useful for patterns like EKS blue-green deployments.