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

:::warning Pitfalls!
Overusing imports can make configurations harder to understand. We recommend limiting import depth to maintain clarity. Review our [best practices](/best-practices/stacks) for practical guidance.
:::

## Configuration

Define an `import` section at the top of any stack configuration:

```yaml
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`](/cli/configuration) 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:

1. `.yaml`
2. `.yml`
3. `.yaml.tmpl`
4. `.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/defaults`
- `mixins/region/us-east-2`
- `orgs/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 sibling `shared` directory

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](https://github.com/hashicorp/go-getter) URL schemes. This enables sharing configurations across repositories, teams, or organizations.

:::warning Remote imports bring configuration, not component code

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

```yaml
# 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`](/cli/commands/terraform/source) so Atmos vendors the root module just-in-time at plan/apply time:

```yaml
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](/cli/commands/vendor/pull) 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.
- **`oci://` - OCI Registries** — Import from an artifact in an OCI registry such as GHCR, Amazon ECR, or Docker Hub.
- **`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:

```yaml
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:

```yaml
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.

### OCI Registry Examples

Import from an artifact in an OCI registry:

```yaml
import:
  # A single file inside the artifact
  - oci://ghcr.io/acme/stacks:v1.2.0//catalog/vpc.yaml

  # A directory of stack files (recursive)
  - oci://ghcr.io/acme/stacks:v1.2.0//catalog/network

  # A glob
  - "oci://ghcr.io/acme/stacks:v1.2.0//catalog/**/*.yaml"
```

An OCI artifact is a set of layers, so the `//` path to the file, directory, or glob inside the artifact is required. As with Git imports, the extension is optional (`//catalog/vpc` resolves `vpc.yaml`). Atmos pulls each artifact once per invocation and resolves every import from it.

Atmos authenticates to the registry with the first of these that applies:

1. Your container registry credentials (the Docker credential store and credential helpers).
2. For `ghcr.io` only, a GitHub token from `ATMOS_GITHUB_TOKEN` or `GITHUB_TOKEN`, together with a username from `ATMOS_GITHUB_USERNAME`, `GITHUB_ACTOR` (set automatically in GitHub Actions), or [`settings.github_username`](/cli/configuration/settings). A token without a username is ignored and a warning is logged.
3. Anonymous access, which works for public artifacts.

`oci://` imports don't use the Git credential injection that `git::` imports use. Pin to an immutable tag or digest for reproducible builds.

