Skip to main content

Imports

Atmos supports importing other CLI configurations to break large configurations into smaller, manageable pieces. Use imports to organize configuration by section or to share common settings across projects.

Configuration​

Imports are defined in the import section of atmos.yaml:

atmos.yaml
import:
# Load the Atmos configuration from the main branch of the 'cloudposse/atmos' repository
- "https://raw.githubusercontent.com/cloudposse/atmos/refs/heads/main/atmos.yaml"
# Then merge the configs
- "configs.d/**/*"
# Finally, override some logging settings
- "./logs.yaml"

Import Types​

Imports can be any of the following:

Remote URL

Load configuration from a remote URL. Always use HTTPS URLs and verify the authenticity of remote sources.

import:
- "https://raw.githubusercontent.com/cloudposse/atmos/refs/heads/main/atmos.yaml"
Specific Path

Load a specific configuration file by path. Relative paths are resolved from the atmos.yaml directory.

import:
- "./logs.yaml"
- "configs/terraform.yaml"
Glob Patterns

Use wildcard globs (*), including recursive globs (**), to import multiple files. Only files ending in .yml or .yaml will be considered.

import:
- "configs.d/**/*"
- "*.yaml"
Pro-Tip

Atmos supports POSIX-style greedy Globs for all file names/paths (double-star/globstar ** is supported as well)

Entry Forms: String or Object​

Each import entry can be either a bare string (a path, glob, or remote URL) or an object with additional controls. The two forms can be mixed in the same list:

atmos.yaml
import:
# String form (optional - a failed import is warned and skipped).
- "configs.d/**/*"

# Object form (required by default - a failed import aborts configuration loading).
- path: "git::https://github.com/acme/shared-config.git//.atmos.d/mcp.yaml?ref=v1.2.3"
skip_if_missing: false # fail closed if it cannot be resolved or merged
when: local # only resolve on workstations, skip in CI

The object form brings CLI-config imports toward parity with stack-manifest imports, which already support an object model. Object entries accept:

path
Required. The import location: a local path, a glob, or a remote go-getter URL.
skip_if_missing
Controls failure behavior when the import cannot be resolved or merged. See Optional vs Required Imports.
when
An optional condition gating whether the import is resolved at all. See Conditional Imports.

Optional vs Required Imports​

By default, the failure behavior depends on the entry form - this preserves backward compatibility:

String entries are optional (fail open)
If the import cannot be resolved or merged, Atmos logs a warning and continues. This is the historical behavior and is unchanged.
Object entries are required (fail closed)
If the import cannot be resolved or merged, configuration loading aborts with an error. Set skip_if_missing: true to opt back into warn-and-skip.
atmos.yaml
import:
# Required: a security-bearing fragment (e.g. a read-only tool allowlist) MUST load.
- path: "git::https://github.com/acme/shared-config.git//.atmos.d/mcp.yaml?ref=v1.2.3"
skip_if_missing: false

# Optional: a nice-to-have fragment that may be absent in some checkouts.
- path: "configs.d/local-overrides.yaml"
skip_if_missing: true
Fail closed for config that matters

A string import that silently fails to load leaves its settings out with only a warning - risky for a fragment that constrains behavior (for example, a tool allowlist that limits what the Atmos MCP server may do). Use the object form with skip_if_missing: false so a missing or malformed guardrail stops the run instead of silently disappearing.

Conditional Imports​

An object entry's when condition gates whether the import is resolved. An unset condition always imports. Conditions are evaluated at config-load time, so the only runtime fact available is CI detection (the subcommand is not yet known):

local
Resolve only when Atmos is not running in a detected CI environment (i.e. on workstations).
ci
Resolve only when Atmos is running in a detected CI environment.
always / never
Always or never resolve.
!cel <expression>
A CEL expression for a dynamic condition. At config-load time the expression can reference ci (boolean) and env (the process environment as a string map), for example '!cel env.DEPLOY_ENV == "prod"'. In atmos.yaml author it as a quoted "!cel ..." string, not a !cel YAML tag - the CLI-config loader only recognizes its own YAML functions as tags, so a bare !cel tag is rejected. A bare (non-predicate) string is also treated as CEL, but the explicit !cel prefix is clearer.
.atmos.d/dev-tooling.yaml
import:
# Developer-tooling config (ai/mcp/toolchain) has no purpose in an automated apply - and a CI job
# holding deploy credentials should not resolve a cross-repo remote import. Scope it to local.
- path: "git::https://github.com/acme/shared-config.git//.atmos.d/mcp.yaml?ref=v1.2.3"
when: local

# Dynamic: load prod overrides only when an environment variable says so. The CEL expression is
# authored as a quoted "!cel ..." string and reads the process environment via the `env` map.
- path: "configs.d/prod-overrides.yaml"
when: '!cel env.DEPLOY_ENV == "prod"'