[`nested_imports: remote`](#nested-imports-in-remote-files) works with `oci://` imports too, resolving nested imports from the stack base path inside the same artifact. The `//` path must include that base path (`stacks` by default, or your `stacks.base_path`) so Atmos can infer where the stack library starts:

```yaml
import:
  - path: oci://ghcr.io/acme/stacks:v1.2.0//stacks/orgs/acme/_defaults.yaml
    nested_imports: remote
```

With the default base path, `//catalog/vpc.yaml` fails with "could not infer the remote stack base path" when combined with `nested_imports: remote`.

### HTTP(S) Examples

Import from any HTTP(S) URL:

```yaml
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:

```yaml
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's `stacks.base_path` (default)
- `remote` - resolve nested imports from the remote source's stack base path

`nested_imports: remote` is supported for Git/go-getter and `oci://` sources where Atmos can infer the remote stack base path from the import path. An import without `nested_imports` inherits the mode of the file that contains it: imports in the consuming repository's stack manifests use `local`, while an import inside a file that was itself imported with `nested_imports: remote` stays `remote`. Set `nested_imports: local` on an import to switch back.

### Caching Remote Imports

When stacks import a catalog from a remote Git repository or an `oci://` artifact, Atmos fetches each unique source **at most once per invocation** and resolves every subdirectory import from that single shared copy. This happens automatically, with no configuration, and a single run always sees one consistent snapshot of each source. Git clones are shallow (`depth=1`) 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`:

```yaml
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`:

```yaml title="atmos.yaml"
imports:
  ttl: 5m
```

Cached sources, including pulled OCI artifacts, 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:

```yaml title=".github/workflows/atmos.yaml"
- 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

1. **Pin Versions:** Always use `?ref=` with a specific tag or commit SHA for Git imports to ensure reproducible builds.

2. **Cache Considerations:** Remote imports are cached locally. Use `atmos vendor pull` to refresh cached imports.

3. **Authentication:** Configure appropriate credentials for private repositories or buckets via environment variables or credential files.

4. **Fallback to Local:** Consider vendoring critical remote imports locally using `atmos vendor pull` for offline access and faster builds.

For more details on go-getter URL formats, see the [go-getter documentation](https://github.com/hashicorp/go-getter#url-format).

## Conventions

We recommend placing all baseline "imports" in the `stacks/catalog` folder, however, they can exist anywhere.

Use [mixins](/howto/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_paths` configuration)

:::info
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](/design-patterns/stack-organization/defaults-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:

```yaml title="stacks/orgs/cp/tenant1/test1/us-east-2.yaml"
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.

:::important
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`](/templates) 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.

```yaml
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:

```yaml
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:

```yaml
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](https://pkg.go.dev/text/template))
- **`skip_templates_processing` - (boolean)** — Skip template processing for the imported file. Can be used if the imported file uses `Go` templates 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 `context` instead of throwing an error. Useful when the imported file contains Go templates for external systems (e.g. Datadog) that Atmos should not evaluate. Unlike `skip_templates_processing` which 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 the [`templates.settings.ignore_missing_template_values`](/cli/configuration/templates) setting in `atmos.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 `local` to resolve nested imports from the consuming repository's stack base path, or `remote` to resolve them from the remote source's stack base path. When omitted, a top-level import defaults to `local`, and an import inside a file imported with `nested_imports: remote` inherits `remote`. See [Nested Imports in Remote Files](#nested-imports-in-remote-files).
- **`when` - (string, list, or map)** — An optional condition gating whether the import is processed at all, for example `ci` to import a file only in a CI pipeline. An unset condition always imports. See [Conditional Imports](#conditional-imports).
- **`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 like `0s`, `5m`, `1h`, `7d`, or keywords like `daily`. Set a default for all imports with the top-level `imports.ttl` setting in `atmos.yaml`. See [Caching Remote Imports](#caching-remote-imports).

### Conditional Imports

An object entry's `when` condition gates whether Atmos processes the import. When the condition does not match, Atmos
never reads the file, so a skipped import cannot fail and contributes nothing to the stack. An unset condition always
imports. This is the same `when` that the [`import:` list in `atmos.yaml`](/cli/configuration/imports#conditional-imports)
supports.

- **`ci`** — Import only when Atmos is running in a detected CI environment.
- **`local`** — Import only when Atmos is **not** running in a detected CI environment, for example on a developer workstation.
- **`always` / `never`** — Always or never import.
- **`!cel <expression>`** — A [CEL expression](/steps#conditional-execution) for a dynamic condition. The expression can read native stack labels through `labels`, native tags through `tags`, or `env`, the process environment as a string map, for example . Use an explicit `!cel` YAML tag or an unprefixed expression. A quoted string beginning with `!cel` is literal text and is rejected as a condition.

A list of conditions imports only when every condition holds. A map with `all`, `any`, or `not` combines conditions.

For example, use the native `pci` label as a feature flag to select a retention mixin wherever a workload is
PCI-scoped. The rule depends on the label, independent of the environment name. Dev and prod share an application
catalog that reads its log retention from [settings](/stacks/settings), with a fallback of `7` days:

```yaml title="stacks/catalog/app.yaml"
components:
  terraform:
    app:
      vars:
        log_retention_days: !template '{{ .settings | dig "default" "log_retention_days" 7 }}'
```

The [`!template` function](/functions/yaml/template) preserves the numeric value. The `dig` lookup returns `7` when
the setting is absent. Templates and Sprig are enabled by default with the current [configuration edition](/cli/configuration/edition).

The PCI mixin supplies a shared setting that any component can consume:

```yaml title="stacks/mixins/pci.yaml"
settings:
  default:
    log_retention_days: 366
```

For example, [PCI DSS v4.0.1, requirement 10.5.1](https://www.middlebury.edu/sites/default/files/2025-01/PCI-DSS-v4_0_1.pdf#page=256) calls for retaining audit logs for at least
12 months, with the most recent three months immediately available for analysis.

Here we use 366 days to account for leap years.

Put the conditional import in one shared environment configuration:

```yaml title="stacks/catalog/environment.yaml"
import:
  - catalog/app
  - path: mixins/pci
    when: !cel labels.pci == true
```

Each environment imports that same configuration and supplies its own classification:

```yaml title="stacks/dev.yaml"
name: dev

metadata:
  labels:
    pci: false

import:
  - catalog/environment
```

```yaml title="stacks/prod.yaml"
name: prod

metadata:
  labels:
    pci: true

import:
  - catalog/environment
```

The environment's labels are available inside the shared configuration's nested imports. The same rule skips the PCI
mixin for dev, retaining 7 days, and includes it for prod, selecting 366 days. The environment stacks contain no
conditional logic; that rule is defined once in `catalog/environment.yaml`.

Atmos evaluates the import condition, merges the selected settings, then renders the component's variable.
The label selects the behavior, the mixin supplies the setting, and the component decides how to use it.
The same label also identifies the component in list commands.

The label is native Atmos metadata, separate from cloud resource tags and component variables.

Use the same label to find the resulting component instances with
`atmos list instances --labels pci:true`, then inspect a selected instance with
`atmos describe component app -s prod`. These commands select using the final component metadata, which can differ
from the stack-wide metadata available during imports if components override it or later imports change it.

#### Metadata available during import resolution

The `labels` map contains literal string and boolean values from stack-wide `metadata.labels`. The `tags` list contains
literal strings from stack-wide `metadata.tags`; an absent list is empty.

- Imports are evaluated in declaration order. Labels and tags from earlier imports are available to later conditions.
- The current manifest's labels override imported labels, even when `metadata` appears after `import` in the YAML.
- Those explicit labels also take precedence inside nested imports. A child can override earlier sibling defaults,
  but cannot override explicit labels in the manifest importing it.
- Later imports do not change earlier decisions. An import cannot enable itself by declaring a label inside its own file.
- Glob matches share the scope available before that import entry. Their merged metadata becomes available to subsequent entries.
- Only stack-wide labels and tags participate. Component-specific metadata does not decide whether a whole file is imported.
- Tag lists follow `settings.list_merge_strategy` from `atmos.yaml` (replacement by default). Structural `!append` lists
  are also supported. An explicit empty list clears inherited tags under replacement; append retains earlier tags.
- Go templates and YAML functions are not evaluated to obtain labels or tags. An unavailable or null label masks an inherited
  value with the same name; an unavailable or null label map masks all inherited labels. Labels in a templated
  manifest must be readable as YAML before rendering. An unavailable or null tag list masks inherited tags; a list
  containing any unavailable member is itself unavailable. Conditions that need an unavailable tag list fail evaluation
  and skip the import, including negative membership checks. An absent or explicitly null list is known to be empty.

Use `true` for a boolean flag and compare with `labels.pci == true`. A quoted `"true"` is a string and does not match.
For an optional label, use `has(labels.pci) && labels.pci == true` to avoid a missing-key evaluation error.

Early authentication discovery uses the same metadata precedence for the local YAML imports it can read, so a skipped
mixin cannot introduce an authentication default. As with other auth discovery, remote and templated imports are not scanned.

[Try the focused example](/examples/conditional-imports), including its terminal recording.

:::note Facts available to `when`
Stack import conditions can use CI detection (`ci` and `local`), the process environment (`env`), and native stack labels
(`labels`) and tags (`tags`). They run before component selection, so component and command facts are unavailable. These metadata contexts
are specific to stack imports; configuration imports in `atmos.yaml` do not receive stack metadata.

A condition that cannot be evaluated does not match, so the import is skipped. Common causes include a missing label
or an unset environment variable. Run Atmos with debug logging to see why an import was skipped.
:::

Environment flags still work, for example `when: !cel env.FEATURE_MONITORING == "true"`. Changing any flag changes the
desired configuration. Keep its value consistent across local and CI runs when comparing plans, and between planning
and applying. Checked-in metadata makes these choices reproducible with the stack configuration.

A combination of the two formats is also supported:

```yaml
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](https://pkg.go.dev/text/template) and [Sprig functions](http://masterminds.github.io/sprig/) 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:

```yaml title="stacks/catalog/terraform/eks_cluster.yaml.tmpl"
# 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 }}"
```

:::note

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:

```yaml title="stacks/orgs/cp/tenant1/test1/us-west-2.yaml"
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:

```shell
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`:

```yaml title="atmos describe component eks-blue/cluster -s tenant1-uw2-test-1"
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
```

```yaml title="atmos describe component eks-green/cluster -s tenant1-uw2-test-1"
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:

```yaml title="stacks/catalog/terraform/eks_cluster_hierarchical.yaml.tmpl"
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):

```yaml title="stacks/orgs/cp/tenant1/test1/us-west-1.yaml"
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:

1. Processes imports in the order they are listed
2. Passes the parent's `context` to each imported file
3. If a child file defines its own `context`, merges it with the parent — child values win on conflicts
4. 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:

```shell
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:

```yaml title="atmos describe component eks-blue/cluster -s tenant1-uw1-test-1"
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
```

:::warning Use Sparingly
Templated imports are powerful but make configurations harder to read and debug. Prefer plain imports and [inheritance](/howto/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](#remote-imports).

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:

```yaml title="stacks/deploy/prod/_defaults.yaml"
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"
```

```yaml title="stacks/catalog_prod/customer.yaml"
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 }}"
```

```yaml title="stacks/deploy/prod/bastille/customers/_defaults.yaml"
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.enabled`](/cli/configuration/templates) is `true`. 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. Set `ignore_missing_template_values: true` on the import (or [`templates.settings.ignore_missing_template_values`](/cli/configuration/templates) globally) to leave unresolved values as-is instead.

:::note Import paths vs. file content
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](http://masterminds.github.io/sprig/) like `hasKey` to conditionally include variables based on the provided `context`:

```yaml
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](https://aws.amazon.com/blogs/containers/kubernetes-cluster-upgrade-the-blue-green-deployment-strategy/).

## Related

- [Configure the Project](/quick-start/advanced/configure-project)
- [Create Atmos Stacks](/quick-start/advanced/create-atmos-stacks)