This lets a fragment stay committed and active for developers while auto-skipping in CI, removing the need to keep it out of the committed config (for example, gitignored behind a *.yaml.example template).

Facts available to when

CLI-config imports are evaluated while atmos.yaml loads, before the subcommand is known, so the only facts a condition can use are CI detection (the ci/local predicates) and the process environment (the env map in a !cel expression). Stack, component, and step facts are not available here.

Merge Order​

Imported configurations are deep-merged in order. The last file in the list supersedes settings in preceding imports:

  1. First imported file (lowest priority)
  2. Second imported file
  3. … (middle files)
  4. Last imported file (highest priority)
  5. Settings in the main atmos.yaml (highest priority)

Example: Organizing by Section​

Break a large configuration into section-specific files:

configs.d/
├── base.yaml # Base paths and general settings
├── terraform.yaml # Terraform component settings
├── helmfile.yaml # Helmfile component settings
├── stacks.yaml # Stack configuration
├── workflows.yaml # Workflow settings
└── logs.yaml # Logging configuration
atmos.yaml
import:
- "configs.d/**/*"

# Override specific settings if needed
logs:
level: Debug

Remote Imports​

Be Careful with Remote Imports
  • Always use HTTPS URLs (not HTTP)
  • Verify the authenticity of remote sources
  • Consider pinning to specific commit hashes instead of branch references for stability

Example with pinned commit:

import:
# Pinned to specific commit for reproducibility
- "https://raw.githubusercontent.com/cloudposse/atmos/abc123def456/atmos.yaml"

Private Repositories and Authentication​

A git:: import authenticates through your local Git credentials, so it reaches private repositories the same way git clone does (and, in CI, through the Atmos GitHub token injection and the Atmos Pro credential broker). A plain HTTPS raw URL (for example a https://raw.githubusercontent.com/... link) does not carry Git credentials and therefore cannot read a private repository - use the git:: form for private sources:

atmos.yaml
import:
# Reaches a private repo via local Git credentials.
- "git::https://github.com/acme/private-config.git//.atmos.d/ai.yaml?ref=v1.2.3"

Pin Remote Imports​

Atmos warns when a git:: import is pinned to a mutable ref - a branch (for example ?ref=main), or no ?ref= at all. An unpinned import silently absorbs the upstream repo's edits on every cache refresh, with no integrity check - the config-import analogue of using an unpinned GitHub Action. Pin to a commit SHA (for example ?ref=<40-hex-sha>) for an immutable, tamper-evident import - a SHA always resolves to the same content. A version tag (for example ?ref=v1.2.3) also silences the warning and is a reasonable, conventional version reference, but a tag can be moved to a different commit, so it does not carry the same immutability guarantee as a SHA. The import still resolves either way; the warning is advisory.

Caching Remote Imports​

Two default behaviors apply, depending on the import form. A git:: import that uses a subdirectory (e.g. git::https://github.com/org/repo.git//path?ref=main) re-clones the source repo on every atmos invocation, so mutable refs (e.g. ?ref=main) always stay current. A plain remote URL (no git:: subdirectory) is cached indefinitely instead — once downloaded, that content is reused until the cache is cleared.

Set imports.ttl to change either default. When set, it applies to every remote import the same way: a git:: subdirectory import is cached and reused across invocations instead of always re-cloning, and a plain URL import expires after ttl instead of being cached forever. Use the same setting stack manifest imports already support (see remote stack imports):

atmos.yaml
imports:
ttl: 5m

This is a single global default applied to CLI-config imports; the object entry form does not add a per-import ttl override (unlike stack manifest imports). ttl accepts durations like 0s (always re-fetch), 5m, 1h, 7d, or keywords like daily. Pin to a commit SHA (immutable) or a version tag with a longer ttl for maximum reuse; keep ttl short for mutable refs like main so the config stays fresh. Leave ttl unset to keep each import form's own default behavior described above.

Auto-Discovery with .atmos.d​

Atmos automatically discovers and deep-merges *.yaml / *.yml fragments under an atmos.d/ or .atmos.d/ directory (next to atmos.yaml and at the repository root), with no explicit import entry. A fragment may itself declare an import: list, and those nested imports are resolved recursively - so a consumer repo can keep a single committed fragment that pulls shared CLI config from one source-of-truth repo:

.atmos.d/shared.yaml
import:
- path: "git::https://github.com/acme/shared-config.git//.atmos.d/ai.yaml?ref=v1.2.3"
when: local
- path: "git::https://github.com/acme/shared-config.git//.atmos.d/toolchain.yaml?ref=v1.2.3"

Fragment entries support the same string/object forms, skip_if_missing, and when controls as top-level import entries.

Limitations​

  • Templated imports of Atmos configuration are not supported (unlike stacks)
  • Only .yml and .yaml files are processed when using glob patterns
  • The object entry form does not support a per-import ttl (use the global imports.ttl)

See Also​