# atmos > The runtime for infrastructure. This file contains all documentation content in a single document following the llmstxt.org standard. ## Validate Only What Changed Large repositories often have validation rules that are useful but expensive or noisy to run across every file on every pull request. That makes it tempting to skip validation exactly when a focused signal would be most helpful. Atmos can now validate the files affected by a change, so pull requests get actionable feedback without rechecking unrelated project inputs. ## The Problem Configuration schema checks, stack validation, formatting rules, and workflow linting protect different parts of a project. Running every check over an entire repository can obscure the result that matters to the change under review. ## The Fix Add `--affected` to the project-wide validator or to an individual validator. Atmos compares the current worktree with the Git merge-base, including local uncommitted and untracked files. In GitHub Actions, it reads the pull request base SHA automatically. ```shell atmos validate --affected --format rich atmos validate config --affected --base origin/main atmos config validate --affected --base origin/main atmos validate stacks --affected --base origin/main atmos stack validate --affected --base origin/main atmos validate editorconfig --affected --base origin/main atmos validate ci --affected --base origin/main ``` Schema and EditorConfig checks validate changed inputs directly. Changes to a schema or rule configuration expand validation to the relevant full set. Stack validation runs whenever stack or Atmos configuration inputs change, preserving import and duplicate-component checks. Workflow validation sends only changed workflows to actionlint unless its configuration changes. ## How to Use It Use the new `Validation (affected)` GitHub Actions job as a model for a project workflow. Check out enough Git history for the merge-base, then run the command with rich output: ```yaml - uses: actions/checkout@v6 with: fetch-depth: 0 - run: atmos validate --affected --format rich ``` ## Get Involved Try affected validation in your pull request workflow and share feedback through [GitHub issues](https://github.com/cloudposse/atmos/issues). --- ## Browse, Search, and Copy Any Atmos Agent Skill Before You Install It Before you install a skill, you want to read what it does. Atmos agent skills did not let you do that. You had to install a skill first to read its full instructions. Or you had to find its file in the Atmos repository on GitHub. An AI agent had the same problem. No page listed every skill with its full content. No single URL let an agent fetch a skill's content on its own. ## The Problem The [`atmos ai skill list`](/cli/commands/ai/skill) command already listed every official skill, showing each skill's name, source, and install status. Add `--detailed`, and it also showed a one-line description. But no view showed a skill's full instructions -- the actual content that teaches the AI its domain knowledge. To read that content, you had two options. Install the skill first. Or open the Atmos repository on GitHub and find the right `SKILL.md` file. Neither option let you compare skills quickly. Neither option gave an AI agent a direct way to fetch and read a skill's content on its own. ## The Fix The [Agent Skills Directory](/ai/skills) lists every skill with its full content. Open the directory. Search by name, description, or category. Click a skill to read its complete instructions -- no install step required. Add a new skill to the Atmos repository, and it appears in the directory automatically. Each skill's page also has a raw Markdown version. Add `.md` to the page's URL. This is the same convention the rest of the Atmos docs site uses. An AI agent can fetch this URL directly and read the skill's content. Each skill's page also has a "Copy as Markdown" button. Use it to copy the skill's full content. Paste it into a chat session, or review it, before you install anything. The [`atmos ai skill list`](/cli/commands/ai/skill) command also gained a `--format` flag and a Category column, so the same categorization shown in the directory is available from the CLI. ## How to Use It Browse and search the directory at [atmos.tools/ai/skills](/ai/skills). Or fetch a skill's content directly: ```shell # Raw Markdown for one skill - readable by a human, fetchable by an agent skill_name=atmos-ai curl "https://atmos.tools/ai/skills/${skill_name}.md" # Table view, now with a Category column atmos ai skill list # Machine-readable output for scripting atmos ai skill list --format=json atmos ai skill list --format=yaml ``` ## Get Involved See the [Agent Skills](/cli/commands/ai/skill) guide to learn how skills are structured. It also explains how to contribute one. Share skill ideas and contributions in the [Atmos community](https://github.com/cloudposse/atmos). --- ## AI Agent Skills for Atmos Atmos now ships 21 agent skills that give AI coding assistants deep knowledge of Atmos conventions, stack configuration, Terraform orchestration, authentication, validation, and more. Skills build on two open standards -- [AGENTS.md](https://agents.md/) and [Agent Skills](https://agentskills.io/specification) -- and work across Claude Code, OpenAI Codex, Gemini CLI, Cursor, Windsurf, GitHub Copilot, and other AI tools. ## What Changed Atmos includes a new `agent-skills/` directory at the repository root containing 21 domain-specific skills packaged as a single Claude Code plugin: `atmos-terraform`, `atmos-helmfile`, `atmos-packer`, `atmos-ansible`, `atmos-workflows`, `atmos-custom-commands`, `atmos-config`, `atmos-introspection`, `atmos-auth`, `atmos-stores`, `atmos-toolchain`, `atmos-devcontainer`, `atmos-stacks`, `atmos-components`, `atmos-vendoring`, `atmos-validation`, `atmos-schemas`, `atmos-gitops`, `atmos-yaml-functions`, `atmos-templates`, `atmos-design-patterns` Each skill is a self-contained package with a primary `SKILL.md` instruction file (under 500 lines) and a `references/` directory with deeper reference material. An `AGENTS.md` router file maps user tasks to the right skill, so AI tools load only the context they need. The skills are built on two open standards: - **[AGENTS.md](https://agents.md/)** -- The cross-tool instruction file standard created by OpenAI with Google, Cursor, and others. Governed by the Linux Foundation's Agentic AI Foundation (AAIF). Adopted by 60,000+ GitHub repos. - **[Agent Skills](https://agentskills.io/specification)** -- The directory-based skill packaging format (`SKILL.md`) created by Anthropic. Adopted by Microsoft, OpenAI, Cursor, and GitHub. ## Why This Matters AI coding assistants are increasingly part of infrastructure workflows. Without domain-specific context, they rely on general training data that may be outdated, incomplete, or wrong. Common issues include: - Generating invalid YAML that doesn't match the Atmos schema - Using incorrect CLI commands or flags - Missing Atmos-specific patterns like deep merging, abstract components, or YAML functions - Not knowing about features like [`!store`](/functions/yaml/store), [`!terraform.output`](/functions/yaml/terraform.output), or multi-provider authentication Agent skills solve this by providing structured, up-to-date knowledge directly in the repository. The AI loads the relevant skill before answering, ensuring accurate and current guidance. ## How to Use It Every major AI tool has its own configuration directory. The skills live in a tool-agnostic `agent-skills/` folder, and you symlink or reference them from each tool's expected location. ### Claude Code Install from the Cloud Posse plugin marketplace: ```bash # Add the Cloud Posse marketplace (one-time setup) /plugin marketplace add cloudposse/atmos # Install the Atmos skills plugin (all 21 skills) /plugin install atmos@cloudposse ``` One plugin, one install command, all 21 skills. The `cloudposse/atmos` GitHub repo serves as the marketplace -- Claude Code fetches the plugin manifest directly from the repo. No central registry or approval is involved. Once installed, the plugin is cached locally and skills activate automatically when you ask Atmos-related questions. To uninstall: ```bash # Remove the plugin /plugin uninstall atmos@cloudposse # Remove the marketplace (optional) /plugin marketplace remove cloudposse ``` For Atmos contributors working directly in the repo, skills are also auto-discovered via `.claude/skills/` symlinks. ### OpenAI Codex Codex natively reads `AGENTS.md` from the repository root (it co-created this standard). Copy the router to the repo root for automatic discovery: ```bash cp agent-skills/AGENTS.md AGENTS.md ``` ### Gemini CLI, Cursor, Windsurf, GitHub Copilot Each tool has its own integration path: ```bash # Gemini CLI -- symlink to .gemini/skills ln -s agent-skills .gemini/skills # Cursor -- create .cursor/rules/atmos.mdc (frontmatter required for auto-loading) mkdir -p .cursor/rules && cat > .cursor/rules/atmos.mdc << 'RULE' --- description: Atmos infrastructure orchestration guidance globs: "*.yaml, *.tf" alwaysApply: false --- @agent-skills/AGENTS.md RULE # Windsurf -- add reference in .windsurfrules (also auto-discovers AGENTS.md) echo 'Always refer to agent-skills/AGENTS.md for Atmos commands and configuration patterns.' >> .windsurfrules # GitHub Copilot -- reference in .github/copilot-instructions.md mkdir -p .github && echo 'Always refer to agent-skills/AGENTS.md for Atmos commands and configuration patterns.' >> .github/copilot-instructions.md ``` ### Other Tools Antigravity reads from `.agent/skills/`, JetBrains Junie reads `AGENTS.md` as a fallback alongside `.junie/guidelines.md`, and Amazon Q uses JSON configs in `.amazonq/cli-agents/`. The skills use standard Markdown with YAML frontmatter, which is universally compatible. ### In Your Infrastructure Project You install the `atmos` binary -- you don't need to clone the Atmos repository. Skills are installed separately through your AI tool. For Claude Code, install skills via the plugin marketplace (see above). For other AI tools that don't have Atmos marketplace support, use Atmos vendoring to pull the skills into your project: ```yaml # Add to vendor.yaml apiVersion: atmos/v1 kind: AtmosVendorConfig metadata: name: atmos-agent-skills description: Vendor Atmos AI agent skills spec: sources: - component: "agent-skills" source: "github.com/cloudposse/atmos.git//agent-skills?ref={{.Version}}" version: "main" targets: - "agent-skills" ``` ```bash atmos vendor pull --component agent-skills ``` This downloads the `agent-skills/` directory with the correct structure intact. To update skills later, run the same command again. See the [AI Agent Skills documentation](/cli/commands/ai/skill) for the full skill reference and [Configure AI Assistants](/projects/setup-editor/ai-assistants) for tool-specific setup instructions. ## How Skills Are Activated You don't invoke skills manually. When you ask your AI assistant a question about Atmos, it automatically activates the right skill based on your question. At session start, the AI loads lightweight metadata from each installed skill. When your question matches a skill's description, the full skill content loads on demand. For example, asking _"How do I configure stack imports?"_ automatically activates the `atmos-stacks` skill. No special syntax needed. ## Get Involved We welcome contributions to expand and improve the skills. Each skill follows a simple structure: one `SKILL.md` file with YAML frontmatter and a `references/` directory for detailed content. Open an issue or pull request on [GitHub](https://github.com/cloudposse/atmos) to suggest improvements or add new skills. --- ## Use Claude Code, Codex CLI, or Gemini CLI as Atmos AI Providers Atmos AI now supports **CLI providers** — invoke your locally installed Claude Code, OpenAI Codex, or Gemini CLI as AI backends. No API keys needed. Just use your existing subscription. ## Why This Matters Until now, using Atmos AI required purchasing API tokens from a provider and configuring keys. Many developers already have Claude Code or Codex installed with active subscriptions (Claude Max, ChatGPT Pro, or Gemini's free tier). CLI providers let you reuse that investment: - **No API keys** — the CLI tool handles auth via its own subscription - **No per-token billing** — included in your existing plan - **Full MCP support** — Claude Code and Codex CLI can use all your configured MCP servers ## Quick Start **File:** `atmos.yaml` ```yaml ai: enabled: true default_provider: "claude-code" # or "codex-cli" or "gemini-cli" providers: claude-code: max_turns: 10 ``` ```bash brew install --cask claude-code && claude auth login atmos ai ask "What did we spend on EC2 last month?" ``` That's it. Atmos detects the binary, generates an MCP config with auth wrapping, and passes everything to Claude Code. You get answers from real AWS data using your subscription. ## Available CLI Providers | Provider | Binary | Subscription | MCP Support | |--------------|----------|-------------------------------|-------------------------------| | Claude Code | `claude` | Claude Pro/Max ($20-200/mo) | Full | | OpenAI Codex | `codex` | ChatGPT Plus/Pro ($20-200/mo) | Full | | Gemini CLI | `gemini` | Google account (free tier) | Blocked for personal accounts | All three providers work for prompt-only queries. For MCP-enabled workflows (AWS billing, security, IAM, etc.), use Claude Code or Codex CLI. ## MCP Pass-Through When `mcp.servers` is configured in `atmos.yaml`, Atmos automatically passes MCP servers to the CLI tool. Each provider uses its native config format: - **Claude Code** — temp `.mcp.json` via `--mcp-config` - **Codex CLI** — `~/.codex/config.toml` (backup/restore after exit) - **Gemini CLI** — `.gemini/settings.json` in the current working directory Auth-requiring servers are wrapped with [`atmos auth exec -i `](/cli/commands/auth/exec). Toolchain PATH and `ATMOS_*` env vars are injected automatically. ## See It in Action > Outputs below are from a production setup with 8 AWS MCP servers configured. ### Claude Code — Security Posture ```text $ atmos ai ask "What is our security posture in us-east-2 region?" ℹ MCP servers configured: 8 (config: /tmp/atmos-mcp-config.json) ℹ AI provider: claude-code 👽 Thinking... ## Security Posture Summary for us-east-2 ### ✅ Enabled Security Services (2/6) GuardDuty - Fully operational • Status: ENABLED • Finding frequency: Every 6 hours • Active data sources: CloudTrail, DNS Logs, VPC Flow Logs, S3 Logs, EKS Audit Logs, RDS Login Events • Current findings: None (clean) Inspector - Partially enabled • Status: ENABLED but no scan types active ### ❌ Disabled Security Services (4/6) 1. IAM Access Analyzer - Not configured 2. Security Hub - Not enabled 3. Trusted Advisor - Error checking status 4. Macie - Not enabled ### Key Recommendations High Priority: 1. Enable Security Hub for centralized findings aggregation 2. Enable IAM Access Analyzer 3. Enable at least one Inspector scan type Your security posture is moderate — you have basic threat detection enabled but are missing several important security services. ``` ### Codex CLI — EC2 Billing ```text $ atmos ai ask "What did we spend on EC2 last month in each region?" ℹ MCP servers configured: 8 (in ~/.codex/config.toml) ℹ AI provider: codex-cli 👽 Thinking... For last month, I used the previous calendar month: March 1, 2026 through April 1, 2026. EC2 spend by region: • us-east-2: $88.10 Cost Explorer returned only us-east-2 for Amazon Elastic Compute Cloud - Compute, so that appears to be the only region with EC2 spend in that period. AWS also marked the result as Estimated, which is normal on April 1, 2026 while billing finalizes. ``` Both providers automatically selected the right MCP server (`aws-security` and `aws-billing`) and returned answers from real AWS data — no manual server selection needed. ## Configuration You can mix CLI and API providers in the same config and switch between them: **File:** `atmos.yaml` ```yaml ai: default_provider: "claude-code" providers: claude-code: max_turns: 10 codex-cli: full_auto: true anthropic: model: "claude-sonnet-4-6" api_key: !env "ANTHROPIC_API_KEY" ``` ```bash # Uses claude-code (default) atmos ai ask "What stacks do we have?" # Override to codex-cli atmos ai ask --provider codex-cli "What did we spend on EC2?" # Override to API provider atmos ai ask --provider anthropic "Describe the vpc component" ``` ## Try It **Example: AI with Claude Code** Complete example with Claude Code CLI provider, AWS MCP servers, and automatic auth. Browse Example[Read more](/examples/ai-claude-code) ## Learn More - [AI Documentation](/ai) - [AI Providers Configuration](/cli/configuration/ai/providers) - [MCP Configuration](/cli/configuration/mcp) --- ## AI-Powered Analysis for Atmos Commands with the Global --ai Flag Add [`--ai`](/cli/global-flags#ai-powered-analysis-examples) to any Atmos command and get instant AI-powered analysis of the output. Successful plans get summarized, errors get explained with step-by-step fixes — zero workflow changes required. ## How It Works 1. Run any Atmos command with `--ai` 2. The command executes normally — output streams to your terminal in real-time 3. After the command completes, the captured output is sent to the AI provider 4. The AI analysis appears below the command output ## Plan Analysis ```shell $ atmos terraform plan vpc -s ue1-prod --ai Terraform will perform the following actions: # null_resource.vpc will be created + resource "null_resource" "vpc" { + id = (known after apply) + triggers = { + "availability_zones" = "us-east-1a,us-east-1b,us-east-1c" + "environment" = "production" + "nat_gateway_enabled" = "true" + "vpc_cidr" = "10.10.0.0/16" } } Plan: 1 to add, 0 to change, 0 to destroy. ✓ AI analysis complete ## Terraform Plan Summary: vpc in ue1-prod ### Plan Succeeded — 1 Resource to Create Metric │ Value ────────────┼─────── To Add │ 1 To Change │ 0 To Destroy │ 0 A null_resource.vpc will be created with CIDR 10.10.0.0/16 , 3 availability zones, and NAT Gateways enabled. ``` ## Error Explanation When commands fail, the AI explains the root cause and provides actionable fixes: ```shell $ atmos terraform plan vpc -s ue1-pro --ai ✓ AI analysis complete ## Component Not Found Error Atmos cannot locate the vpc component within the ue1-pro stack. ## Quick Fix Check │ Command ─────────────────────────────────┼──────────────────────────────────── Stack name is correct │ atmos list stacks Component is defined in stack │ atmos list components -s ue1-pro No YAML syntax errors │ atmos validate stacks ``` ## Domain-Specific Analysis with `--skill` Pair `--ai` with [`--skill`](/cli/global-flags#core-global-flags) for domain-specific expertise. Combine multiple skills with commas or repeated flags: ```bash # Terraform expertise for plan analysis atmos terraform plan vpc -s ue1-prod --ai --skill atmos-terraform # Multiple skills (comma-separated) atmos terraform plan vpc -s ue1-prod --ai --skill atmos-terraform,atmos-stacks # Multiple skills (repeated flag) atmos terraform plan vpc -s ue1-prod --ai --skill atmos-terraform --skill atmos-stacks ``` ## Enable via Environment Variable ```bash # Enable for all commands in the session export ATMOS_AI=true atmos terraform plan vpc -s ue1-prod # With skills ATMOS_AI=true ATMOS_SKILL=atmos-terraform,atmos-stacks atmos terraform plan vpc -s ue1-prod ``` ## Works with Everything The `--ai` flag works with **any** Atmos command: ```bash atmos terraform plan vpc -s ue1-prod --ai atmos terraform apply vpc -s ue1-prod --ai atmos describe stacks --ai atmos validate stacks --ai atmos list components --ai ``` ## Try It **Explore the AI Example** Try `--ai` and `--skill` with a complete multi-region infrastructure project using mock components — no cloud credentials required. Browse Example[Read more](/examples/ai) ## Learn More - [AI Configuration](/cli/configuration/ai) — Full configuration reference - [Global Flags](/cli/global-flags) — All global flags including `--ai` and `--skill` - [AI Skills](/cli/configuration/ai/skills) — Available skills for domain-specific analysis --- ## Keep Installed AI Skills Up to Date Package managers normally tell you when an installed package is out of date and let you refresh just that one. Installed AI skills did not work that way: `atmos ai skill install` copies a bundled skill's content once, and upgrading the `atmos` binary afterward never touches that already-installed copy, even when the new release ships updated skill content. ## The Problem Official Atmos skills (`atmos-terraform`, `atmos-stacks`, and the rest of the catalog) are embedded directly in the `atmos` binary. Installing one by name copies its `SKILL.md` and supporting files to `~/.atmos/skills/` at that point in time. If a later `atmos` release bundles an improved version of that skill, nothing tells you, and nothing refreshes your local copy — the only way to pick it up was `atmos ai skill install --force`, applied on faith, one skill at a time, with no way to check first whether anything had actually changed. `atmos ai skill list --detailed` already surfaced an "update available" hint when an installed skill's version drifted from the catalog. There was no command that acted on it. ## The Fix `atmos ai skill update` closes that gap. It compares each installed bundled skill's recorded version against the catalog embedded in the running binary and reinstalls only the ones that are actually outdated — skills already at the current version are left untouched. Run it with no arguments to refresh every installed bundled skill that has an update available (a single confirmation, not one per skill), or name one skill to update just that one. Skills that are already current report "already up to date" and nothing is rewritten, so it's safe to run `atmos ai skill update` repeatedly, including as a habit after every `atmos` upgrade. An outdated skill is reinstalled the same way `atmos ai skill install --force` would install it, so `--client`, [`--scope`](/cli/commands/ai/skill), `--global`, [`--all-clients`](/cli/commands/ai/skill), and [`--path`](/cli/commands/ai/skill) all work exactly as they do on `install`. Skills installed from a GitHub repository aren't covered yet — there's no cheap way to check whether a git-sourced skill's upstream has moved without re-fetching it. Run `atmos ai skill install --force` to refresh one of those manually. ## How to Use It ```shell # Update a single bundled skill if a newer version is available. atmos ai skill update atmos-terraform # Update every installed bundled skill that has an update available. atmos ai skill update # Skip the confirmation prompt (for automation). atmos ai skill update --yes # Update and redistribute to a specific AI client. atmos ai skill update atmos-terraform --client vscode ``` ## Get Involved Read the [`atmos ai skill`](/cli/commands/ai/skill) documentation for the full command reference. To share feedback or request an improvement, [open an issue](https://github.com/cloudposse/atmos/issues). --- ## Ambient Credential Support for IRSA, IMDS, and ECS Task Roles Atmos now supports ambient AWS credentials from IRSA, EC2 instance profiles, and ECS task roles via two new identity kinds: `ambient` (generic passthrough) and `aws/ambient` (AWS SDK default credential chain). ## What's New Two new identity kinds make it possible to run Atmos natively in environments where credentials are already available: - **`ambient`** — A cloud-agnostic passthrough that preserves all environment variables as-is. No credential clearing, no IMDS disabling, no file overrides. Use this when you just want Atmos to leave the environment alone. - **`aws/ambient`** — An AWS-specific identity that resolves credentials through the [AWS SDK's default credential provider chain](https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html). This supports environment variables, shared config files, IRSA web identity tokens, EC2 instance metadata (IMDS), and ECS container credentials. Unlike `ambient`, it returns real AWS credentials that can be used by chained identities like `aws/assume-role`. ## Quick Start ### EKS Pod with IRSA ```yaml # atmos.yaml auth: identities: eks-deployer: kind: aws/ambient principal: region: us-east-1 ``` That's it. The pod's IRSA-injected `AWS_WEB_IDENTITY_TOKEN_FILE` and `AWS_ROLE_ARN` environment variables are preserved and used by the AWS SDK when Terraform runs. ### EC2 Instance Profile ```yaml # atmos.yaml auth: identities: instance-creds: kind: aws/ambient ``` The EC2 instance metadata service (IMDS) provides credentials automatically. No region needed if it's set elsewhere. ### Simple Passthrough ```yaml # atmos.yaml auth: identities: passthrough: kind: ambient ``` Use this when credentials are pre-configured in the environment and you just want Atmos to pass them through without any modification. ## Why This Matters Previously, Atmos auth explicitly disabled IMDS and cleared IRSA environment variables to prevent accidental credential leakage. This was the right default for developer workstations and interactive SSO flows, but it made it impossible to use infrastructure-provided credentials in: - **EKS pods** using IAM Roles for Service Accounts (IRSA) - **EC2 instances** with IAM instance profiles - **ECS tasks** with task roles - **CI runners** with pre-configured AWS credentials Teams in these environments had to either bypass Atmos auth entirely (losing identity management, integrations, and audit trail) or maintain parallel credential management. ## Chaining with Assume Role A common pattern in multi-account environments: use the pod's IRSA credentials to assume a role in another account. ```yaml # atmos.yaml auth: identities: # Base: use the pod's IRSA credentials pod-base: kind: aws/ambient principal: region: us-east-1 # Chain: assume a cross-account role using IRSA as the base cross-account-deployer: kind: aws/assume-role via: identity: pod-base principal: assume_role: "arn:aws:iam::999999999999:role/TerraformDeployRole" ``` The `aws/ambient` identity resolves the IRSA credentials, then `aws/assume-role` uses them to call `sts:AssumeRole` into the target account. ## How It Works Every other AWS identity kind in Atmos calls a shared `PrepareEnvironment()` helper that: 1. Clears credential env vars (`AWS_ACCESS_KEY_ID`, `AWS_WEB_IDENTITY_TOKEN_FILE`, etc.) 2. Sets `AWS_EC2_METADATA_DISABLED=true` 3. Points `AWS_SHARED_CREDENTIALS_FILE` and `AWS_PROFILE` to Atmos-managed files The `aws/ambient` identity **skips all of this**. It creates a copy of the environment and returns it with only an optional region override. This allows the AWS SDK's full default credential chain to work naturally. The `ambient` identity is even simpler — it returns the environment completely unchanged. ## When to Use Each | Identity Kind | Use When | |---|---| | `aws/ambient` | Running on AWS infrastructure (EKS/EC2/ECS) where you need chaining support | | `ambient` | Any environment where credentials are pre-configured and you want zero interference | | `aws/assume-role` via SSO | Developer workstations, interactive flows | | `aws/assume-role` via GitHub OIDC | GitHub Actions CI/CD | ## Get Started - [Authentication Configuration](/stacks/auth) — Full auth documentation with ambient examples - [EKS Profile Example](https://github.com/cloudposse/atmos/tree/main/examples/config-profiles/profiles/eks) — Ready-to-use EKS/IRSA configuration For usage and configuration, see [atmos auth user configure](/cli/commands/auth/user/configure). --- ## Ansible Component Support Atmos now supports Ansible as a first-class component type, enabling unified orchestration of infrastructure provisioning (Terraform) and configuration management (Ansible) from the same stack manifests. See it in action: [View the full example](/examples/demo-ansible) ## What Changed Ansible joins Terraform, Helmfile, and Packer as a native component type in Atmos: - **New [`atmos ansible playbook`](/cli/commands/ansible/playbook) command** executes Ansible playbooks with stack-based configuration - **New [`atmos ansible version`](/cli/commands/ansible/version) command** displays Ansible version information - **Full-stack processor support** including inheritance, vars, env, settings, and auth sections - **Automatic variable file generation** passed to Ansible via `--extra-vars @` - **Native flag passthrough** via `--` separator for any Ansible option ## Why This Matters Infrastructure automation typically involves two layers: provisioning (creating resources) and configuration (setting up software). Teams often use Terraform for the first and Ansible for the second, but manage them with separate tooling and workflows. With Ansible component support, you can: - **Manage both layers from one tool** with consistent patterns - **Share variables between Terraform and Ansible** components in the same stack - **Apply the same inheritance model** to Ansible playbooks - **Use familiar Atmos workflows** for configuration management ## How to Use It ### Configuration Add Ansible configuration to `atmos.yaml`: ```yaml components: ansible: base_path: "components/ansible" command: "ansible-playbook" # optional ``` ### Stack Manifest Define Ansible components alongside Terraform: ```yaml components: terraform: vpc: vars: cidr_block: "10.0.0.0/16" ansible: webserver: vars: app_name: myapp app_port: 8080 settings: ansible: playbook: site.yml inventory: inventory/production ``` ### Commands ```bash # Show Ansible version atmos ansible version # Run playbook with stack settings atmos ansible playbook webserver --stack prod # Override playbook and inventory atmos ansible playbook webserver -s prod -p deploy.yml -i hosts.ini # Pass native Ansible flags atmos ansible playbook webserver -s prod -- --check --verbose # Use aliases atmos an pb webserver -s prod ``` ## Get Involved - Report issues or request features on [GitHub](https://github.com/cloudposse/atmos/issues) - Join the discussion in [Slack](https://slack.cloudposse.com/) --- ## New !append YAML Function to Extend Inherited Lists Atmos now supports the `!append` YAML function, which **adds items to an inherited list** during stack merging instead of replacing it — giving you per-field control over list merging without changing any global setting. ## The Problem Atmos configuration is layered: stacks inherit from imports, and later layers merge over earlier ones. By default, when two layers define the same list, the later one **replaces** it entirely: ```yaml # base.yaml -> eks.dependencies.components: [{ name: vpc }, { name: iam-role }] components: terraform: eks: dependencies: # This REPLACES the inherited list — vpc and iam-role are lost. components: - name: rds - name: elasticache ``` Your only options were to re-declare the full inherited list in every override (brittle — the base and override drift apart) or flip the global `list_merge_strategy` to `append`, which changes merge behavior for **every** list in your configuration. ## The Solution The `!append` function appends to the inherited list for just that one field: ```yaml import: - base # eks.dependencies.components: [{ name: vpc }, { name: iam-role }] components: terraform: eks: dependencies: components: !append - name: rds - name: elasticache # Result: [vpc, iam-role, rds, elasticache] ``` Base items come first, appended items follow, and every other list keeps its normal behavior. ## Use Cases - **Dependencies** — add `dependencies.components` entries without restating the base ones. - **Security groups** — layer environment-specific rules onto a shared base. - **IAM policies** — extend base policy statements. - **Tags/labels** — add extra tags while preserving organizational defaults. - **EKS node groups** — extend a base cluster with additional node pools. ## How It Works `!append` is unusual among YAML functions because it influences the **merge**, not value resolution. During parsing, an `!append`-tagged list is wrapped with append metadata (in both `atmos.yaml` and stack manifests); during merging, Atmos detects the wrapper and concatenates the list onto the inherited value. It appends exactly once regardless of the global `list_merge_strategy` (`replace`, `append`, or `merge`), and works with any list item type — strings, numbers, or maps. It pairs naturally with [`!unset`](/functions/yaml/unset): `!append` adds to inherited lists, `!unset` removes inherited keys — together giving fine-grained control over what inheritance brings in. ## Get Started The `!append` function is available now. See the [documentation](/functions/yaml/append) for more examples, and browse the full set of [Atmos YAML functions](/functions/yaml). --- ## Archive Step Type: Pack Zip/Tar Archives Without Shelling Out Packaging a directory into a deployable archive — most commonly zipping a Lambda function's source into `handler.zip` before `terraform plan`/`apply` — has always meant reaching for a shell-based hook that wraps the `zip`/`tar` binary. That works until you actually depend on it: the flags aren't even the same between BSD `tar` (macOS) and GNU `tar` (most Linux CI images), errors surface as opaque shell exit codes instead of anything typed, and — the part that's easy to miss — the archive you get back isn't reproducible. Build the exact same source twice and you get two different files. A new `archive` step type fixes all three: implemented on the Go standard library only, it behaves identically everywhere Atmos runs, validates its config before touching the filesystem, and can now produce byte-identical output for identical input. ## The Problem This surfaced while migrating a Terragrunt Lambda example (Lambda + DynamoDB + IAM role) to Atmos. The source unit used a `before_hook "package"` wrapping `scripts/package.sh`, which itself wrapped the `zip` binary, before every `plan`/`apply`/`destroy`. Translating that directly meant an equivalent shell-based hook: ```yaml hooks: package: kind: command command: bash args: ["-c", "cd src && zip -r ../handler.zip . -x '*.test.js'"] events: [before.terraform.plan, before.terraform.apply, before.terraform.destroy] ``` Three problems with this, once you look past "it worked when I tested it": - **The invocation isn't even portable between macOS and Linux**, let alone Windows. `zip -x` and `tar --exclude` take different glob syntax; BSD `tar` (what macOS ships) and GNU `tar` (most Linux CI images) disagree on flags like `--exclude` vs. `-X excludefile`. Two engineers on the same team can get different results running the "same" hook. - **Failures are opaque.** A typo in a glob, a missing source directory — all of it surfaces as a shell exit code with no structure Atmos can validate ahead of time. - **The output isn't reproducible.** Zip and tar both bake in each file's real modification time and permission bits. A fresh `git clone` sets every file's mtime to checkout time, and umask varies across machines and CI images — so identical source content produces a _different archive_ on every rebuild. This is the same failure mode Terraform's own `archive_file` provider has been [reported for](https://github.com/hashicorp/terraform-provider-archive/issues/34) over the years: two runs, same input, different checksum. Anything downstream that keys off the archive's hash — Lambda only redeploying when the zip's checksum changes, a build cache, a provenance check — sees a "change" that isn't one. A `data "archive_file"` Terraform data source was tried first for the packaging step itself. It works fine when a config references the data source's own output attribute directly (`output_path`, `output_base64sha256`) — Terraform's graph orders that correctly. It broke here because the migrated module computed the zip path independently in a `locals` block, mirroring the original Terragrunt script's own variable, instead of referencing the data source's attribute — so there was no dependency edge forcing `archive_file` to run first. That's not a one-off: it's a [documented class of bug](https://github.com/hashicorp/terraform/issues/30042), closed once the reporter switched to referencing the output attribute directly — the same fix this migration's module hadn't made. A `before.terraform.*` hook sidesteps the whole class of problem structurally — it runs and completes before Terraform's graph is even evaluated, so there's no reference to get wrong. ## The Fix The new `archive` step type packages a directory or file into a `zip`, `tar`, or `tgz` archive using only Go's standard library — `archive/zip`, `archive/tar`, `compress/gzip`. No shelling out, so the same config produces the same archive structure everywhere Atmos runs: ```yaml steps: - name: package type: archive source: src/ destination: handler.zip exclude: - "**/*.test.js" - "**/node_modules/**" ``` It has no dedicated hook kind — like every step type added since [`kind: step`](/changelog/hooks-step-types) shipped, it reaches hooks through the same bridge, so it works as a lifecycle hook with zero extra plumbing: ```yaml hooks: package: kind: step type: archive events: [before.terraform.plan, before.terraform.apply, before.terraform.destroy] with: source: src/ destination: handler.zip ``` ## How to Use It - `source` / `destination` — required. What to archive, and where to write it. - `format` — `zip`, `tar`, `tgz`, `tar.bz2`, or `tar.xz`. Inferred from `destination`'s extension when omitted (writing `tar.bz2`/`tar.xz` isn't implemented yet — see below). - `subpath` — nests `source`'s content under a path inside the archive, e.g. `subpath: opt/nodejs` for a Lambda Layer. - `include` / `exclude` — glob filters, evaluated exclude-then-include. - `action` — `replace` (default) always rebuilds the archive fresh from `source`. `update` adds/refreshes entries in an existing archive without touching the rest — supported for `zip` and uncompressed `tar` only, since `tgz`/`tar.bz2`/`tar.xz` compress the whole stream as one unit and can't be edited surgically. Selecting `update` on one of those formats fails with a clear error instead of silently falling back to a full rebuild. `create` and `extract` are reserved in the schema for a future release, so it won't need a breaking change to add them later. ## Reproducible Output A native step type fixes the shelling-out problems, but not the reproducibility one by itself — by default, entries still carry the source files' real mtime and permission bits (same as omitting the field, or setting it to `filesystem` explicitly). Set `mtime: epoch` or `mtime: git` to fix that too: ```yaml steps: - name: package type: archive source: src/ destination: handler.zip mtime: epoch ``` `mtime` names the mechanism, not an outcome: it's the modification-time metadata stamped into each _archive entry_ — not the source files on disk, and not the archive file's own OS-level mtime. - `filesystem` — the default. Every entry carries the source file's real mtime and permission bits. - `epoch` — every entry gets the same timestamp: the most recent Git commit that touched anything under `source`. One timestamp, whole archive. Named after the [`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/docs/source-date-epoch/) reproducible-builds convention, which this mirrors conceptually — one shared reference timestamp for the whole build — even though the value here comes from Git history rather than an environment variable. - `git` — each entry gets its own timestamp, from its most recent commit. Files with no Git history (build output, `node_modules/`) fall back to the same value `epoch` would use. `epoch` and `git` both also normalize permission bits (`0644`, or `0755` if the source is executable), since inconsistent umask-derived permissions produce different archive bytes just as reliably as inconsistent timestamps do. Timestamps come from Git commit history via `go-git` — no shelling out to `git log`, and no dependency on `SOURCE_DATE_EPOCH` being threaded through every tool in the chain. Outside a Git repository, both modes fall back to a fixed reference date rather than failing the step. It's opt-in: existing workflows that don't set `mtime` see no change in behavior. **`action: replace` + `mtime` is idempotent — `action: update` is not.** With `replace`, the same `source` produces the same bytes on every rerun, because `replace` always rebuilds from scratch. `update` doesn't get that guarantee even with `mtime` set: its existing entries keep whatever mtime/mode a prior write already gave them, and only the entries a given `update` call adds or refreshes get normalized — so the final bytes depend on the archive's history, not just `source`'s current content. If you need the idempotent guarantee, use `replace`. ## Get Involved See the [archive step reference](/workflows/steps/type/archive) for the full field list, and the [hooks reference](/stacks/hooks#kind-step-run-a-step-type) for using it as a lifecycle hook. Questions or ideas? Join us in the [Cloud Posse community Slack](https://cloudposse.com/slack). --- ## Artifactory Store Fix and Documentation Corrections Fixed an issue where retrieving values from Artifactory stores would fail when using nested paths, and corrected store documentation to accurately reflect supported backends. ## What Changed ### Artifactory Store Fix When using Artifactory as a store backend with nested directory structures, retrieving values would fail with a "file not found" error even though the data existed. This happened because files were being downloaded to unexpected locations within temporary directories. For example, storing a value at `dev/myapp/private_ip` and then trying to retrieve it would fail because Atmos was looking for the file in the wrong place. This is now fixed. Retrieving values from Artifactory works correctly regardless of how deeply nested your paths are. ### Documentation Corrections The store documentation had several issues that could cause confusion: - **Wrong field names**: Examples showed `backend` and `config` instead of the correct `kind` and `options` - **Wrong store selectors**: Examples mixed old `type` names with the newer slash-style `kind` names - **Non-existent backends**: Vault was documented before it was implemented - **Missing backends**: Azure Key Vault and Google Secret Manager were implemented but not documented - **Artifactory setup**: Added guidance on using Generic repository type in JFrog Artifactory ## Why This Matters If you've been struggling to get Artifactory working as a store backend, especially with nested paths, this fix should resolve those issues. And if you've been confused by store configuration examples that didn't work, the documentation now reflects the actual supported backends and correct configuration format. For usage and configuration, see [Stores Configuration](/cli/configuration/stores). ## Get Involved Have questions or feedback? Join us on [Slack](https://slack.cloudposse.com/) or open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Ask AI: Conversational Search for Atmos Documentation Finding information in documentation shouldn't require knowing the exact terminology or page structure. With **Ask AI**, you can now ask natural language questions about Atmos and get intelligent, contextual answers—powered by [Algolia DocSearch v4](https://docsearch.algolia.com/docs/v4/askai/) and ChatGPT. ## How It Works When you open the search dialog (press `/` or `Cmd+K`), you can seamlessly switch between traditional keyword search and a chat-style assistant that understands your questions. **Key features:** - **Natural language queries** - Ask questions like "How do I configure AWS SSO authentication?" or "What's the difference between stacks and components?" - **Contextual answers** - Responses are generated from the actual documentation content, not generic AI knowledge - **Source citations** - Every answer includes links to the specific documentation pages it drew from - **Seamless experience** - Switch between keyword search and AI assistant without leaving the search dialog. ## Under the Hood Ask AI uses Algolia's integration with large language models (LLMs) to understand your questions and find relevant information from the indexed documentation. The assistant is specifically trained on Atmos documentation, so it understands: - Atmos CLI commands and their flags - Stack configuration patterns and YAML syntax - Authentication providers and identity management - Workflows, custom commands, and extensibility patterns - Design patterns and best practices ## Try It Now Visit [atmos.tools](https://atmos.tools) and press `/` or `Cmd+K` to open the search dialog. Try asking questions like: - "How do I set up AWS SSO with Atmos?" - "What is the difference between imports and inheritance?" - "How do I create a custom workflow?" - "What YAML functions are available?" The AI assistant will provide detailed answers with direct links to the relevant documentation pages. ## Technical Details This feature is enabled by upgrading to Docusaurus 3.9.2 and DocSearch v4, which adds native support for Algolia's Ask AI feature. The assistant is configured with an understanding of Atmos as a DevOps and cloud automation tool, ensuring responses are relevant and technically accurate. For usage and configuration, see [atmos ai ask](/cli/commands/ai/ask). ## Get Involved Have feedback on Ask AI? We'd love to hear how it's helping you navigate the documentation: - [Open an issue](https://github.com/cloudposse/atmos/issues) for bugs or feature requests - [Join our Slack](https://slack.cloudposse.com/) to discuss with the community - [Star the repo](https://github.com/cloudposse/atmos) if you find Atmos helpful --- ## Atmos Now Builds Atmos Atmos now builds itself through a first-class Atmos command: ```bash atmos build --target linux ``` We replaced the old Make-based build and CI entrypoints with Atmos custom commands to improve the developer experience and dogfood our own workflow engine. Build and test workflows now expose the flags developers actually care about, such as the target platform, while Atmos owns the shell details, cross-compilation wiring, output paths, and CI glue behind the command. ## What Changed The repository's development commands now live in `.atmos.d/` command groups instead of Make targets. The root `atmos.yaml` stays focused on project configuration, while the command surface is split by concern: - `atmos build ...` for dependencies, binaries, versions, and docs generation. - `atmos test` plus mode flags for short, acceptance, coverage, and race-test workflows. - `atmos lint ...` for changed-file linting, custom lintroller rules, go.mod checks, and link checks. - `atmos check ...`, `atmos format ...`, and `atmos cache ...` for repository maintenance workflows. - `atmos screengrabs ...` for website screengrab generation. Makefiles remain only as migration guards. If someone runs an old target, the Makefile exits and points them to the Atmos command that replaced it. ## A Cleaner Command Surface The result is intentionally plain. The repository now has command groups that read like the workflows contributors actually run: ```bash atmos build deps atmos build --target linux atmos test acceptance --cover atmos lint --changed atmos screengrabs all ``` This keeps the developer contract narrow. The command can grow flags like `--target` without forcing every contributor or CI job to understand the lower-level build minutia. Atmos handles the parsing, defaults, and orchestration once, and every caller gets the same behavior. ## Why It Matters This is dogfooding in the useful sense: Atmos exercises its own custom command engine for the daily loops that maintain Atmos. That gives contributors one command vocabulary locally and in CI, and it gives us immediate feedback when custom commands need to become better. It also removes a layer. CI now calls grouped Atmos commands such as `atmos build deps`, `atmos build --target linux`, and `atmos test acceptance --cover`. The screengrab workflow calls `atmos screengrabs all`. The install script has its own Linux, macOS, and Windows smoke test, so the installer path gets exercised directly too. ## Try the Pattern If you are maintaining a repo with an aging Makefile, `.atmos.d/` is a good place to split the command surface by concern. Keep project configuration in `atmos.yaml`; keep operational commands in grouped files such as `.atmos.d/build.yaml`, `.atmos.d/test.yaml`, and `.atmos.d/lint.yaml`. For the general command schema, see the [custom commands documentation](/cli/configuration/commands). For a smaller standalone example, browse the [custom commands example](/examples/custom-commands). --- ## See Atmos in Action Start with the recording. That is the point of this update: the docs now have short terminal casts for the parts of Atmos that are easier to understand by watching them run. No big setup. No long pitch before the useful part. Open a page, press play, and see what happens. ## New Recordings The first set covers the runs people usually want to inspect first: - plan - deploy - output - source pull - stack list - component details - stack details - local AWS demo Each cast is meant to answer the same question: what will I see when I run this? ## Plan, Deploy, Output The plan cast starts the set. The next two show the follow-up runs: Together, they show the basic path from preview to apply to result. ## Source Pull This cast shows the source pull by itself: It is intentionally plain. You can see what is fetched and what the terminal prints when it finishes. ## Look Around First Sometimes the next step is to inspect the project before changing anything. These casts show what Atmos sees before you ask it to do anything else. ## Local AWS Demo The longest cast shows a full local AWS run: It brings the local service up, waits for it, runs against it, prints the result, and shuts it down. ## Kept Current The recordings are made by Atmos: [github.com/cloudposse/atmos/blob/main/demo/casts/atmos.yaml](https://github.com/cloudposse/atmos/blob/main/demo/casts/atmos.yaml) That file builds the binary, prepares the demo files, records the casts, copies them into the website, and checks the result. The docs get a visible example. The repo gets a repeatable way to refresh it. For usage and configuration, see [atmos workflow](/cli/commands/workflow). --- ## Atmos Git: A Foundational Capability for GitOps Pipelines Atmos now treats Git as a foundational platform capability — on par with Toolchain, Auth, and Hooks — built to enable GitOps workflows where you need to commit artifacts to a source of truth: deployment repos consumed by Argo CD or Flux, provider lock files, rendered manifests. Define managed repositories once in `atmos.yaml`, then clone, inspect, diff, commit, and push them through a new [`atmos git`](/cli/commands/git/usage) command group, or publish generated artifacts automatically on lifecycle events with the new `git` hook kind. ## What Changed A new top-level `git` section in `atmos.yaml` declares managed repositories by logical name: ```yaml git: repositories: flux-deploy: uri: https://github.com/acme/flux-deploy.git branch: main auth: identity: platform-admin commit: signing: auto author: name: atmos[bot] email: atmos-bot@acme.com ``` On top of that foundation: - **`atmos git` commands** — `init`, `clone`, `pull`, `status`, `diff`, `commit`, `push`, and `list`, with `--all` bulk operations across every configured repository. Clone accepts configured names, plain URLs, or go-getter style URIs (`git::https://...?ref=main&depth=1`) — the same syntax you already use in vendoring. `init`, `clone`, `pull`, and `push` pass arguments after `--` verbatim to the underlying git invocation, so uncommon git flags still compose with Atmos safety rules. - **Bootstrap from scratch or from a template** — [`atmos git init`](/cli/commands/git/init) creates a configured repository whose remote has no content yet: workdir, initial branch, and remote wired up, ready to commit and push. `--from=` seeds it from a template (one fresh initial commit), and `--from= --keep-history` migrates an existing repository while keeping the source pullable as an `upstream` remote. - **A `git` hook kind** — publish generated files on lifecycle events like `after.terraform.apply`, committing to the current repository or a managed deployment repository, with templated commit messages and provenance trailers (`Atmos-Stack`, `Atmos-Component`). - **Local Git hook shims** — [`atmos git hooks install`](/cli/commands/git/hooks/install) wires `.git/hooks/*` (pre-commit, commit-msg, ...) to Atmos workflows and custom commands, replacing Husky-style glue. - **Native CI checkout** — in GitHub Actions with `ci.enabled: true`, a bare [`atmos git clone`](/cli/commands/git/clone) infers the current repository from CI metadata and clones it into the workspace, replacing `actions/checkout`. GitHub STS credentials flow to every Git subprocess automatically via `GIT_CONFIG_*`. ## Why This Matters GitOps workflows have always needed glue: scripts to render manifests into deployment repos, commit them, handle push races, and wire up credentials. Atmos already owned rendering, lifecycle events, and credentials — now it owns the Git operations between them: - **Safety rules are centralized.** Fast-forward-only pulls, no force pushes ever, path-scoped commits that refuse to touch unrelated dirty files, and automatic retry-with-rebase when a concurrent publisher wins the push race. - **Commits work in CI out of the box.** Author identity is injected per invocation (no `user.name` configuration on runners), and commit signing is configurable per repository. - **Clones are cache-friendly.** Managed repositories live under the Atmos XDG cache root, so the native CI cache restores them across runs — and clone is defined as _reconcile_ (fetch and fast-forward), so a stale restored clone is just a faster clone. ## What This Is — and Isn't Atmos owns the **publishing side** of GitOps: render → diff → commit → push, with centralized safety rules. Reconciliation stays where it belongs — Argo CD or Flux pulls from the repository, or your CI applies on merge. There are no agents and no drift-correction loop in Atmos itself; it's the producer feeding your reconciler. And this isn't a replacement for the [GitHub Actions integration](/ci) you may already run plan/apply pipelines with — it's the Git plumbing those pipelines use. ## How to Use It A real problem every Terraform team hits: CI runs `terraform init`, the provider lock file (`.terraform.lock.hcl`) changes — a provider was upgraded, or hashes for a new platform were added — and that change silently drifts because nothing commits it back. With the `git` hook kind, Atmos commits it after apply: ```yaml components: terraform: vpc: hooks: commit-lockfile: events: - after.terraform.apply kind: git # No repository: configured — the hook operates on the # current repository, the one your pipeline checked out. commit: message: "chore: update provider lock file for {{ .component }} in {{ .stack }}" paths: - components/terraform/vpc/.terraform.lock.hcl push: true ``` When `repository:` is omitted, the hook targets the **current repository** — no special name needed. The commit is path-scoped (it refuses to sweep up unrelated dirty files), the author is injected so it works on runners with no Git identity, and if another job pushed first, the push retries with a rebase. If nothing changed, it's a clean no-op. To publish into a _different_ repository instead — rendered manifests into an Argo CD / Flux deployment repo — add `repository: flux-deploy` pointing at a configured entry under `git.repositories`. Imperatively, the same operations are first-class commands: ```shell atmos git clone --all # reconcile every configured repository atmos git diff flux-deploy # preview what would change — before committing atmos git commit flux-deploy --message="Render argocd for prod" --path=clusters/prod atmos git push flux-deploy ``` Here's what reconciling a configured repository looks like end to end: [View the full example](/examples/gitops) And in GitHub Actions, the checkout step becomes Atmos-native: ```yaml steps: - uses: cloudposse/github-action-setup-atmos@v2 - run: atmos git clone # replaces actions/checkout - run: atmos git clone --all # warm all deployment repos ``` See the [`atmos git` command reference](/cli/commands/git/usage) and the [Git configuration docs](/cli/configuration/git) for the full surface, including clone depth controls, signing modes, and push retry tuning. ## Bootstrapping a Repository — `atmos git init` Cloning assumes the repository already exists. Often it doesn't yet: you're standing up a _new_ GitOps deployment repo for a new cluster, a new tenant, a new environment. `atmos git init` is clone's counterpart — it creates a configured repository whose remote has no content, on the configured branch, with the remote wired up, ready for [`atmos git commit`](/cli/commands/git/commit) and [`atmos git push`](/cli/commands/git/push): ```shell atmos git init flux-prod # empty repo, origin -> configured uri, ready to push ``` The interesting part is `--from`, which turns init into a repository _factory_. Point it at a template and Atmos instantiates a fresh repository from it: ```shell # Stamp out a new deployment repo from your org's GitOps template. # Fresh history: one clean initial commit, no link back to the template. atmos git init flux-prod --from=https://github.com/acme/gitops-template.git ``` This is the pattern platform teams reach for constantly: a golden GitOps template (Flux/Argo layout, Kustomize bases, policy guardrails) that every new cluster or tenant repo starts from. Instead of "clone the template, delete `.git`, re-init, fix the remote, commit" by hand, it's one command — wired to the repository you already declared in `atmos.yaml`, with the right identity, branch, and signing applied. And when you're _migrating_ an existing repository to a new home, `--keep-history` preserves the full commit history and keeps the source reachable as an `upstream` remote, so you can keep pulling updates from it: ```shell # Migrate, preserving history; pull future template updates with `git pull upstream`. atmos git init flux-prod --from=https://github.com/acme/old-flux.git --keep-history ``` Same declarative model as the rest of `atmos git`: the repository, its identity, and its conventions are defined once in `atmos.yaml`, and `init` brings it into existence. ## Get Involved This is the foundation of a larger GitOps story — pull-request-based publishing for protected branches and Kubernetes deployment-repository provisioning are next. Read the PRD in `docs/prd/git-ops.md`, try it out, and share feedback in [GitHub Discussions](https://github.com/cloudposse/atmos/discussions). --- ## Introducing the Atmos Media Kit The official [Atmos Media Kit](/media-kit) is now available with downloadable logo assets, usage guidance, brand colors, and product language for teams writing about or building integrations with Atmos. ## What's Included The [media kit](/media-kit) gives contributors, partners, and community teams a single place to find approved Atmos brand assets: - **Logo downloads** for the Atmos mark and wordmark, including light and dark variants. - **Powered by Atmos badges** for projects that want to show they run on Atmos. - **Brand colors** sourced from the official Atmos logo artwork, plus separate animated accent colors for the web treatment. - **Usage guidelines** for sizing, clear space, backgrounds, and preserving the original logo proportions. - **Product facts and links** for consistent references to Atmos, Cloud Posse, GitHub, and the community. ## Why This Matters Atmos shows up in documentation sites, internal developer portals, integration pages, conference decks, and open source READMEs. Without a canonical source, teams either copied assets from the website or recreated them by hand. The media kit makes those references easier to keep consistent. Use the provided files directly, keep the canonical green logo palette for brand-forward placements, and reserve the animated accent colors for the web treatment. ## Get the Assets Visit the [Atmos Media Kit](/media-kit) to browse the assets or download the complete ZIP. --- ## Just-in-time GitHub tokens for CI with Atmos Pro STS Fetching private Terraform modules, Atmos `source:` components, and vendored artifacts in CI has always meant handing a long-lived, over-privileged GitHub credential to your pipeline — a PAT, a machine user, or a deploy key, sitting in a CI secret. Atmos Pro **STS** replaces that with just-in-time, least-privilege, short-lived GitHub tokens that are minted at the start of a run and revoked at the end — with **zero `.tf` changes**. ## What Changed Two new [`atmos auth`](/cli/commands/auth/usage) kinds, modeled on the existing `aws/ecr` and `aws/eks` integrations: - **Provider `kind: atmos/pro`** — authenticates the Atmos CLI _to Atmos Pro_ by federating the GitHub Actions runner's OIDC token (no secrets required) into an Atmos Pro session. v1 is OIDC-only, so it works out of the box in GitHub Actions. - **Integration `kind: github/sts`** — on login it asks Atmos Pro to mint scoped, short-lived GitHub App installation tokens, materializes them as per-owner git URL rewrites for child processes, and revokes them when the command finishes (in CI) and on [`atmos auth logout`](/cli/commands/auth/logout). We also added **`ATMOS_PRO_GITHUB_TOKEN`**, a convenience env var that Atmos-native git operations (vendoring, source provisioning, go-getter) prefer over `ATMOS_GITHUB_TOKEN`/`GITHUB_TOKEN`. And — because the `atmos/pro` identity isn't something a stack ever "claims" the way it claims an AWS/Azure/GCP identity — **in CI, `github/sts` now provisions automatically the first time Atmos is about to read a remote git source.** No explicit [`atmos auth login`](/cli/commands/auth/login) step is required: `atmos vendor pull`, a `source:` component, a remote `import:`, or `terraform init` of a private module each transparently trigger the mint. It's gated on running in CI plus having the `atmos/pro` + `github/sts` config present (`auto_provision`, default `true`), and minted tokens are reused across invocations in the same job until they expire. ## Why This Matters - **No standing credentials.** Tokens live only for the duration of a run. Nothing long-lived sits in a CI secret waiting to be leaked. - **Least privilege, deny-by-default.** Identity is derived server-side from your workspace's trust policies — the CLI never asks for a specific repo. Repos you aren't trusted for are simply excluded (with a clear reason in the logs). - **Zero `.tf` changes.** The same minted token transparently authenticates [`atmos vendor pull`](/cli/commands/vendor/pull), an Atmos `source:` component, and `terraform init` of a private `git::https://…` module. Terraform's native `git` honors the injected `GIT_CONFIG_*` rewrites, so nothing in your Terraform code changes. - **Multi-org by design.** Because tokens are minted per `(installation, permission-set)`, a single run can pull from several orgs at once — something a single token env var can't express. ## How to Use It Declare the provider, a passthrough identity, and the integration. The integration binds directly to the provider via `via.provider`: ```yaml auth: providers: atmos-pro: kind: atmos/pro spec: workspace_id: # or ATMOS_PRO_WORKSPACE_ID identities: atmos-pro: kind: atmos/pro via: { provider: atmos-pro } integrations: github-sts: kind: github/sts via: { provider: atmos-pro } spec: git_config_mode: env # or "file" to keep tokens off the environment revoke_on_exit: true # set false to keep creds for a separate CI step ``` Your workflow only needs `permissions: id-token: write`. In CI you don't even need an explicit login — the first remote read provisions tokens for you: ```bash atmos vendor pull # first remote read → tokens minted automatically atmos terraform plan ... # terraform init of private modules just works ``` You can still drive it explicitly (locally, or to control timing/teardown): ```bash atmos auth login --identity atmos-pro # mints scoped GitHub tokens atmos vendor pull # private repos just work atmos terraform plan ... # terraform init of private modules just works atmos auth logout # revokes the tokens ``` ### Hand the token to any GitHub-token consumer Beyond Atmos's own git operations, you can hand the minted token to **anything** that takes a GitHub token — `gh`, `actions/checkout`, or the REST API — all built into the CLI. Set `token_env` on the integration to pick the variable name, then mint in one step and consume in the next with [`atmos auth env --format=github`](/cli/commands/auth/env): ```yaml # atmos.yaml auth: integrations: github-sts: kind: github/sts via: { provider: atmos-pro } spec: token_env: GH_TOKEN # export the raw token as $GH_TOKEN ``` ```yaml # .github/workflows/example.yml — only id-token: write required permissions: id-token: write steps: - name: Mint a GitHub token run: atmos auth env --identity=atmos-pro --format=github --login # writes GH_TOKEN to $GITHUB_ENV - uses: actions/checkout@v4 with: repository: acme/private-repo token: ${{ env.GH_TOKEN }} - run: gh repo view acme/private-repo env: GH_TOKEN: ${{ env.GH_TOKEN }} ``` Use `token_env: GH_TOKEN_{owner}` to export one token per owner in multi-org runs. For usage and configuration, see [Atmos Pro](/cli/configuration/settings/pro). ## Get Involved See the [`atmos/pro` provider](/cli/configuration/auth/providers#atmos-pro) and [`github/sts` integration](/cli/configuration/auth#github-sts-atmos-pro) docs for the full configuration reference, and the [Atmos Pro STS PRD](https://github.com/cloudposse/atmos/blob/main/docs/prd/atmos-pro-sts.md) for the design and roadmap. Questions or ideas? Join us in the [Cloud Posse community](https://cloudposse.com/slack). --- ## Atmos Profiles: One Configuration, Multiple Contexts Stop fighting with different Atmos configurations for development, CI/CD, and production. Profiles let you switch contexts with a single flag while keeping your core configuration consistent. ## The Problem Here's a scenario you've probably faced: You want Atmos to behave one way in CI, another way for developers, and yet another way for platform engineers. You need: - **Different default identities** - Platform engineers use admin permissions, developers use sandbox access - **Different error reporting** - Sentry enabled in CI for verbose error tracking, but disabled in production to avoid noise - **Different terminal settings** - CI needs plain text output with no color, developers want rich formatting - **Different authentication** - Interactive AWS SSO for developers, GitHub OIDC for automation Sure, you _could_ handle this with environment variables scattered across GitHub Actions, `.bashrc`, and CI configs: ```bash # Every single time in CI - export all these env vars export ATMOS_IDENTITY=github-oidc export ATMOS_LOGS_LEVEL=info export NO_COLOR=1 # Plus Sentry config, terminal settings, etc. atmos terraform apply vpc -s prod # Every single time as a developer - different env vars export ATMOS_IDENTITY=developer-sandbox export ATMOS_LOGS_LEVEL=debug # Plus terminal width, color settings, etc. atmos terraform plan vpc -s dev ``` Either way, you're juggling a complex combination of overrides that aren't version-controlled or easily shared. How do you codify these contexts so they're consistent, version-controlled, and easy to switch between? ## The Solution: Atmos Profiles Profiles let you codify what it means to have a certain context and change all the settings of Atmos to match that context. No flags to remember. No environment variables to export. Just set it once and forget it. ```bash # Developer: One flag replaces 5+ configuration settings atmos terraform plan vpc -s dev --profile developer # CI: Set once in your workflow export ATMOS_PROFILE=ci atmos terraform apply vpc -s prod # Platform Engineer: One flag, consistent settings atmos terraform plan vpc -s prod --profile platform-admin ``` Same command, same component, same stack—but the profile handles your identity, logging, terminal settings, error reporting, and everything else. It's all version-controlled configuration, not scattered flags and environment variables. ## How It Works Profiles are simply directories containing YAML configuration files that override your base `atmos.yaml` settings. Atmos discovers profiles from multiple locations with clear precedence rules: 1. **Configurable location** (set in `profiles.base_path`) 2. **Project-hidden** (`.atmos/profiles/`) 3. **User directory** (`~/.config/atmos/profiles/`) 4. **Project directory** (`profiles/`) ### Example: Developer Profile Create a profile for local development with a sandbox identity: ```bash # profiles/developer/auth.yaml auth: defaults: identity: developer-sandbox # Set default identity for this profile identities: developer-sandbox: kind: aws/permission-set via: provider: aws-sso principal: account_id: "123456789012" permission_set: DeveloperAccess ``` ```bash # profiles/developer/settings.yaml logs: level: Debug settings: terminal: max_width: 120 ``` Activate it with: ```bash atmos terraform plan vpc -s dev --profile developer ``` Or set it as your default: ```bash export ATMOS_PROFILE=developer atmos terraform plan vpc -s dev ``` ### Example: CI Profile Create a profile for GitHub Actions with OIDC authentication, Sentry error tracking, and CI-friendly output: ```bash # profiles/ci/auth.yaml auth: defaults: identity: github-oidc # Set default identity for CI identities: github-oidc: kind: aws/role via: provider: aws-oidc audience: sts.amazonaws.com principal: role_arn: arn:aws:iam::123456789012:role/GitHubActionsRole ``` ```bash # profiles/ci/settings.yaml logs: level: Info errors: sentry: enabled: true # Enable Sentry in CI for error tracking environment: ci settings: terminal: color: false # Plain text for CI logs max_width: 80 markdown: code: style: ascii ``` Set once in your workflow: ```yaml # .github/workflows/deploy.yml env: ATMOS_PROFILE: ci # Set it once, forget it jobs: deploy: steps: - run: atmos terraform apply vpc -s prod # No flags needed - profile handles everything ``` ## Combining Profiles Need to layer multiple contexts? Stack profiles from left to right, with rightmost values winning: ```bash # Base security settings + developer overrides atmos terraform plan vpc -s dev --profile security-baseline --profile developer ``` This is powerful for composing shared policies with role-specific settings. ## Managing Profiles List available profiles: ```bash atmos profile list ``` View profile details: ```bash atmos profile show developer ``` The output shows you exactly what settings the profile provides, where it's located, and how to use it. See it in action — listing available profiles and inspecting one in detail: [View the full example](/examples/config-profiles) ## Why This Matters Before profiles, you had environment variables and config scattered everywhere: - `.bashrc` for developers with `ATMOS_IDENTITY`, `ATMOS_LOGS_LEVEL`, etc. - GitHub Actions secrets for CI with different values - GitLab CI variables for other pipelines - Different dotfiles for different team members - `atmos.yaml` overrides that vary by machine - No version control, no consistency This approach meant: - ❌ **Settings aren't codified** - Tribal knowledge about which flags to use - ❌ **Easy to get wrong** - Forget one flag, wrong behavior - ❌ **Can't share configurations** - Everyone maintains their own setup - ❌ **Onboarding is painful** - New team members reinvent the wheel - ❌ **Drift over time** - CI uses different settings than developers With profiles, you: - ✅ **Codify each context once** - Developer, CI, platform engineer profiles all version-controlled - ✅ **Set it and forget it** - `export ATMOS_PROFILE=ci` or `--profile developer`, done - ✅ **No flag juggling** - One profile replaces dozens of configuration overrides - ✅ **Version-controlled consistency** - Commit profiles, share with the team - ✅ **Compose when needed** - Layer profiles for complex scenarios (security baseline + role overrides) - ✅ **Onboard instantly** - New team members use proven profiles, no setup required ## Real-World Example Here's what a complete profile setup looks like for a team: ``` profiles/ ├── developer/ # Local development │ ├── auth.yaml # Sandbox identity via AWS SSO │ └── settings.yaml # Debug logs, wide terminal ├── ci/ # GitHub Actions │ ├── auth.yaml # OIDC identity for automation │ └── settings.yaml # Plain output, no color ├── platform-admin/ # Platform engineers │ ├── auth.yaml # Admin identity via AWS SSO │ └── settings.yaml # Warning-level logs only └── audit/ # Security reviews ├── auth.yaml # Read-only identity └── settings.yaml # Full audit logging ``` Each team member activates the profile that matches their role: - **Developers** use the `developer` profile → get sandbox identity with debug logging - **CI workflows** use the `ci` profile → get OIDC identity with plain text output - **Platform engineers** use the `platform-admin` profile → get admin identity with minimal logs - **Security audits** use the `audit` profile → get read-only identity with comprehensive logging No duplicated configuration. No environment variable sprawl. No confusion about which identity to use. Just clear, version-controlled contexts that do exactly what they say. ## Learn More - **Example Configuration**: See the complete working example in `examples/config-profiles/` with developer, CI, and production profiles - **Profile Commands**: Use [`atmos profile list`](/cli/commands/profile/profile-list) and [`atmos profile show `](/cli/commands/profile/profile-show) to discover and inspect profiles - **PRD Documentation**: See `docs/prd/atmos-profiles.md` for complete technical specification ## Get Involved - [GitHub Pull Request](https://github.com/cloudposse/atmos/pull/1766) - Share your feedback in the [Atmos Community Slack](https://cloudposse.com/slack) --- ## Compose YAML Functions in Templates with atmos.Resolve Atmos adds a new template function, `atmos.Resolve`, that evaluates any [Atmos YAML function](/functions/yaml) — like [`!git.repository`](/functions/yaml/git.repository), [`!exec`](/functions/yaml/exec), [`!store`](/functions/yaml/store), or [`!terraform.output`](/functions/yaml/terraform.output) — at template-render time. This lets you **compose** a YAML function's result with other strings and template variables in a single value. ## What Changed A YAML tag owns the entire scalar, so you cannot write `"!git.repository/{{ ... }}"`. And because Atmos evaluates Go templates **before** YAML functions, referencing a function through an intermediate variable in a plain template doesn't work either — at template time the value is still the unresolved tag string `"!git.repository"`. `atmos.Resolve` closes that gap. It runs the YAML-function processor on demand during template rendering, so the resolved value is available where you can concatenate it with other text: ```yaml key: '{{ atmos.Resolve "!git.repository" }}/something' ``` ## Why This Matters The motivating use case is prefixing the Terraform `workspace_key_prefix` (or any value) with the repository slug, without resorting to `!exec` and shell concatenation: ```yaml settings: context: repo: "!git.repository" components: terraform: vpc: backend: s3: workspace_key_prefix: '{{ atmos.Resolve .settings.context.repo }}/{{ or .metadata.name .metadata.component }}' ``` In a repository whose `origin` remote is `https://github.com/cloudposse/atmos.git`, this resolves to `cloudposse/atmos/vpc`. ## How to Use It `atmos.Resolve` works with every Atmos YAML function. A plain (untagged) string is returned unchanged, so it's safe to pass values that may or may not contain a YAML function: ```yaml vars: bucket: '{{ atmos.Resolve "!env BUCKET_PREFIX" }}-state' ``` It runs during the template phase, giving it the same evaluation semantics as [`atmos.Component`](/functions/template/atmos.Component). Reach for a plain YAML tag when you don't need composition, and use `atmos.Resolve` when you do. ## Get Involved See the [`atmos.Resolve`](/functions/template/atmos.Resolve) documentation for the full reference. --- ## Get the exact stack-manifest schema for the Atmos you have installed Editors, CI pipelines, and offline environments that want to validate stack manifests locally have had one option: fetch the JSON Schema from `atmos.tools` over the network and hope it matches whatever `atmos` binary you actually have installed. There was no way to just ask your own binary for its schema. ## The Fix A new command, [`atmos stack schema`](/cli/commands/stack/stack-schema), prints the JSON Schema Atmos uses to validate stack manifests — the exact one built into the binary you're running, not a version fetched from the internet. Pass a path and it writes the schema to a file instead of stdout. This is useful for: - **Offline/air-gapped environments** — no network call needed, since the schema comes straight out of the binary already on disk. - **IDE setup** — point your editor's YAML language server at a local file instead of a URL. - **Version certainty** — the output always matches the `atmos` version you ran it with, so there's no risk of validating against a schema that's ahead of or behind your binary. ## How to Use It ```shell # Print the schema to stdout. atmos stack schema # Write it to a file for your editor or CI to use. atmos stack schema ./atmos-manifest.json ``` It lives under the existing [`atmos stack`](/cli/commands/stack/usage) command group, alongside the other stack-manifest commands (`get`, `set`, `delete`, `format`, `config`). For usage and configuration, see [atmos stack schema](/cli/commands/stack/stack-schema). --- ## Introducing the Atmos Version Manager Atmos now supports automatic version switching, making it easy to pin projects to specific Atmos versions and ensure consistency across teams. ## What Changed Atmos can now automatically switch to a specific version when running commands. This works through three mechanisms: 1. **`--use-version` flag**: Run any command with a specific version ```bash atmos --use-version 1.199.0 terraform plan -s mystack ``` 2. **`ATMOS_VERSION` environment variable**: Set the version for all commands ```bash export ATMOS_VERSION=1.199.0 atmos terraform plan -s mystack ``` 3. **`version.use` in atmos.yaml**: Pin your project to a specific version ```yaml version: use: "1.199.0" ``` When a version is specified, Atmos will: - Check if that version is installed - Install it automatically if needed (using the toolchain installer) - Re-execute itself with the specified version ## Why This Matters **Team Consistency**: Everyone on your team uses the same Atmos version, eliminating "works on my machine" issues. **CI/CD Reliability**: Pin your pipelines to specific versions for reproducible builds. **Gradual Upgrades**: Test new versions in development before rolling them out to production workflows. ## How to Use It The simplest approach is adding `version.use` to your `atmos.yaml`: ```yaml version: use: "1.199.0" ``` Now every `atmos` command in that project will use version 1.199.0, regardless of which version is installed globally. You can also combine this with [Atmos profiles](/cli/configuration/profiles) for environment-specific versions: ```yaml # .atmos.d/dev.yaml version: use: "1.201.0" # Latest in dev # .atmos.d/prod.yaml version: use: "1.199.0" # Stable in prod ``` ## Get Involved We'd love to hear how you're using version management in your workflows. Join the discussion on [Slack](https://slack.cloudposse.com) or open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## One Catalog for Every Version Pin: the Atmos Version Tracker Your infrastructure's versions live everywhere except one place. `actions/checkout@v4` in a dozen workflow files, `TOFU_VERSION=1.9.0` in a Dockerfile, `nginx:1.27` in a stack, `kubectl` wherever your bootstrap script says. When a version needs to move, you grep and hope. When it shouldn't move, nothing enforces that either — and every unpinned mutable tag is a supply-chain incident waiting for its moment. The Atmos Version Tracker gives all of those versions a single source of truth: a catalog in `atmos.yaml`, a deterministic lock file, policy-driven updates, and file managers that rewrite your workflows, Dockerfiles, and rendered files from the lock. Here's the same idea applied to vendored component versions: [View the full example](/examples/demo-component-versions) ## The Problem Version pins are configuration, but nobody manages them like configuration: - **No source of truth.** The same tool version is repeated across workflow YAML, Dockerfiles, stack manifests, and CI scripts. Environments drift silently because there is nothing to drift _from_. - **Bolt-on updaters don't understand your stacks.** Renovate and Dependabot see files, not Atmos configuration. They can't resolve a version into a stack, don't know your tracks or environments, and produce PR noise you tune with regex managers instead of policy. - **Mutable tags are a supply-chain risk.** `actions/checkout@v4` and `nginx:latest` can change underneath you. Pinning to SHAs by hand is the fix everyone agrees on and nobody keeps up with. ## The Fix The Atmos Version Tracker makes external versions first-class Atmos configuration. You declare a catalog under `version:` in `atmos.yaml`; Atmos owns resolution, locking, policy, and file rewriting: ```yaml title="atmos.yaml" version: track: prod dependencies: checkout: ecosystem: github-actions package: actions/checkout desired: v6 update: pin: sha # lock and render the immutable commit SHA opentofu: ecosystem: toolchain package: opentofu desired: "~1.10" nginx: ecosystem: oci package: library/nginx desired: "1.29.0" tracks: prod: dependencies: nginx: desired: "1.29.1" files: - manager: github-actions paths: [.github/workflows/*.yaml] - manager: marker paths: [Dockerfile] - manager: template paths: ["**/*.tmpl"] ``` Resolved versions land in a committed `versions.lock.yaml`, so local runs and CI are deterministic. Updates are policy-driven — strategy caps (`major`/`minor`/`patch`), cooldown windows (`14d`, `2w`), include/exclude rules, and prerelease policy — and every version held back by policy is reported with the reason instead of silently skipped: ```shell atmos version track update ``` In an interactive terminal this renders as a styled table. Use `--format=csv` or `--format=tsv` for delimited output; piped table output remains a table: ```shell Track,Name,From,To,Updated,Reason prod,checkout,v6.1.0,v6.2.0,true, prod,nginx,1.29.0,,false,"1.30.0 released 3d ago; cooldown 14d has not elapsed" ``` File managers then rewrite your actual project files from the lock. Pinned entries get the immutable SHA with the human-readable version as a trailing comment — the same round-trip convention Renovate and Dependabot use: ```yaml title=".github/workflows/ci.yaml" - uses: actions/checkout@8edcb1bdb4e267140fa742c62e395cd74f332709 # v6.1.0 ``` Any text file with comments can carry a managed version via a marker annotation: ```dockerfile title="Dockerfile" # atmos:version opentofu ENV TOFU_VERSION=1.10.6 ``` And comment-hostile formats (like JSON) render from `*.tmpl` templates with the `.version` context. In stacks, [`!version name`](/functions/yaml/version) and `{{ .version.name }}` resolve from the lock, so versions stop being copy-pasted into stack manifests at all. ## How to Use It Add an entry (the ecosystem is inferred from the package), lock, and apply: ```shell atmos version track add checkout --package=actions/checkout --pin=sha atmos version track lock # resolve desired versions into versions.lock.yaml atmos version track apply # rewrite managed files from the lock ``` Then gate CI on the whole thing staying honest: ```yaml title=".github/workflows/version-check.yaml" steps: - uses: actions/checkout@v6 - run: atmos version track apply --check # fail if any managed file is stale - run: atmos version track verify # lock fresh AND files current ``` [`atmos version track status`](/cli/commands/version/track/status) shows where you stand at any time — including `newer-available (blocked)` when a newer version exists upstream but your cooldown or strategy is deliberately holding it back. That's a passing state, not a failure: the locked version is exactly what your policy wants deployed. See the [`atmos version track` command reference](/cli/commands/version/track) and [Managed Versions configuration](/cli/configuration/version/managed-versions) for the full surface, or try the runnable [`examples/version-tracker`](https://github.com/cloudposse/atmos/tree/main/examples/version-tracker) example. For usage and configuration, see [Version](/cli/configuration/version). ## Get Involved If you've been stitching together Renovate configs to approximate this, we'd love to hear which datasources and ecosystems you need next. Open a discussion or issue in the [Atmos repo](https://github.com/cloudposse/atmos), or join us in the [Cloud Posse Slack](https://cloudposse.com/slack). --- ## atmos.yaml Gets a JSON Schema That Can't Go Stale Editing `atmos.yaml` has always been a matter of trust. Misspell a key, indent a section one level too deep, or put a string where a map belongs, and nothing tells you — unknown keys are silently ignored, and you discover the mistake only when Atmos doesn't behave the way you expected. Stack manifests solved this with a published JSON Schema and editor auto-completion, but the CLI configuration itself — a surface that has grown far larger than stack manifests — had no schema at all. Now it does: a schema generated from the very code that reads the configuration, validated by default, published with every release, and wired into your editor with one comment line. ## The Problem Your `atmos.yaml` is the control plane for everything Atmos does — stacks, components, commands, auth, toolchains, integrations. It's also the file where mistakes hide best: - A typo like `worfklows:` is ignored without a warning, and the real setting silently keeps its default. - Nothing in your editor tells you what keys exist, what type they expect, or what they mean. - Configuration is spread across `atmos.yaml`, `atmos.d/` fragments, and [profiles](/cli/configuration/profiles) — partial files that no hand-written schema handled. - Hand-maintained schemas rot. Every release adds configuration options, and a schema updated by memory falls behind the moment someone forgets. ## The Fix Atmos now ships a complete JSON Schema for `atmos.yaml` — and it's generated from the configuration code itself, on every change, enforced by CI. If a release adds a configuration option, the schema already models it. There is no separate document to remember to update, so it cannot drift from what Atmos actually reads. The schema understands how real Atmos configurations are written: - **YAML functions are first-class.** Where a section expects a map, the schema also accepts [`!include shared.yaml`](/functions/yaml/include), [`!env`](/functions/yaml/env), [`!exec`](/functions/yaml/exec), and every other function `atmos.yaml` supports — so dynamic configs validate cleanly. - **Fragments validate standalone.** No field is required, so `atmos.d/` files and profile fragments — which each carry only a slice of the configuration — pass on their own. - **Descriptions come from the source.** Hover documentation in your editor is sourced from the same documentation the code carries. ## How to Use It Validation now happens out of the box. With no configuration at all, [`atmos validate schema`](/cli/commands/validate/schema) checks `atmos.yaml`, `atmos.d/` fragments, and project-local profiles: ```shell atmos validate schema ``` ```text ✓ Validated atmos.yaml ✓ Validated atmos.d/logs.yaml ✓ Validated profiles/dev/settings.yaml ✓ All schemas validated successfully ``` To validate just the CLI configuration, [`atmos config validate`](/cli/commands/config/config-validate) is a shorthand alias for `atmos validate schema config`, and exits non-zero on violations — handy for CI and pre-commit hooks. Symmetrically, [`atmos stack validate`](/cli/commands/stack/stack-validate) now aliases [`atmos validate stacks`](/cli/commands/validate/stacks). Validation is also fast in large repositories: file matching no longer walks the whole tree for plain file names, so results stream immediately. Print the schema for the exact Atmos version you're running with the new [`atmos config schema`](/cli/commands/config/config-schema) command — a sibling of [`atmos stack schema`](/cli/commands/stack/stack-schema): ```shell atmos config schema # print to stdout atmos config schema atmos-config.json # write to a file ``` For editor auto-completion and inline validation, add one comment to the top of `atmos.yaml`: ```yaml # yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-config/1.0/atmos-config.json base_path: "./" ``` The schema reaches every surface where you touch `atmos.yaml`: the experimental Atmos LSP server ([`atmos lsp start`](/cli/commands/lsp/start)) now reports schema violations as inline diagnostics with line positions, and AI assistants connected through the Atmos MCP server get a new `atmos_validate_schema` tool alongside `atmos_validate_stacks`. The floating URL above always tracks the latest release. Every release also publishes an immutable, version-pinned snapshot at `https://atmos.tools/schemas/atmos/atmos-config//atmos-config.json`, and a `schemas.config` entry in `atmos.yaml` lets you pin validation to a reviewed schema version or point it at your own: ```yaml schemas: config: schema: "https://atmos.tools/schemas/atmos/atmos-config/1.219.0/atmos-config.json" ``` See [CLI Configuration Schemas](/cli/configuration/schemas) for the full reference. ## Get Involved Try `atmos validate schema` on your project and add the modeline to your `atmos.yaml`. If the schema rejects a configuration that Atmos accepts — or your editor's completions are missing something — [open an issue](https://github.com/cloudposse/atmos/issues); the generator makes those fixes one small change away. Join us in the [Cloud Posse community](https://cloudposse.com/slack/) to share feedback. --- ## Auth and Utility Commands No Longer Require Stack Configurations Atmos auth, documentation, and workflow management commands now work independently of stack configurations, making it easier to use Atmos in CI/CD pipelines and alongside "native" Terraform workflows. ## What Changed Six Atmos commands that don't operate on stacks have been updated to no longer require stack configurations: **Auth Commands:** - [`atmos auth env`](/cli/commands/auth/env) - Export cloud credentials as environment variables - [`atmos auth exec`](/cli/commands/auth/exec) - Execute commands with authenticated credentials - [`atmos auth shell`](/cli/commands/auth/shell) - Launch an authenticated shell session **Utility Commands:** - [`atmos list workflows`](/cli/commands/list/list-workflows) - List available workflows - [`atmos list vendor`](/cli/commands/list/list-vendor) - List vendor configurations - [`atmos docs `](/cli/commands/docs/usage) - Display component documentation ## Why This Matters Previously, these commands would fail with an error if you didn't have `stacks.base_path` and `stacks.included_paths` configured in your `atmos.yaml`: ```text Error: failed to initialize atmos config stack base path must be provided in 'stacks.base_path' config or ATMOS_STACKS_BASE_PATH' ENV variable ``` This created an unnecessary barrier for teams who wanted to: - Use Atmos auth for credential management without adopting full stack-based configuration - Run authentication commands in CI/CD pipelines - Browse component documentation without setting up stacks - Manage workflows independently of stack operations With these changes, Atmos now works with "native" Terraform, regardless of whether you use Atmos to manage stack configuration or not (but let's face it, [Nobody Runs Native Terraform](https://cloudposse.com/blog/nobody-runs-native-terraform/)). You can now use Atmos features incrementally: ### Just Authentication Use Atmos for cloud credential management without any stack configuration: ```yaml # atmos.yaml - minimal config for auth only base_path: . auth: providers: aws-prod: kind: aws-sso type: aws region: us-east-1 sso_start_url: https://mycompany.awsapps.com/start sso_region: us-east-1 sso_account_id: "123456789012" sso_role_name: AdministratorAccess identities: prod-admin: provider: aws-prod default: true ``` Then use it with your existing Terraform: ```bash # Get authenticated credentials atmos auth exec -- terraform plan # Or export credentials for your scripts eval $(atmos auth env) ``` ### Just Vendor Management Use Atmos to vendor and manage component dependencies without adopting stack-based configuration: ```bash # List all vendored components atmos list vendor # Pull component updates atmos vendor pull ``` ### Just Documentation Browse component README files without any stack configuration: ```bash # View component documentation atmos docs vpc atmos docs eks-cluster ``` ### Incremental Adoption Start with authentication and vendor management, then gradually adopt stack-based configuration as your needs evolve. Each Atmos feature can be used independently. ## What Hasn't Changed Commands that actually work with stacks still require stack configuration: - [`atmos list stacks`](/cli/commands/list/stacks) - [`atmos list components`](/cli/commands/list/components) - [`atmos describe component`](/cli/commands/describe/component) - [`atmos terraform plan/apply`](/cli/commands/terraform/usage) This ensures that stack-dependent operations have the context they need while allowing utility commands to work independently. ## Related Links - [PR #1717: Relax stack config requirement for commands that don't operate on stacks](https://github.com/cloudposse/atmos/pull/1717) - [Nobody Runs Native Terraform](https://cloudposse.com/blog/nobody-runs-native-terraform/) - [Authentication Documentation](/cli/commands/auth/usage) - [Vendor Configuration](/cli/commands/vendor/usage) --- ## Isolated Browser Sessions for Multi-Account Console Access The [`atmos auth console`](/cli/commands/auth/console) command now supports isolated browser sessions, allowing you to have multiple cloud provider consoles open simultaneously — one per identity — without logout conflicts. ## What Changed When you run `atmos auth console` for different identities, each session now opens in its own isolated Chrome browser context. No more logout conflicts when switching between accounts — whether you're using AWS, Azure, or any other supported provider. Enable it globally in your `atmos.yaml`: ```yaml auth: console: isolated: true ``` Or per-invocation with the [`--isolated`](/cli/commands/auth/console#flags) flag: ```shell atmos auth console --identity plat-staging/AdministratorAccess --isolated atmos auth console --identity cards-staging/AdministratorAccess --isolated ``` Both sessions run simultaneously in separate browser windows with fully isolated cookies and session state. ## Why This Matters Teams working across multiple cloud accounts frequently need to have several consoles open at once — comparing configurations, debugging cross-account issues, or monitoring deployments across environments. Cloud providers like AWS and Azure enforce a single session per browser context, forcing users to log out and back in every time they switch accounts. If you've ever seen this, you know the pain: ![AWS requires you to log out before signing into a different account](/img/changelog/aws-logout-conflict.png) Isolated sessions solve this by giving each identity its own Chrome browser profile via `--user-data-dir`. This works for any provider that `atmos auth console` supports — AWS, Azure, and others as they're added. Sessions are deterministic per identity, so reopening the same identity reuses its profile (no re-login needed within the session lifetime). Different identities are fully isolated. ## Platform Support Isolated sessions work on any platform with Chrome or Chromium installed: - **macOS**: Uses `open -na "Google Chrome" --args --user-data-dir=` - **Linux**: Uses `google-chrome --user-data-dir=` - **Windows**: Uses `chrome.exe --user-data-dir=` If Chrome is not installed, Atmos falls back to the default browser with a helpful warning. The feature degrades gracefully — it never blocks console access. ## Get Involved Have questions or feedback? Join us on [Slack](https://slack.cloudposse.com/) or open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Cloud Console Access with atmos auth console Atmos now includes [`atmos auth console`](/cli/commands/auth/console), a convenience command for opening cloud provider web consoles. Similar to `aws-vault login`, this command uses your authenticated Atmos identities to generate temporary console sign-in URLs and open them in your browser. ## What Changed The `atmos auth console` command generates temporary sign-in URLs for cloud provider web consoles and opens them in your default browser. This removes the need to manually copy credentials or log in separately. ### Features - Single command to open cloud consoles - Provider-agnostic design (AWS available now, Azure and GCP planned) - Uses provider-native federation endpoints - Configurable session duration (up to 12 hours for AWS) - Service aliases: use `s3`, `ec2`, `lambda` instead of full URLs - Navigate directly to specific console pages (100+ AWS services) - Print URLs for scripting workflows ## Why This Helps Instead of manually copying credentials or maintaining separate browser sessions, you can access cloud consoles with a single command: ```shell atmos auth console ``` The command integrates with your existing Atmos auth workflows: ```shell # Quick console access atmos auth console # Access specific AWS services atmos auth console --destination https://console.aws.amazon.com/s3 # Longer sessions for complex tasks atmos auth console --duration 4h # Print URL for scripts atmos auth console --print-only | pbcopy ``` ## How to Use It ### Basic Usage Open the cloud console with your default identity: ```shell atmos auth console ``` ### With Specific Identity ```shell atmos auth console --identity prod-admin ``` ### Navigate to Specific AWS Services Atmos supports 100+ AWS service aliases for convenient shorthand access: ```shell # S3 Console (using alias) atmos auth console --destination s3 # EC2 Console (using alias) atmos auth console --destination ec2 # CloudFormation Console (using alias) atmos auth console --destination cloudformation # Lambda Console (using alias) atmos auth console --destination lambda # DynamoDB Console (using alias) atmos auth console --destination dynamodb ``` You can also use full URLs if preferred: ```shell # Full URL format atmos auth console --destination https://console.aws.amazon.com/s3 ``` **Supported aliases include**: `s3`, `ec2`, `lambda`, `dynamodb`, `rds`, `vpc`, `iam`, `cloudformation`, `cloudwatch`, `eks`, `ecs`, `sagemaker`, `bedrock`, and many more. Aliases are case-insensitive. ### Scripting and Automation ```shell # Print URL without opening browser atmos auth console --print-only # Copy to clipboard (macOS) atmos auth console --print-only | pbcopy # Copy to clipboard (Linux) atmos auth console --print-only | xclip # Use in scripts CONSOLE_URL=$(atmos auth console --print-only --identity prod-oncall) echo "Emergency console access: $CONSOLE_URL" ``` ### Custom Session Duration ```shell # 2-hour session for extended work atmos auth console --duration 2h # Maximum AWS session (12 hours) atmos auth console --duration 12h ``` ## Under the Hood For AWS identities, Atmos uses the [AWS Federation Endpoint](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html) to generate secure console URLs: 1. **Authenticate**: Atmos obtains temporary credentials using your configured identity (AWS SSO, SAML, etc.) 2. **Federation Token**: Temporary credentials are exchanged for a signin token via AWS's federation endpoint 3. **Console URL**: A special URL containing the signin token is constructed 4. **Browser Launch**: The URL opens in your default browser, providing instant console access. ### Provider-Agnostic Design The implementation uses a flexible interface pattern that makes it easy to add support for other cloud providers: ```go type ConsoleAccessProvider interface { GetConsoleURL(ctx context.Context, creds ICredentials, options ConsoleURLOptions) (url string, duration time.Duration, err error) SupportsConsoleAccess() bool } ``` This means Azure Portal and Google Cloud Console support will be straightforward to add in future releases. ## Real-World Use Cases ### Incident Response ```shell # Rapidly access production console during an incident atmos auth console --identity prod-oncall --duration 2h ``` ### Multi-Account Workflows ```shell # Quickly switch between different account consoles atmos auth console --identity dev-account atmos auth console --identity staging-account atmos auth console --identity prod-account ``` ### CI/CD Integration ```shell # Generate console URL in CI/CD for manual verification CONSOLE_URL=$(atmos auth console --print-only) slack-notify "Deployment complete. Verify at: $CONSOLE_URL" ``` ### Team Collaboration ```shell # Use custom issuer to track which team accessed the console atmos auth console --issuer platform-team --duration 4h ``` ## Current Provider Support | Provider | Status | Notes | |----------|--------|-------| | AWS (IAM Identity Center) | ✅ Available Now | Full support with federation endpoint | | AWS (SAML) | ✅ Available Now | Full support with federation endpoint | | Azure | 🚧 Coming Soon | Planned for future release | | GCP | 🚧 Coming Soon | Planned for future release | ## Security Best Practices 1. **Never Share Console URLs**: Signin tokens provide authenticated access and should be treated as sensitive credentials 2. **Use Appropriate Durations**: Choose session durations based on your actual needs (shorter is more secure) 3. **Enable Logging**: Use custom issuer names to track console access in your audit logs 4. **Require MFA**: Ensure your identity provider enforces MFA for console access ## Examples ### Opening AWS S3 Console ```shell $ atmos auth console --destination s3 **Console URL generated** Provider: aws-sso Identity: prod-admin Account: 123456789012 Session Duration: 1h Console URL: https://signin.aws.amazon.com/federation?Action=login&Issuer=atmos&Destination=https%3A%2F%2Fconsole.aws.amazon.com%2Fs3&SigninToken=VeryLongTokenString... Opening console in browser... ``` ### Printing URL for Scripting ```shell $ atmos auth console --print-only https://signin.aws.amazon.com/federation?Action=login&Issuer=atmos&Destination=https%3A%2F%2Fconsole.aws.amazon.com&SigninToken=VeryLongTokenString... ``` ## Get Involved Try it out and let us know what you think: - Update to the latest Atmos version and run `atmos auth console` - Share feedback on what works well and what could be improved - Tell us which cloud providers you'd like to see supported next - Contribute Azure or GCP support at [github.com/cloudposse/atmos](https://github.com/cloudposse/atmos) ## Related Documentation - [atmos auth console command reference](/cli/commands/auth/console) - [AWS Console Federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html) --- This feature is available in Atmos v2.x and later. Update your installation to start using `atmos auth console` today! --- ## Auth Context: Centralizing Authentication State in Atmos We've implemented a centralized authentication context system to enable **concurrent multi-provider identities** - allowing Atmos to manage AWS, GitHub, and other cloud provider credentials simultaneously in a single operation. ## What Changed We introduced `AuthContext` as the **single source of truth** for runtime authentication credentials across multiple cloud providers. This context flows through the entire authentication pipeline and enables concurrent identity management (e.g., AWS and GitHub at the same time) plus proper credential handling in operations like Terraform state access. **Key changes:** - Added `schema.AuthContext` with provider-specific fields (AWS, GitHub, future: Azure/GCP) - Refactored `PostAuthenticate` interface to use `PostAuthenticateParams` struct (reducing parameters from 6 to 2) - Updated Terraform backend operations to accept and use `authContext` parameter - Created `SetAuthContext()` function to populate context after authentication - Derived environment variables from auth context rather than duplicating credential logic - **Migrated AWS credential storage to XDG Base Directory Specification** for consistency with other Atmos data. ## Why This Matters **The core problem:** Atmos needs to support **multiple cloud providers simultaneously** in a single component deployment. For example, a component might need AWS credentials for infrastructure AND GitHub credentials for repository management - both active at the same time. **Before:** Credential information was scattered and provider-specific: - Identity-specific files written by each authenticator - Environment variables set independently (only one provider at a time) - Backend operations couldn't access proper credentials for S3 state - No way to track multiple active provider credentials concurrently - Multi-identity chains overwrote each other's credentials **After:** Unified context supports concurrent providers: ```go Authenticate → SetupFiles → SetAuthContext → SetEnvironmentVariables ↓ AuthContext { AWS: { profile, region, creds... } GitHub: { token, org... } // Future: Azure, GCP, etc. } ↓ Used by: Terraform state ops, SDK calls, spawned processes ``` ## XDG Base Directory Compliance Atmos-managed AWS credentials now follow the **XDG Base Directory Specification**, providing a consistent location pattern across all Atmos data: **New default locations:** - **Linux**: `~/.config/atmos/aws//credentials` - **macOS**: `~/.config/atmos/aws//credentials` - **Windows**: `%APPDATA%\atmos\aws\\credentials` **Why this matters:** - **Consistency**: All Atmos data (cache, keyring, AWS credentials) now follows XDG conventions - **Overridable**: Respect `$XDG_CONFIG_HOME` and `$ATMOS_XDG_CONFIG_HOME` environment variables - **Clean namespace**: Atmos-managed credentials stay under the `atmos/` namespace, not mixed with user's personal AWS credentials - **Platform-aware**: Automatically uses platform-appropriate paths (Linux/macOS/Windows) **Migration:** Users with custom `files.base_path` in their provider configuration are unaffected. For users relying on defaults, credentials will be created in the new XDG location on next login. ## For Atmos Contributors This is an **internal architecture improvement** with minimal user-facing impact. The changes enable: 1. **Concurrent multi-provider support** - Components can use AWS + GitHub + other providers simultaneously without credentials conflicting 2. **Terraform state operations with proper auth** - `terraform.output()` and state queries now work correctly in multi-identity scenarios 3. **Cleaner interface design** - PostAuthenticateParams struct is more maintainable than 6 individual parameters 4. **Extensibility** - Adding new providers (Azure, GCP) just means adding fields to AuthContext 5. **Better testability** - Auth context can be mocked/injected for testing 6. **XDG compliance** - AWS credential storage follows same patterns as cache and keyring **Related PRs:** - \#1695 - Auth context implementation - See `docs/prd/auth-context-multi-identity.md` for complete technical design For usage and configuration, see [atmos auth](/cli/commands/auth/usage). ## Get Involved This refactoring sets the foundation for future authentication improvements. If you're working on auth-related features, ensure you: - Pass `authContext` through your call chains - Use `SetAuthContext()` to populate credentials - Derive from auth context rather than duplicating credential logic Questions? Discussion in #1695 or reach out to the core team. --- ## Improved AWS IAM User Authentication: Automatic Recovery and Better Guidance Atmos now automatically detects when your AWS IAM User credentials have been rotated or revoked and prompts you for new credentials inline. No more persistent authentication failures after credential rotation. Plus, improved guidance when credentials expire. ## What Changed This release improves the AWS IAM User authentication experience with automatic recovery and better guidance: ### Automatic Credential Recovery When [`atmos auth login`](/cli/commands/auth/login) encounters an `InvalidClientTokenId` error from AWS STS, it now: 1. **Automatically clears stale credentials** from the keyring 2. **Prompts for new credentials inline** - no separate configure command needed 3. **Retries authentication** with the new credentials 4. **Provides actionable error messages** if prompting is cancelled ### Improved Status and Guidance - **`auth whoami`** now shows session token expiration and displays a warning with recovery instructions when credentials are invalid - **`auth exec`** now provides a helpful tip when subprocesses fail due to expired credentials ### Session Duration Fix A bug was fixed where **session duration configured via [`atmos auth user configure`](/cli/commands/auth/user/configure) was not being used**, causing tokens to expire after 12 hours instead of the configured 36 hours. ## Applies To This enhancement applies to **AWS IAM User** identities (`aws/user` kind). Other identity types like AWS SSO, assume-role, and permission-set are not affected as they use different authentication flows. ## The Problem Previously, if your AWS access keys were rotated or revoked on the AWS side: 1. Your session token would expire (normal) 2. `atmos auth login` would fail with a cryptic error 3. [`atmos auth logout`](/cli/commands/auth/logout) + `atmos auth login` wouldn't fix it 4. Only full user reconfiguration would work This was frustrating because it broke developer workflows unpredictably. ## The Solution Now Atmos detects the root cause and handles it automatically with inline credential prompting: ```shell $ atmos auth login dev-admin ⚠ AWS credentials are required for identity: dev-admin AWS Access Key ID: AKIAXXXXXXXXXX AWS Secret Access Key: ******** MFA ARN (optional): arn:aws:iam::123456789012:mfa/user Session Duration (optional, default: 12h): 36h ✓ Credentials saved to keyring: dev-admin Enter MFA Token: 123456 ✓ Authentication successful! Provider aws-user Identity dev-admin Account 123456789012 Region us-east-1 Expires 2024-12-24 04:58:00 MST (35h 59m) ``` No separate `atmos auth user configure` command needed - everything happens in one flow. ## Improved Whoami Status The `auth whoami` command now properly displays session token expiration. When credentials are invalid or expired, it shows a warning with recovery instructions: ```shell $ atmos auth whoami dev-admin ✗ Current Authentication Status Provider aws-user Identity dev-admin Expires 2025-12-30 10:11:05 EST (expired) Last Updated 2025-12-30 09:55:34 EST ⚠ Credentials may be expired or invalid. Run 'atmos auth login --identity dev-admin' to refresh. ``` ## Auth Exec Guidance When `auth exec` runs a command that fails due to expired credentials, it now provides a helpful tip: ```shell $ atmos auth exec --identity dev-admin -- aws sts get-caller-identity An error occurred (ExpiredToken) when calling the GetCallerIdentity operation: The security token included in the request is expired Tip If credentials are expired, refresh with: atmos auth login --identity dev-admin ``` ## Error Detection Atmos now detects three common AWS STS errors for IAM User authentication: | Error | Meaning | Automatic Action | |-------------------------|-------------------------|------------------------------------------------| | `InvalidClientTokenId` | Keys rotated/revoked | Clears stale credentials, prompts for new ones | | `ExpiredTokenException` | Session expired | Guides to re-login | | `AccessDenied` | Missing IAM permissions | Guides to check IAM policies | ## Session Duration Fix This release also fixes a bug where session duration configured during `atmos auth user configure` was not being passed through correctly. If you configured 36 hours with MFA, tokens were still expiring after 12 hours (the default). Now your configured session duration is correctly preserved and used when generating session tokens. ## Architecture This implementation introduces a generic credential prompting interface that can be extended to other cloud providers in the future. The interface uses a field-based specification that allows each identity type to define required credentials, making it easy to add support for Azure, GCP, and other cloud providers. ## Why This Matters - **Single command recovery**: `atmos auth login` now handles everything inline for AWS IAM Users - **Extended MFA sessions work correctly**: 36-hour sessions with MFA now last the full duration - **Clear guidance**: Know exactly what went wrong and how to fix it - **Actionable warnings**: `auth whoami` shows warnings with recovery commands when credentials are invalid - **Helpful tips**: `auth exec` guides you to refresh credentials when commands fail - **Proper expiration display**: `auth whoami` shows session token expiration, not just keyring metadata - **Automatic cleanup**: No stale credentials causing repeated failures - **Extensible design**: Generic interface ready for multi-cloud credential prompting ## Get Involved If you encounter other AWS authentication errors that should have better handling, please open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## GitHub Actions Format for Auth Credentials The [`atmos auth env`](/cli/commands/auth/env) command now supports `--format=github` for direct output to `$GITHUB_ENV`, eliminating shell pipelines in CI workflows. ## The Problem Previously, exporting auth credentials to GitHub Actions required awkward shell pipelines: ```yaml - name: Export credentials run: | atmos auth env --identity azure-dev-ci 2>/dev/null | grep "^export " | sed 's/^export //' >> $GITHUB_ENV ``` ## The Solution With `--format=github`, credentials are exported directly: ```yaml - name: Export credentials run: atmos auth env --identity azure-dev-ci --format=github ``` When `$GITHUB_ENV` is set (automatically in GitHub Actions), credentials are written directly to that file. No redirection needed. ## Features - **Auto-detection:** Automatically writes to `$GITHUB_ENV` when set - **Explicit output:** Use [`--output-file`](/cli/commands/auth/env#flags) (or `-o`) to specify a different file - **Multiline support:** Values with newlines use GitHub's heredoc syntax - **No quotes:** Output is `KEY=value` format (no shell quoting) ## Example Workflow ```yaml jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@v6 - name: Authenticate run: atmos auth env --identity prod-deploy --format=github - name: Deploy run: atmos terraform apply vpc -s prod --auto-approve ``` ## Get Started The `--format=github` flag is available now. See the [auth env documentation](/cli/commands/auth/env) for details. --- ## AWS Region Now Exported by atmos auth env The [`atmos auth env`](/cli/commands/auth/env) command now exports `AWS_REGION` and `AWS_DEFAULT_REGION` when region is configured in your identity settings. ## What Changed The `atmos auth env` command exports AWS credentials to your shell for use with **external tools** (AWS CLI, direct terraform runs, etc.). Previously it only exported credential file paths: - `AWS_SHARED_CREDENTIALS_FILE` - `AWS_CONFIG_FILE` - `AWS_PROFILE` Now it also exports region when configured: - `AWS_REGION` - `AWS_DEFAULT_REGION` ## When You Need This Use `atmos auth env` when you want to run external tools that need AWS credentials exported to the shell: ```bash # Export auth environment for external tools eval $(atmos auth env --identity my-identity) # Now external tools have access to AWS credentials and region aws s3 ls terraform plan # running terraform directly, not via atmos ``` ## When You Don't Need This For atmos commands ([`atmos terraform plan`](/cli/commands/terraform/plan), [`atmos terraform apply`](/cli/commands/terraform/apply), etc.), region is **automatically injected** - no sourcing required. The [`!env AWS_REGION`](/functions/yaml/env) YAML function works automatically in stack configurations when using atmos commands. ## Important Notes - Region is only exported when **explicitly configured** in your identity or provider - No default region is assumed - if you don't configure region, it won't be exported - This is purely additive and doesn't break existing scripts ## Get Involved Found an issue or have a feature request? [Open an issue on GitHub](https://github.com/cloudposse/atmos/issues). --- ## Seamless First Login with Provider Fallback [`atmos auth login`](/cli/commands/auth/login) now automatically falls back to provider authentication when no identities are configured, enabling seamless first-time login with `auto_provision_identities`. ## What Changed Previously, running `atmos auth login` with only a provider configured (no static identities) would fail with "no identities available". Users had to know to use `--provider ` explicitly. Now Atmos automatically detects this scenario and falls back to provider-level authentication: - **Single provider**: Auto-selected without prompting - **Multiple providers**: Interactive selection (or use [`--provider`](/cli/commands/auth/login#flags) flag) - **Non-interactive/CI**: Requires explicit `--provider` flag with helpful error message ## Why This Matters With `auto_provision_identities: true`, providers discover and cache available identities after SSO login. But on first login (or after [`atmos auth logout`](/cli/commands/auth/logout) clears the cache), there are no identities yet. This change eliminates the friction: ```shell # Before: Required knowledge of the --provider flag atmos auth login --provider my-sso # Had to know this # After: Just works atmos auth login # Automatically uses the configured provider ``` ## How to Use It No configuration changes needed. If you have a provider configured with `auto_provision_identities: true`, `atmos auth login` now works out of the box: ```yaml auth: providers: my-sso: type: aws-sso auto_provision_identities: true config: sso_region: us-east-1 sso_start_url: https://my-org.awsapps.com/start ``` ```shell # First login - automatically uses my-sso provider atmos auth login # After login, identities are discovered and cached # Subsequent logins use cached identities as before ``` The `--provider` flag remains available for explicit control when needed. --- ## Auth Realm Isolation for Multi-Repository Workflows Atmos now supports credential realm isolation, preventing collisions when engineers work with multiple customer repositories using identical identity names. ## The Problem When working with multiple repositories that use the same identity names (like `core-root/terraform`), credentials from one repository could leak into another. This was especially problematic for consultants and engineers managing infrastructure across multiple customer environments. ## What Changed Credentials are now stored in realm-scoped paths and keyring keys: ``` # Before ~/.config/atmos/aws/{provider}/credentials Keyring: {identity} # After ~/.config/atmos/{realm}/aws/{provider}/credentials Keyring: atmos:{realm}:{identity} ``` The realm is computed automatically based on your project location, ensuring each repository has isolated credentials. ## Realm Computation Realms are determined by priority: 1. **Environment variable**: `ATMOS_AUTH_REALM` - explicit override 2. **Config file**: `auth.realm` in atmos.yaml - per-project setting 3. **Auto-generated**: SHA256 hash of the atmos.yaml path (first 8 characters) ## Viewing Your Realm The [`atmos auth whoami`](/cli/commands/auth/whoami) and [`atmos auth login`](/cli/commands/auth/login) commands now display the active realm: ``` atmos auth login --identity my-identity Realm a1b2c3d4 (auto) Provider aws-sso Identity my-identity Region us-east-1 ``` ## New Logout Options A new `--all-realms` flag allows logging out from all realms across all repositories: ```bash # Logout from current realm only atmos auth logout --all # Logout from ALL realms (all repositories) atmos auth logout --all --all-realms ``` ## Breaking Change This is a **breaking change**. Existing credentials will not be found after updating because they were stored in the old path format. **Action required**: Run `atmos auth login` after updating to re-authenticate. For usage and configuration, see [Authentication](/cli/configuration/auth). ## Get Involved Found an issue or have a feature request? [Open an issue on GitHub](https://github.com/cloudposse/atmos/issues). --- ## Required Identities for Multi-Account Components Atmos identities now support `required: true`, enabling automatic authentication of multiple identities before Terraform runs — without prompting. ## The Problem When Terraform components use multiple AWS provider aliases for multi-account patterns (e.g., hub-spoke networking), each provider assumes a different IAM role. In CI environments with OIDC authentication, only the primary identity's profile was written to the shared credentials file. The additional provider aliases failed because their AWS profiles didn't exist. ## The Solution Identities can now be marked as `required: true`. Before Terraform runs, Atmos automatically authenticates every required identity and writes their profiles to the shared credentials file — no prompting, no selection. The `required` field is orthogonal to `default`: - **`default: true`** — sets the PRIMARY identity (`AWS_PROFILE`, credential env vars). Only one allowed. - **`required: true`** — auto-authenticate without prompting. Multiple allowed. ## Example ```yaml auth: identities: core-network: kind: aws/assume-role default: true # Primary identity required: true # Auto-authenticate # ... via, principal, etc. (see full identity config below) plat-prod: kind: aws/assume-role required: true # Auto-authenticate as secondary # ... via, principal, etc. plat-staging: kind: aws/assume-role required: true # Auto-authenticate as secondary # ... via, principal, etc. ``` > This snippet highlights the `default` and `required` fields only. > Each identity also needs `via` and `principal` configuration — see the full > [identities documentation](/cli/configuration/auth/identities) for complete `aws/assume-role` examples. ## How It Works 1. Atmos authenticates the `default` identity as the primary (sets `AWS_PROFILE` and default credentials). 2. Atmos finds all identities with `required: true` and authenticates each one. 3. All profiles are written to the shared credentials file, making them available for Terraform provider aliases. 4. Failures for non-primary required identities are non-fatal — Atmos logs a warning and continues. 5. Terraform runs with all profiles available, so multi-account provider aliases resolve correctly. The `--identity` CLI flag takes precedence over `default` for primary selection, but required identities are still authenticated as secondary. --- ## New Guides for Atmos Auth: Leapp Migration and Geodesic Integration We've published two comprehensive guides to help you adopt and integrate [`atmos auth`](/cli/commands/auth/usage) into your workflows: migrating from Leapp and configuring Geodesic for seamless authentication. ## What's New The `atmos auth` command (introduced in v1.194.1) provides native AWS IAM Identity Center authentication directly in Atmos, eliminating the need for external credential management tools. To help teams adopt this feature, we've created two detailed tutorials: ### 1. [Migrating from Leapp](/tutorials/migrating-from-leapp) If your team uses Leapp for credential management, this guide walks you through the migration process step-by-step: - **Understanding the mapping** between Leapp concepts (providers, sessions, identities) and `atmos auth` configuration - **Quick migration examples** showing side-by-side comparisons - **Field-by-field reference** for converting Leapp sessions to Atmos identities - **Troubleshooting common issues** during migration The guide includes practical examples using real Leapp session configurations, making it easy to translate your existing setup. ### 2. [Configuring Geodesic with Atmos Auth](/tutorials/configuring-geodesic) For teams using [Geodesic](https://github.com/cloudposse/geodesic) as their DevOps toolbox, this guide explains how to integrate `atmos auth`: - **Host-based authentication flow** - How authentication works on your laptop before starting Geodesic - **Dockerfile configuration** with required environment variables - **Makefile setup** for automatic authentication before shell start - **Source profile configuration** for assume-role utilities - **Complete working examples** showing all components together The guide covers the authentication workflow, explaining that authentication happens on your host machine (not inside the container) and details keychain integration behavior with containers. ## Key Benefits of Atmos Auth Using `atmos auth` provides several advantages over external credential managers: - **Configuration as code** - Authentication config lives in `atmos.yaml` alongside your infrastructure - **Component-level auth** - Different components can use different AWS identities - **Workflow integration** - No separate credential management app to run - **Cross-platform** - Works consistently on Linux, macOS, and Windows - **Team consistency** - Everyone uses the same authentication approach ## Getting Started 1. **Read the guides**: - [Migrating from Leapp](/tutorials/migrating-from-leapp) - [Configuring Geodesic](/tutorials/configuring-geodesic) 2. **Review the main documentation**: - [Authentication User Guide](/cli/commands/auth/usage) - [Command Reference](/cli/commands/auth/login) 3. **Try it out**: ```bash # Configure providers and identities in atmos.yaml # Then authenticate atmos auth login # Verify authentication atmos auth whoami # Use with Terraform atmos terraform plan -s ``` ## Feedback Welcome These guides are designed to be practical and actionable. If you encounter issues, find gaps in the documentation, or have suggestions for improvement: - Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues) - Share your experience in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) - Contribute improvements via pull request --- **Ready to migrate?** Start with the [Leapp migration guide](/tutorials/migrating-from-leapp) or jump straight to [Geodesic configuration](/tutorials/configuring-geodesic) if you're already using `atmos auth`. --- ## Authentication Experience Improvements We've made several quality-of-life improvements to Atmos authentication commands, making identity management smoother and more intuitive. ## Interactive Identity Selection for Terraform Terraform commands now support interactive identity selection when you use [`--identity`](/cli/commands/auth/usage#flags) without specifying a name: ```bash # Interactive selector appears atmos terraform plan mycomponent -s mystack --identity # Or use the ATMOS_IDENTITY environment variable export ATMOS_IDENTITY=dev-admin atmos terraform plan mycomponent -s mystack ``` When both are set, the `--identity` flag takes precedence over the `ATMOS_IDENTITY` environment variable. This brings terraform commands up to parity with other auth commands (`auth shell`, `auth exec`, etc.) that already supported interactive selection. For more details on authentication flags and behavior, see the [CLI authentication documentation](/cli/commands/auth/usage). ## Case-Insensitive Identity Names Identity names are now matched case-insensitively while preserving the original case from your `atmos.yaml` for display: ```yaml auth: identities: Dev-Admin: # Original case preserved provider: aws-sso ``` ```bash # All of these work and display "Dev-Admin" atmos auth login dev-admin atmos auth login DEV-ADMIN atmos auth whoami Dev-Admin ``` This makes the CLI more forgiving while maintaining visual consistency with your configuration. ## Selective Identity Logout You can now log out of specific identities without affecting others that share the same provider: ```bash # Clear only dev-admin cached credentials (keyring + files) atmos auth logout dev-admin # Other identities using the same provider remain authenticated atmos auth whoami prod-admin # Still works ``` The `auth logout` command now offers three levels of cleanup: - **[`atmos auth logout `](/cli/commands/auth/logout)** - Clear one identity's cached credentials (keyring + files) - **`atmos auth logout --provider `** - Clear provider and all identities using it (keyring + files + provider directory) - **`atmos auth logout --all`** - Clear all identities AND providers (complete cleanup) **Bug fix**: We discovered and fixed a bug where `--all` was only logging out identities but leaving orphaned provider credentials. This has been corrected and is now covered by a dedicated test to prevent regression. Note: Your `atmos.yaml` configuration is never modified - logout only removes cached credentials. Each identity in [`atmos auth list`](/cli/commands/auth/list) now shows authentication status indicators: - ✓ Authenticated with valid credentials - ⚠ Authenticated but credentials expiring soon - ✗ Not authenticated or credentials expired ## Legacy Path Warning (Once Per Session) If you're using the legacy `~/.aws/atmos/` credential path, Atmos will now show the migration warning only once per execution instead of repeatedly: ```bash ⚠ Using legacy credentials path: ~/.aws/atmos/aws-sso/credentials Run 'atmos auth login' to migrate to XDG-compliant path: ~/.config/atmos/aws/aws-sso/credentials ``` This keeps the terminal output clean while still guiding you toward the recommended configuration. ## Summary These improvements focus on polish and ergonomics—making authentication work the way you'd expect without getting in your way. Identity selection is more flexible, logout is more precise, and the overall experience is cleaner. --- ## Authentication Support for Workflows and Custom Commands We're excited to announce two major improvements to Atmos authentication: **per-step authentication for workflows** and **authentication support for custom commands**. These features enable you to seamlessly use cloud credentials in your automation while maintaining security through file-based credential management. ## Background: File-Based Credential Security Atmos uses a secure file-based credential approach to prevent credential exposure: - **Credentials are written to temporary files** following the XDG Base Directory Specification (e.g., `~/.config/atmos/aws/{provider-name}/credentials`, `~/.config/atmos/aws/{provider-name}/config`) - **Environment variables point to these files** (`AWS_SHARED_CREDENTIALS_FILE`, `AWS_CONFIG_FILE`, `AWS_PROFILE`) - **Raw credentials are never exposed** in environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` are never set) - **SDKs read credentials from files** using the standard credential chain - **Provider-organized structure** - credentials are organized by provider under XDG config directories This approach ensures that credentials are isolated and never serialized in logs, process listings, or other outputs. ## Bug Fix: Auth Shell Environment Variables We fixed a regression in [`atmos auth shell`](/cli/commands/auth/shell) where it wasn't setting the required environment variables for credential file resolution. Previously, when you ran: ```bash atmos auth shell my-identity ``` The spawned shell didn't have `AWS_SHARED_CREDENTIALS_FILE`, `AWS_CONFIG_FILE`, or `AWS_PROFILE` set, so AWS SDK commands couldn't find credentials even though authentication succeeded. This is now fixed - `atmos auth shell` properly configures the environment with file-based credential paths. ## Feature 1: Authentication for Workflows Workflows now support per-step authentication with the new `identity` field: ```yaml workflows: deploy-multi-account: description: Deploy infrastructure across multiple AWS accounts steps: - name: Deploy to dev account command: terraform apply identity: dev-account - name: Deploy to staging account command: terraform apply identity: staging-account - name: Deploy to prod account command: terraform apply identity: prod-account ``` ### Command-Line Override You can also set a default identity for all steps using the `--identity` flag: ```bash # Use dev-account for all steps that don't specify their own identity atmos workflow deploy-multi-account --identity dev-account ``` **Precedence**: Step-level `identity` > `--identity` flag > no authentication ### Use Cases - **Multi-account deployments**: Authenticate to different AWS accounts per step - **Cross-cloud workflows**: Switch between AWS, GCP, and Azure credentials - **Role assumption chains**: Use different identities that assume through other identities - **Environment-specific automation**: Dev, staging, and production credentials in a single workflow ## Feature 2: Authentication for Custom Commands Custom commands now support authentication with the `identity` field: ```yaml commands: - name: deploy description: Deploy infrastructure with authentication identity: production-account steps: - terraform init - terraform plan - terraform apply ``` ### Runtime Override with --identity Flag All custom commands automatically get an `--identity` flag for runtime override: ```bash # Override the configured identity at runtime atmos deploy --identity staging-account ``` **Precedence**: `--identity` flag > configured `identity` > no authentication ### Shared Identity Across Steps All steps in a custom command share the same identity - the command authenticates once and all steps execute with those credentials. ## Examples ### Workflow with Mixed Authentication ```yaml workflows: multi-cloud-deploy: description: Deploy across AWS and GCP steps: # AWS deployment - name: Deploy AWS infrastructure command: terraform apply -target=module.aws identity: aws-production # GCP deployment - name: Deploy GCP infrastructure command: terraform apply -target=module.gcp identity: gcp-production # No authentication needed - name: Update documentation command: ./scripts/update-docs.sh ``` ### Custom Command with Component Config ```yaml commands: - name: plan-all description: Plan all components in a stack identity: developer component_config: component: "{{ .Arguments.component }}" stack: "{{ .Arguments.stack }}" steps: - atmos terraform plan {{ .ComponentConfig.Component }} -s {{ .ComponentConfig.Stack }} ``` Run with override: ```bash # Use production credentials instead of developer atmos plan-all vpc prod --identity production ``` ## Documentation For complete details, see: - [Workflow Authentication](/workflows/steps/identity) - [Custom Command Authentication](/cli/configuration/commands/identity#using-authentication-with-custom-commands) - [Auth Commands](/cli/commands/auth/usage) ## Try It Out Update to the latest version of Atmos and try the new authentication features: ```bash # Authenticate to an identity atmos auth login my-identity # Use in workflows atmos workflow deploy --identity my-identity # Use in custom commands atmos my-command --identity my-identity # Use auth shell (now with proper environment variables!) atmos auth shell my-identity ``` We're excited to see how you use these features to simplify your multi-account and multi-cloud automation! --- ## Solving the Terraform Bootstrap Problem with Automatic Backend Provisioning We're excited to introduce **automatic backend provisioning** in Atmos, a feature that solves the Terraform bootstrap problem. No more manual S3 bucket creation, no more chicken-and-egg workarounds—Atmos provisions your state backend automatically with secure defaults, making it fully compatible with Terraform-managed infrastructure. ## The Problem: State Backend Bootstrapping Every Terraform project faces the same bootstrapping challenge: before you can manage infrastructure, you need somewhere to store your state. The typical workflow looks like this: 1. Manually create an S3 bucket via AWS Console or CLI 2. Configure bucket versioning, encryption, and public access blocking 3. Set up DynamoDB table for state locking (optional with Terraform 1.10+) 4. Finally, start using Terraform This creates friction for new projects, complicates CI/CD pipelines, and introduces manual steps that conflict with infrastructure-as-code principles. ## The Solution: Automatic Provisioning Atmos now provisions backends automatically when needed. Just enable it in your stack configuration: ```yaml components: terraform: vpc: backend_type: s3 # Must be at component level backend: bucket: my-terraform-state key: vpc/terraform.tfstate region: us-east-1 provision: backend: enabled: true # That's it! ``` When you run [`atmos terraform plan vpc -s dev`](/cli/commands/terraform/plan), Atmos: 1. **Checks** if the backend exists 2. **Provisions** it if needed (with secure defaults) 3. **Initializes** Terraform 4. **Continues** with your command All automatically. No manual intervention required. ## Configuration Flexibility The `provision.backend` configuration works with Atmos's inheritance system, allowing you to set defaults at any level (organization, environment, component) and override when needed. ## Secure by Default The S3 backend provisioner applies hardcoded security best practices: - ✅ **Versioning enabled** - Protect against accidental deletions - ✅ **AES-256 encryption** - AWS-managed keys, always enabled - ✅ **Public access blocked** - All four block settings enabled - ✅ **Native S3 locking** - Terraform 1.10+ support (no DynamoDB needed) - ✅ **Resource tags** - Automatic tagging for cost allocation These settings aren't configurable—they're opinionated defaults that follow AWS security best practices. ## Solves the Terraform Bootstrap Problem Automatic provisioning is **fully compatible with Terraform-managed backends**. In fact, it solves a classic chicken-and-egg problem: "How do I manage my state backend with Terraform when I need that backend to exist before Terraform can run?" **The traditional workaround:** 1. Use local state temporarily 2. Create S3 bucket with Terraform using local state 3. Switch backend configuration to S3 4. Import the bucket into the S3-backed state 5. Delete local state files **With Atmos automatic provisioning:** 1. Enable `provision.backend.enabled: true` 2. Run `atmos terraform plan` - bucket auto-created with secure defaults 3. Import the bucket into Terraform (no local state dance needed) 4. Done - everything managed by Terraform **Import the provisioned backend:** ```hcl import { to = aws_s3_bucket.terraform_state id = "my-terraform-state" } resource "aws_s3_bucket" "terraform_state" { bucket = "my-terraform-state" } # Add any additional configuration resource "aws_s3_bucket_lifecycle_configuration" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id rule { id = "delete-old-versions" status = "Enabled" noncurrent_version_expiration { noncurrent_days = 90 } } } ``` This **eliminates the bootstrap hack** and makes it easier to manage everything with Terraform, not harder. The provisioner creates standard AWS resources that Terraform can import and manage - no special handling required. **Note:** You can leave `provision.backend.enabled: true` even after importing to Terraform. The provisioner is idempotent - it will detect the bucket exists and skip creation, causing no conflicts with Terraform management. ## Cross-Account Support Provisioners integrate with Atmos AuthManager for cross-account operations: ```yaml components: terraform: vpc: backend_type: s3 # Must be at component level backend: bucket: my-terraform-state region: us-east-1 assume_role: role_arn: arn:aws:iam::999999999999:role/TerraformStateAdmin provision: backend: enabled: true ``` The provisioner automatically assumes the role to create the bucket in the target account. ## CLI Command For manual provisioning or CI/CD pipelines, use the [`atmos terraform backend`](/cli/commands/terraform/terraform-backend) command: ```bash # Provision backend explicitly atmos terraform backend create vpc --stack dev # Automatic in CI/CD atmos terraform backend create vpc --stack dev atmos terraform backend create eks --stack dev atmos terraform apply vpc --stack dev # Only runs if provisioning succeeded ``` Provisioning failures return non-zero exit codes, ensuring CI/CD pipelines fail fast. Here's the full `create` / `update` / `delete` lifecycle running end-to-end against a local sandbox — no AWS account required: [View the full example](/examples/backend-provisioning) ## Extensible Architecture The provisioner system is built on a **self-registering architecture** that makes it easy to add support for additional backend types and provisioner types in the future Backend provisioners register themselves and declare when they should run via hook events: ```go // Register S3 backend provisioner provisioner.RegisterProvisioner(provisioner.Provisioner{ Type: "backend", HookEvent: "before.terraform.init", Func: ProvisionS3Backend, }) ``` ## Getting Started Enable automatic backend provisioning in your stack configuration: ```yaml # stacks/dev.yaml components: terraform: vpc: backend_type: s3 # Must be at component level backend: bucket: acme-terraform-state-dev key: vpc/terraform.tfstate region: us-east-1 provision: backend: enabled: true ``` Then run your Terraform commands as usual: ```bash atmos terraform plan vpc -s dev # Backend provisioned automatically if needed ``` For more information: - [CLI Documentation](/cli/commands/terraform/terraform-backend) - [Backend Configuration](/components/terraform/backends) ## Community Feedback We'd love to hear how you're using automatic backend provisioning! Share your experience in [GitHub Discussions](https://github.com/cloudposse/atmos/discussions) or report any issues in our [issue tracker](https://github.com/cloudposse/atmos/issues). --- **Try it today** and simplify your Terraform state management workflow! For usage and configuration, see [Backend Provisioning](/stacks/components/provision/backend). --- ## Backends Auto-Provisioned on Terraform Init Atmos now automatically provisions backends during `terraform init`, eliminating the need for a separate `backend create` command. When `provision.backend.enabled: true` is set, backends are created just-in-time during your first Terraform operation. ## What Changed Previously, automatic backend provisioning required running [`atmos terraform backend create`](/cli/commands/terraform/terraform-backend) explicitly before initializing Terraform. Now, backends are provisioned automatically when you run any Terraform command that triggers initialization: ```bash # Before: Two commands required atmos terraform backend create vpc -s dev atmos terraform plan vpc -s dev # After: Single command - backend auto-provisioned on init atmos terraform plan vpc -s dev # ⠋ Provisioning S3 backend `my-terraform-state` for `vpc` in stack `dev`... # ✓ Provisioned S3 backend `my-terraform-state` for `vpc` in stack `dev` # ... terraform init continues automatically ``` ## How It Works When `provision.backend.enabled: true` is configured, Atmos registers a hook that runs before `terraform init`: 1. **Check if enabled** - Silent skip if `provision.backend.enabled` is not `true` 2. **Check if exists** - Silent skip if backend already exists (idempotent) 3. **Provision with feedback** - Create backend with spinner showing progress 4. **Continue init** - Terraform initialization proceeds automatically This behavior is completely opt-in. Without the provision configuration, nothing changes. ## Configuration The configuration remains the same - just add `provision.backend.enabled: true`: ```yaml components: terraform: vpc: backend_type: s3 backend: bucket: my-terraform-state key: vpc/terraform.tfstate region: us-east-1 provision: backend: enabled: true ``` You can also set this at the global terraform level for all components: ```yaml terraform: provision: backend: enabled: true components: terraform: vpc: # Inherits provision.backend.enabled: true from above backend_type: s3 backend: bucket: my-terraform-state key: vpc/terraform.tfstate region: us-east-1 ``` ## UX Improvements This release also includes improved output formatting: - **Spinner feedback** - Visual progress indicator during provisioning - **Consolidated messages** - Single line output instead of multiple status messages - **Backend type and name** - Shows backend type (S3, GCS, etc.) and resource name (bucket) - **Markdown-friendly formatting** - Backticks around names render correctly in terminals ## Idempotent Behavior The auto-provisioning is idempotent: - **First run**: Backend is created with spinner feedback - **Subsequent runs**: Silent skip (backend already exists) - **After Terraform import**: Safe to leave enabled - no conflicts This means you can safely leave `provision.backend.enabled: true` in your configuration permanently. ## Explicit Command Still Available The explicit `backend create` command remains available for CI/CD pipelines or manual provisioning: ```bash atmos terraform backend create vpc -s dev ``` ## Getting Started Enable automatic backend provisioning in your stack configuration and run any Terraform command: ```bash atmos terraform plan vpc -s dev ``` That's it - no separate provisioning step required. For more details, see our [original announcement](/changelog/automatic-backend-provisioning) and [CLI documentation](/cli/commands/terraform/terraform-backend). For usage and configuration, see [Backend Provisioning](/stacks/components/provision/backend). --- ## AWS Assume Root Identity for Centralized Root Access Atmos now supports the `aws/assume-root` identity kind, enabling secure, centralized management of root access across your AWS Organization using the STS AssumeRoot API. ## The Challenge with Root Access Root access in AWS member accounts has traditionally been problematic: - **Security risk**: Root credentials scattered across multiple accounts - **Audit gaps**: No centralized logging of root access - **Operational burden**: Managing root passwords for dozens or hundreds of accounts - **Compliance issues**: Difficult to prove who accessed root and when AWS introduced [Centralized Root Access](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-enable-root-access.html) to address these challenges, allowing management account administrators to assume root in member accounts using short-lived credentials. Now Atmos makes this capability accessible through its authentication system. ## How It Works The `aws/assume-root` identity kind integrates with AWS STS AssumeRoot API to provide: 1. **Credential chaining**: Chain from an SSO permission set to assume root in target accounts 2. **Task-scoped access**: Use AWS-managed task policies to limit root operations 3. **Short-lived credentials**: 15-minute maximum session duration (AWS limit) 4. **Audit trail**: All access logged through CloudTrail ### Configuration Example ```yaml # atmos.yaml auth: providers: acme-sso: kind: aws/iam-identity-center start_url: https://acme.awsapps.com/start region: us-east-1 identities: # First, authenticate with a permission set that has sts:AssumeRoot org-root-access: kind: aws/permission-set via: provider: acme-sso principal: name: RootAccess account: name: core-root # Then chain to assume root in the target account audit/iam-audit-root: kind: aws/assume-root via: identity: org-root-access principal: target_principal: "123456789012" # Target account ID task_policy_arn: arn:aws:iam::aws:policy/root-task/IAMAuditRootUserCredentials ``` ### Usage ```bash # Authenticate and assume root in one command atmos auth login --identity audit/iam-audit-root # Or use exec for one-off commands atmos auth exec --identity audit/iam-audit-root -- aws iam list-mfa-devices ``` ## Supported Task Policies AWS provides managed task policies that scope root access to specific operations: | Policy | Use Case | |--------|----------| | `IAMAuditRootUserCredentials` | Audit root user MFA and access keys | | `IAMCreateRootUserPassword` | Create or reset root password | | `IAMDeleteRootUserCredentials` | Remove root access keys and MFA | | `S3UnlockBucketPolicy` | Unlock S3 buckets with restrictive policies | | `SQSUnlockQueuePolicy` | Unlock SQS queues with restrictive policies | These policies ensure root access is limited to the specific task at hand. ## Prerequisites To use `aws/assume-root`: 1. **Enable centralized root access** in your AWS Organization 2. **Configure a permission set** with `sts:AssumeRoot` permission in your management account 3. **Target account must be a member** of your AWS Organization ## Security Benefits - **No persistent root credentials**: All access uses short-lived STS tokens - **Principle of least privilege**: Task policies limit what root can do - **Centralized audit**: All access logged in CloudTrail - **Credential chaining**: Leverages existing SSO infrastructure ## Get Started Update to the latest Atmos and configure your first assume-root identity: ```yaml auth: identities: my-account/audit-root: kind: aws/assume-root via: identity: your-root-access-permission-set principal: target_principal: "YOUR_ACCOUNT_ID" task_policy_arn: arn:aws:iam::aws:policy/root-task/IAMAuditRootUserCredentials ``` For detailed configuration options, see the [Authentication Documentation](/cli/commands/auth/usage). ## Feedback We'd love to hear how assume-root works for your organization. Share your experience in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) or report issues in [GitHub Issues](https://github.com/cloudposse/atmos/issues). For usage and configuration, see [Identities](/cli/configuration/auth/identities). --- ## New !aws.organization_id YAML Function Access the AWS Organization ID directly in stack configuration with the new [`!aws.organization_id`](/functions/yaml/aws.organization-id) YAML function. ## What Changed Atmos now includes a new `!aws.organization_id` YAML function that retrieves the AWS Organization ID by calling the AWS Organizations `DescribeOrganization` API. This complements the existing AWS context functions: [`!aws.account_id`](/functions/yaml/aws.account-id), [`!aws.caller_identity_arn`](/functions/yaml/aws.caller-identity-arn), [`!aws.caller_identity_user_id`](/functions/yaml/aws.caller-identity-user-id), and [`!aws.region`](/functions/yaml/aws.region). ```yaml components: terraform: my-component: vars: organization_id: !aws.organization_id ``` ## Why This Matters The AWS Organization ID is commonly needed for: - **Service Control Policies (SCPs)** - Scoping policies to the organization - **Cross-account trust policies** - Referencing the organization in IAM conditions - **Resource tagging** - Tagging resources with the organization ID for cost allocation - **Multi-account governance** - Configuring components that operate at the organization level Previously, users had to hardcode the organization ID or use workarounds. Now it's available dynamically, just like other AWS context values. ## How to Use It Use `!aws.organization_id` anywhere in your stack YAML files: ```yaml components: terraform: governance: vars: org_id: !aws.organization_id tags: OrganizationId: !aws.organization_id ``` The function requires the `organizations:DescribeOrganization` IAM permission and the account must be a member of an AWS Organization. Results are cached for the duration of the CLI invocation, so multiple references only make one API call. This is equivalent to Terragrunt's `get_aws_org_id()` function. ## Get Involved - Read the [documentation](/functions/yaml/aws.organization-id) for full details - Report issues on [GitHub](https://github.com/cloudposse/atmos/issues) --- ## Analyze AWS Security Findings and Map Them to Your Infrastructure Atmos can now pull security findings from AWS Security Hub, map them to the exact Atmos components and stacks that manage the affected resources, and generate structured remediation reports — all from a single command. ## Why This Matters Reviewing AWS security findings today means navigating Security Hub, cross-referencing resources with Terraform code, and manually figuring out which stack configuration caused the issue. This is slow and requires deep AWS + Terraform expertise. With [`atmos aws security analyze`](/cli/commands/aws/security/analyze), one command replaces that entire workflow: ```bash atmos aws security analyze --stack prod-us-east-1 ``` The command fetches findings, maps them to your Atmos components via resource tags, and shows which code manages each affected resource. Add [`--ai`](/cli/commands/aws/security/analyze#flags) for AI-powered remediation with specific code changes and deploy commands. ## Quick Start **File:** `atmos.yaml` ```yaml aws: security: enabled: true identity: "security-readonly" # Atmos Auth identity → Security Hub account region: "us-east-2" # Security Hub aggregation region tag_mapping: stack_tag: "atmos:stack" component_tag: "atmos:component" ``` ```bash # Authenticate and analyze atmos auth login atmos aws security analyze atmos aws security analyze --ai # With AI remediation ``` ## What You Get **Without `--ai`** — findings with component mapping: ```text ## CRITICAL Findings (2) ### 1. S3 bucket without encryption | Field | Value | |-------|-------| | **Severity** | CRITICAL | | **Source** | security-hub (CIS-1.4) | | **Resource** | `arn:aws:s3:::my-bucket` | | **Component** | s3-bucket | | **Stack** | prod-us-east-1 | | **Confidence** | exact | ``` **With `--ai`** — adds structured remediation: ```text #### Remediation **Root Cause:** The S3 bucket was provisioned without enabling versioning. **Steps:** 1. Add versioning_enabled variable to the stack configuration 2. Apply the change **Stack Changes:** vars: versioning_enabled: true **Deploy:** `atmos terraform apply s3-bucket -s prod-us-east-1` **Risk:** low ``` ## Key Features - **Finding-to-code mapping** — traces AWS resources back to Atmos components via tags or naming heuristics - **Atmos Auth integration** — `identity` field targets the Security Hub delegated admin account - **Multi-turn AI analysis** — API providers can call `atmos_describe_component`, `read_component_file` to gather context before generating remediation - **CLI provider support** — Claude Code and Codex CLI fall back to enriched single-prompt mode - **Compliance reports** — [`atmos aws compliance report --framework cis-aws`](/cli/commands/aws/compliance/report) for framework-specific posture - **Four output formats** — Markdown (terminal), JSON (CI/CD), YAML (config), CSV (spreadsheets) - **Structured schema** — every output follows the same schema regardless of AI provider ## Commands ```bash # All findings across all stacks atmos aws security analyze # Filter by stack and/or component atmos aws security analyze --stack prod-us-east-1 atmos aws security analyze --stack prod-us-east-1 --component vpc # Filter by severity or source atmos aws security analyze --severity critical,high --source guardduty # AI-powered remediation (deduplicates findings, retries on errors) atmos aws security analyze --stack prod-us-east-1 --ai # Save to file in any format atmos aws security analyze --format json --file findings.json atmos aws security analyze --stack prod-us-east-1 --file report.md atmos aws security analyze --format csv --file audit.csv # Compliance reports atmos aws compliance report --framework cis-aws atmos aws compliance report --framework pci-dss --format json --file compliance.json # Override identity or region at runtime atmos aws security analyze --identity security-admin --region us-west-2 ``` ## See It in Action Tested against a multi-account AWS organization (11 accounts, Security Hub delegated admin, 500 findings fetched, 97% mapped to Atmos components). **Without `--ai`** — findings mapped to components: ```text $ atmos aws security analyze --stack plat-use2-dev --component rds/example ℹ Fetching security findings... ℹ Mapping 500 findings to Atmos components... ℹ Filtered to 4 findings matching stack="plat-use2-dev" component="rds/example" # Security Report: plat-use2-dev / rds/example Findings: 4 (1 CRITICAL, 3 HIGH) ## CRITICAL Findings (1) ### 1. Security groups should not allow unrestricted access to ports with high risk | Field | Value | |----------------|--------------------------------------------------------------| | **Severity** | CRITICAL | | **Source** | security-hub (aws-foundational-security-best-practices/1.0) | | **Resource** | arn:aws:ec2:us-east-2:***:security-group/sg-*** | | **Component** | rds/example | | **Stack** | plat-use2-dev | | **Confidence** | exact | | **Mapped By** | finding-tag | Resource Tags: • atmos_stack = plat-use2-dev • atmos_component = rds/example • terraform_component = rds • terraform_workspace = plat-use2-dev-rds-example • Name = acme-plat-use2-dev-example-postgres-db • Namespace = acme • Tenant = plat • Environment = use2 • Stage = dev ## HIGH Findings (3) 1. Security groups should only allow unrestricted incoming traffic for authorized ports 2. Security groups should not allow ingress from 0.0.0.0/0 to port 22 3. Security groups should not allow ingress from 0.0.0.0/0 to port 3389 ## Summary | Severity | Count | Mapped | Unmapped | |-----------|-------|--------|----------| | CRITICAL | 1 | 1 | 0 | | HIGH | 3 | 3 | 0 | | **Total** | **4** | **4** | **0** | ``` **With `--ai`** — the AI reads the actual Terraform source and stack config via tools, detects drift, and generates targeted remediation: ```text $ atmos aws security analyze --stack plat-use2-dev --component rds/example --ai ℹ Analyzing findings with AI... ✓ AI analysis complete — Security Analysis: rds/example in plat-use2-dev ## Summary The analysis surfaced 4 findings against a single security group — all mapped with exact confidence to this component via Atmos tags. | Severity | Count | |-------------|-------| | 🔴 CRITICAL | 1 | | 🟠 HIGH | 3 | ## Findings Breakdown ### 🟠 Finding 1 — EC2.18: Unrestricted Ingress on Unauthorized Port (HIGH) Standard: AWS Foundational Security Best Practices v1.0.0 Port 5432 (PostgreSQL) is open to 0.0.0.0/0. The likely cause is allowed_cidr_blocks being set to an overly permissive value — potentially from commented-out lines in catalog/rds/defaults.yaml that were activated at some point. Fix: Set in catalog/rds/example.yaml: allowed_cidr_blocks: [] publicly_accessible: false ### 🟠 Finding 2 — EC2.13: Unrestricted Ingress on Port 22/SSH (HIGH) Standard: CIS AWS Foundations Benchmark v1.2.0 ⚠️ This is anomalous — port 22 has no business being on an RDS security group. This strongly suggests an out-of-band manual change was made directly in the AWS Console, or a referenced SG in associate_security_group_ids carries a port-22 rule. Fix: 1. Immediately audit and manually remove the port-22 rule in the AWS Console 2. Audit any SGs referenced via associate_security_group_ids / security_group_ids 3. Re-apply via Terraform to restore IaC control and eliminate drift ## Root Cause (Common Thread) Both findings stem from the same security group and share a root cause: var.allowed_cidr_blocks being set too permissively, compounded by possible out-of-band drift. The cloudposse/rds/aws module internally creates and manages SG ingress rules based on this variable. ## Priority Actions 1. Immediately remove the port-22 inbound rule manually — this is likely out-of-band drift and poses direct unauthorized access risk 2. Update catalog/rds/example.yaml to explicitly enforce safe defaults: allowed_cidr_blocks: [] publicly_accessible: false associate_security_group_ids: [] use_private_subnets: true 3. Add Terraform validation guards to rds-variables.tf to prevent future regressions: validation { condition = !contains(var.allowed_cidr_blocks, "0.0.0.0/0") && !contains(var.allowed_cidr_blocks, "::/0") error_message = "allowed_cidr_blocks must not contain 0.0.0.0/0 or ::/0." } 4. Clean up catalog/rds/defaults.yaml — permanently remove (don't just comment out) any lines with 0.0.0.0/0 or publicly_accessible: true 5. Plan then apply: atmos terraform plan rds/example -s plat-use2-dev atmos terraform apply rds/example -s plat-use2-dev ## Risk Assessment | Finding | Risk | Note | |----------------------|--------|---------------------------------------------------| | EC2.18 (port 5432) | Medium | Removing rule breaks direct internet connections | | | | to DB; client SG-based connections are unaffected | | EC2.13 (port 22/SSH) | Low | No RDS traffic should depend on SSH; removing | | | | has no expected legitimate impact | ``` The AI used multi-turn tools (`atmos_describe_component`, `read_component_file`) to read the actual Terraform source and stack config, detected that port 22 on an RDS security group is anomalous (likely AWS Console drift), identified the common root cause in `allowed_cidr_blocks`, and generated targeted remediation with Terraform validation guards to prevent future regressions. Duplicate findings are deduplicated before AI analysis — one call covers all related findings. **Compliance report** — framework-specific posture scoring: ```text $ atmos aws compliance report # Compliance Report: CIS AWS Foundations Benchmark ## Score: 35/42 Controls Passing (83%) ### Failing Controls | Control | Title | Severity | |--------------|--------------------------------------------------------------------------|----------| | Config.1 | AWS Config should be enabled with service-linked role | CRITICAL | | EC2.14 | Security groups should not allow ingress from 0.0.0.0/0 to port 3389 | HIGH | | EC2.13 | Security groups should not allow ingress from 0.0.0.0/0 to port 22 | HIGH | | S3.1 | S3 buckets should have block public access settings enabled | MEDIUM | | EC2.6 | VPC flow logging should be enabled in all VPCs | MEDIUM | | IAM.17 | Ensure IAM password policy expires passwords within 90 days | LOW | | CloudTrail.7 | Ensure S3 bucket access logging is enabled on CloudTrail S3 bucket | LOW | ``` **Compliance with `--ai`** — adds prioritized remediation guidance: ```text $ atmos aws compliance report --ai ✓ AI analysis complete — CIS Foundations Benchmark ## Overall Status: 🟡 83% Compliant (35/42 controls passing) ## 🚨 Priority Issues (Fix First) ### CRITICAL | Control | Issue | Action | |----------|-----------------------------------------|--------------------------------------| | Config.1 | AWS Config not enabled or missing role | Enable in all regions, attach role | ### HIGH | Control | Issue | Action | |---------|-----------------------------------|-----------------------------------------| | EC2.14 | RDP (port 3389) open to 0.0.0.0/0 | Restrict to known IP ranges or VPN | | EC2.13 | SSH (port 22) open to 0.0.0.0/0 | Use SSM Session Manager instead of SSH | ⚠️ Open SSH/RDP to the world is a common attack vector. ## 🟠 Medium Priority • S3.1 — Enable S3 Block Public Access at the account level • EC2.6 — Enable VPC Flow Logs for all VPCs ## 🟢 Low Priority • IAM.17 — Set IAM password policy MaxPasswordAge to ≤ 90 days • CloudTrail.7 — Enable S3 access logging on CloudTrail bucket ## Recommended Next Steps 1. Lock down security groups for ports 22/3389 2. Enable AWS Config — also helps detect future drift 3. Run `atmos terraform apply` on security-groups, vpc, config components 4. Re-run this report after remediation to verify score improves ``` ## Try It **Example: AWS Security & Compliance** Configuration example with auth, tag mapping, AI provider, and all available commands. Browse Gist[Read more](/gists/aws-security-compliance) ## Learn More - [Security Configuration](/cli/configuration/aws/security) - [Security Analyze Command](/cli/commands/aws/security/analyze) - [Compliance Report Command](/cli/commands/aws/compliance/report) - [Atmos Auth](/cli/configuration/auth) --- ## AWS Security Findings Now Export to SARIF and OCSF The [`atmos aws security analyze`](/cli/commands/aws/security/analyze) command is the native Atmos command for turning AWS security findings into infrastructure-aware remediation guidance. It reads findings from AWS Security Hub and Amazon Inspector, including Security Hub product findings from services such as AWS Config, GuardDuty, Macie, and IAM Access Analyzer, then uses Atmos component tags and mapping heuristics to connect affected resources back to the stacks and components that manage them. Those mappings make findings more actionable: instead of stopping at an AWS resource ARN, Atmos can show the owning stack, component path, severity, source service, and remediation context. With new SARIF 2.1.0 and OCSF 1.4.0 output, those findings can now flow into code scanning, SIEM, governance, risk, and compliance workflows without a translation layer. ## What Changed The command now gains two new output formats: - **`--format=sarif`** — produces a SARIF 2.1.0 document. Findings keep their Atmos context (stack, component, component path, remediation steps) as SARIF result properties, so downstream tooling sees the same information the Markdown report does. Output is byte-stable across runs, so diffs and dedup work cleanly. - **`--format=ocsf`** — produces OCSF 1.4.0 Detection Finding events with cloud and vulnerability profile fields, suitable for SIEM and security data lake ingestion. ```shell # Local SARIF for GitHub code scanning. atmos aws security analyze --format=sarif --file=findings.sarif # OCSF Detection Findings for security data lakes. atmos aws security analyze --format=ocsf --file=findings.ocsf.json ``` ## Why This Matters Security findings need to land in the systems that already track risk — GitHub Advanced Security, vulnerability dashboards, SIEM pipelines, and ticketing automations. Rendering Markdown is great for ad-hoc review, but SARIF is the format code-scanning surfaces understand, while OCSF gives security analytics platforms a normalized event shape. With both formats in the toolbox, `atmos aws security analyze` plugs into existing pipelines without a translation layer. ## How to Use It The `--stack` filter is where the report becomes operational: it narrows the export to the environment you own, and the mapped component metadata travels with each finding. ```shell atmos aws security analyze --stack prod-use1 --format=sarif --file=findings.sarif atmos aws security analyze --stack prod-use1 --format=ocsf --file=findings.ocsf.json ``` Use SARIF when you want stack-scoped findings in GitHub code scanning or another code-scanning surface. Use OCSF when you want the same stack and component context in your SIEM, GRC tooling, data lake, or security analytics pipeline. ## Get Involved If you're already collecting security findings outside Atmos and want them to flow through the same tooling that manages your stacks, this is the path. Open an issue with the SARIF or OCSF behavior you'd like to see at [github.com/cloudposse/atmos/issues](https://github.com/cloudposse/atmos/issues). --- ## Zero-Configuration AWS SSO Identity Management Atmos now automatically provisions AWS SSO permission sets as identities when you authenticate. Log in once, and all your available roles are instantly ready to use—no manual configuration required. ## The Zero-Configuration Principle One of Atmos's core design principles is **zero-configuration**: the tool should work intelligently out of the box, reducing friction and setup time. With AWS SSO identity auto-provisioning, we're bringing this principle to authentication. Previously, if you had access to 50 AWS accounts with multiple permission sets each, you'd need to manually configure hundreds of identity entries in your `atmos.yaml`. This was tedious, error-prone, and a barrier to adoption. Now, you authenticate once with [`atmos auth login --provider {provider}`](/cli/commands/auth/login), and Atmos automatically discovers and provisions all available permission sets as identities. You can immediately use any role without touching configuration files. ## Why This Matters ### Faster Time to First Auth **Before auto-provisioning:** - Clone repository - Read documentation to understand identity configuration - Query AWS SSO for available accounts and roles - Manually write 50+ identity entries in `atmos.yaml` - Test authentication with each identity **After auto-provisioning:** ```yaml # atmos.yaml auth: providers: sso-prod: kind: aws/iam-identity-center start_url: https://my-org.awsapps.com/start region: us-east-1 auto_provision_identities: true # One line to enable ``` ```bash atmos auth login --provider sso-prod # ✓ Provisioned 47 identities across 12 accounts (2.3s) atmos terraform plan --identity sandbox/PowerUserAccess # Works immediately - no additional configuration ``` ### Developer Experience Auto-provisioning removes common friction points: 1. **No manual configuration**: Identities are discovered automatically from AWS SSO 2. **Always up-to-date**: New accounts and roles appear automatically after re-authentication 3. **Works across teams**: Same configuration works for all team members regardless of their assigned permission sets 4. **Fail-safe**: Provisioning failures don't block authentication—worst case, you fall back to manual configuration ## How It Works ### High-Level Architecture Auto-provisioning follows a two-phase design: **Phase 1: Authentication & Provisioning** 1. User authenticates: `atmos auth login --provider sso-prod` 2. Provider with `auto_provision_identities: true` queries AWS SSO APIs 3. `ListAccounts` discovers all accessible AWS accounts 4. `ListAccountRoles` discovers permission sets for each account 5. Identities are written to `~/.cache/atmos/auth/sso-prod/provisioned-identities.yaml` **Phase 2: Config Loading** 1. User runs any Atmos command: [`atmos terraform plan`](/cli/commands/terraform/plan) 2. Config loader automatically imports provisioned identity files 3. Provisioned imports are loaded FIRST (manual config takes precedence) 4. All identities (manual + provisioned) are available ### Dynamic Import Injection The key innovation is **dynamic import injection** during config loading. Provisioned identities are stored as standard Atmos config files in the XDG cache directory (`~/.cache`), then automatically imported before your manual configuration. This approach has several advantages: - **Separation of concerns**: Provisioned identities don't pollute your version-controlled config - **Manual override**: Your `atmos.yaml` imports process after provisioned imports, allowing you to override or customize any identity - **Standard format**: Provisioned files use the same YAML structure as manual config—you can view, edit, or copy them as needed - **Cache invalidation**: Delete the cache directory to force re-provisioning ### Provider Support Auto-provisioning is currently available for: - **AWS IAM Identity Center (SSO)**: Automatically discovers accounts and permission sets Future provider support is planned for additional cloud platforms and identity providers. ## Configuration ### Enable Auto-Provisioning Add `auto_provision_identities: true` to your AWS SSO provider: ```yaml # atmos.yaml auth: providers: sso-prod: kind: aws/iam-identity-center start_url: https://my-org.awsapps.com/start region: us-east-1 auto_provision_identities: true # Enable auto-provisioning ``` ### Viewing Provisioned Identities Use [`atmos auth list`](/cli/commands/auth/list) to see all identities, including provisioned ones: ```bash atmos auth list # Authentication Configuration # └──sso-prod (aws/iam-identity-center) # └──Identities # ├──● sandbox/PowerUserAccess (sso-prod) 11h58m # ├──● staging/DeployerAccess (sso-prod) 11h58m # ├──● production/DeployerAccess (sso-prod) 11h58m # └──● dev/ReadOnly (sso-prod) 11h58m ``` Provisioned identities appear in the tree view alongside manually configured identities. There's no visual distinction—they're treated as first-class identities. ### Manual Override and Deep Merge Provisioned identities can be enhanced or overridden in your `atmos.yaml`. Atmos performs **deep merge** of manual configuration with auto-provisioned identities, preserving all fields from both sources: ```yaml # atmos.yaml auth: providers: sso-prod: kind: aws/iam-identity-center start_url: https://my-org.awsapps.com/start region: us-east-1 auto_provision_identities: true identities: # Enhance provisioned identity with additional fields production/DeployerAccess: default: true # Mark as default (merged with auto-provisioned fields) session: duration: 12h # Extend from default 1h ``` **Deep Merge Behavior:** When you manually configure an identity that was also auto-provisioned: - **All auto-provisioned fields are preserved** (`provider`, `kind`, `via`, `principal`, etc.) - **Manual fields are added or override** specific nested values - **Result**: Combined identity with fields from both sources **Example:** Auto-provisioned identity: ```yaml production/DeployerAccess: provider: sso-prod kind: aws/permission-set via: provider: sso-prod principal: name: DeployerAccess account: name: production id: "123456789012" ``` Your manual override: ```yaml production/DeployerAccess: default: true session: duration: 12h ``` Final merged identity: ```yaml production/DeployerAccess: provider: sso-prod # ✅ From auto-provisioning kind: aws/permission-set # ✅ From auto-provisioning default: true # ✅ From manual config via: # ✅ From auto-provisioning provider: sso-prod principal: # ✅ From auto-provisioning name: DeployerAccess account: name: production id: "123456789012" session: # ✅ From manual config duration: 12h ``` This deep merge allows you to: - **Mark provisioned identities as default** without losing auto-discovered metadata - **Customize session durations** while keeping all provider/principal information - **Add custom fields** (aliases, tags, metadata) without manual duplication ### Cache Location Provisioned identities are stored following [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html): ``` $XDG_CACHE_HOME/atmos/auth/ └── sso-prod/ └── provisioned-identities.yaml ``` By default, this resolves to `~/.cache/atmos/auth/` on Linux/macOS and `%LOCALAPPDATA%\atmos\cache\` on Windows. You can inspect this file to see exactly what was provisioned. For example, on Linux/macOS: ```bash cat ~/.cache/atmos/auth/sso-prod/provisioned-identities.yaml ``` To force re-provisioning, log out and re-authenticate: ```bash atmos auth logout --provider sso-prod atmos auth login --provider sso-prod ``` ## Use Cases ### 1. Multi-Account AWS Deployments Organizations with many AWS accounts benefit immediately: ```bash # Before: Manually configure 50+ identities # After: One login, 50+ identities available atmos auth login --provider sso-prod atmos terraform plan --identity sandbox/PowerUserAccess atmos terraform plan --identity staging/DeployerAccess atmos terraform plan --identity dev/ReadOnly # All work without additional configuration ``` ### 2. Team Onboarding New team members can authenticate and deploy in minutes: ```bash git clone https://github.com/acme/infrastructure cd infrastructure atmos auth login --provider sso-prod atmos terraform plan --identity sandbox/PowerUserAccess # Ready to deploy ``` ## Implementation Details ### Non-Fatal Provisioning Provisioning is designed to be **non-fatal**. If provisioning fails for any reason: 1. The failure is logged as a warning 2. Authentication proceeds successfully 3. You fall back to manually configured identities This ensures that auto-provisioning never blocks your workflow—worst case, you're in the same position as before the feature existed. ### Identity Naming Convention Provisioned identities use the naming convention: ``` {account-name}/{permission-set-name} ``` Examples: - `sandbox/PowerUserAccess` - `staging/DeployerAccess` - `dev/ReadOnly` This convention ensures unique names and makes it easy to understand which account and role each identity represents. ## Get Started Auto-provisioning is available in Atmos v2.x. To enable it: 1. Update your `atmos.yaml`: ```yaml auth: providers: sso-prod: kind: aws/iam-identity-center start_url: https://your-org.awsapps.com/start region: us-east-1 auto_provision_identities: true ``` 2. Authenticate: ```bash atmos auth login --provider sso-prod ``` 3. Use any available role: ```bash atmos terraform plan --identity sandbox/PowerUserAccess ``` For detailed configuration options, see the [Authentication Documentation](/cli/commands/auth/usage). ## Feedback We'd love to hear how auto-provisioning works for your use case. Share your experience in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) or report issues in [GitHub Issues](https://github.com/cloudposse/atmos/issues). --- ## One Browser Flow Unlocks Every AWS SSO Provider [`atmos auth login`](/cli/commands/auth/login) now performs **one** browser interaction per AWS SSO portal, no matter how many `aws/iam-identity-center` providers in your `atmos.yaml` point at it. Cached tokens also refresh silently for the full ~8-hour portal session window — no more re-prompt every hour. ## What Changed The `aws/iam-identity-center` provider's token-acquisition path was rebuilt around the AWS SSO token provider behavior that AWS CLI v2 has used since late 2022: - **Implicit session sharing.** Providers with identical `start_url` + `region` now share an OIDC token in a single in-process registry, keyed by `sha1(start_url + "|" + region)`. The first provider to authenticate completes the device-authorization flow once; subsequent providers reuse the same token without prompting. - **Refresh-token renewal.** When the cached access token expires but the refresh token is still valid, atmos silently exchanges it via `ssooidc:CreateToken` with `grant_type=refresh_token`. Browser interaction only happens when both the access token AND the refresh token have expired (typically after ~8 hours of portal-level session). - **Session-keyed disk cache.** The on-disk cache moved from `~/.cache/atmos/aws-sso//token.json` to `~/.cache/atmos/aws-sso/sessions/.json`. Renaming a provider in `atmos.yaml` no longer invalidates a valid cached token, and the cache file format is compatible with the AWS SDK's `ssocreds` package. There are **no configuration changes**. Same `atmos.yaml`, same `kind: aws/iam-identity-center`, same `start_url`/`region` fields. The improvement is invisible until you notice you're hitting the browser less often. ## Why This Matters A common atmos setup has one provider per environment (dev / staging / prod) all backed by the same corporate SSO portal. Before this release, `atmos auth login` would launch the browser flow three times — once per provider — even though the underlying portal session is the same. Worse, each provider had its own token cache file, so a rename or a config refactor silently invalidated cached tokens. The headline UX problem: AWS itself shows users the message _"Your credentials have been shared successfully and can be used until your session expires"_ after a single portal sign-in. Atmos's behavior didn't match that promise. This release closes the gap. The secondary problem solved is **silent renewal**. The original SSO flow re-ran the full browser interaction on every access-token expiry (~1 hour). With refresh tokens wired into the cache, atmos now mirrors the AWS CLI's behavior: one browser interaction holds for the portal-session lifetime, often a full 8-hour workday. ## How It Works The new `Authenticate()` flow is a four-stage pipeline: 1. **In-memory session cache** — instant return for any provider that shares an already-authenticated portal in the current process. 2. **On-disk session cache** — survives process restart; populated by prior atmos invocations. 3. **Refresh-token exchange** — silent network call when the cached access token expired but the refresh token is still valid. No browser. 4. **Device-authorization flow** — full browser interaction. Only runs when all three fast paths miss. Single-flighted per session, so parallel [`atmos terraform`](/cli/commands/terraform/usage) invocations don't race into duplicate browser prompts. Per-session mutexes coalesce concurrent callers: if you fire [`atmos terraform plan`](/cli/commands/terraform/plan) against three components backed by the same SSO portal, only one device-auth flow runs and the other two wait on its result. ## Migration None required. Existing `atmos.yaml` files keep working. Old per-provider cache files can become orphans on first login post-upgrade, and they are not deleted automatically. [`atmos auth logout`](/cli/commands/auth/logout) evicts the in-memory portal session and removes the new session-keyed cache file; clearing any leftover legacy per-provider files is a manual step. If you previously relied on atmos's cache being disjoint from the `aws` CLI's cache, that separation is unchanged in this release. Sharing the cache with `aws sso login` directly is tracked as a future opt-in; see the PRD at [`docs/prd/aws-sso-session-support.md`](https://github.com/cloudposse/atmos/blob/main/docs/prd/aws-sso-session-support.md) for the design rationale. ## Get Involved Found an issue or have feedback? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Enhanced AWS SSO Authentication: Better UX with Styled Dialogs and Graceful Cancellation We've significantly improved the AWS SSO authentication experience with styled verification code dialogs, animated status indicators, and proper Ctrl+C handling. ## What Changed When authenticating with AWS IAM Identity Center (SSO), Atmos now displays the verification code in a styled dialog box with clear visual feedback during the authentication process. ### New Features **1. Styled Verification Dialog** The verification code is now displayed in a bordered dialog box with color-coded elements for better visibility: ``` ╭───────────────────────────────────────────────╮ │ 🔐 AWS SSO Authentication Required │ │ │ │ Verification Code: WDDD-HRQV │ │ │ │ https://company.awsapps.com/start/#/device │ │ │ │ Opening browser... If it doesn't open, │ │ visit the URL above. │ ╰───────────────────────────────────────────────╯ ``` **2. Animated Spinner** While waiting for authentication, an animated spinner shows real-time status with success/failure feedback: ``` ⠋ Waiting for authentication... ``` **3. Proper Ctrl+C Handling** Pressing Ctrl+C during authentication now properly cancels the entire authentication process: - Immediately stops the polling goroutine - Closes all channels cleanly - Returns a clear "authentication cancelled" error - No resource leaks or hanging processes **4. Graceful Degradation** The enhancement automatically adapts to your environment: - **Terminal environments**: Beautiful styled dialog with colors and animations - **CI/CD pipelines**: Simple text output optimized for logs - **Non-TTY environments**: Plain text fallback ## Why This Matters ### Better User Experience Previously, the verification code was not displayed in the terminal at all. Now you can: - **See the verification code directly in your terminal** without having to find it in the browser - **Quickly locate the code** when switching between browser and terminal - **Verify the code matches** what AWS displays in the browser - **Monitor authentication progress** with real-time feedback - **Cancel authentication cleanly** with Ctrl+C without leaving background processes running ### Consistent Across All Commands This enhancement applies to **all** Atmos authentication commands that trigger AWS SSO login: - `atmos auth login` - Interactive authentication - `atmos auth env` - Get credentials as environment variables - `atmos auth exec` - Execute commands with authenticated credentials Any command that requires AWS SSO authentication will now show the styled verification dialog. ## Important Note The verification code displayed is a **device authorization user code** (e.g., "WDDD-HRQV") generated by AWS for the OAuth 2.0 device flow—**this is NOT an MFA token**. Any MFA prompts (such as authenticator app codes or SMS codes) will appear in your browser during the authentication flow, not in the terminal. ## Technical Details ### Implementation The enhancement uses the [Charm Bracelet](https://charm.sh/) ecosystem for beautiful terminal UIs: - **Lipgloss** - Styling and layout for the verification dialog - **Bubbletea** - Terminal UI framework for the interactive spinner - **Bubbles** - Pre-built spinner component The implementation includes: - TTY detection to automatically choose between styled dialogs and plain text - Context-based cancellation for clean shutdown on Ctrl+C - Proper goroutine management to prevent resource leaks - Full test coverage with unit tests for all code paths ### Test Coverage We've added comprehensive tests covering: - Styled dialog rendering with various inputs - Plain text fallback for non-TTY environments - Context cancellation behavior - Spinner state management ## Related Documentation - [atmos auth login](/cli/commands/auth/login) - Authentication command reference - [atmos auth env](/cli/commands/auth/env) - Environment variables command - [atmos auth exec](/cli/commands/auth/exec) - Execute with credentials command This enhancement makes AWS SSO authentication more user-friendly while maintaining full backward compatibility with existing workflows. --- ## AWS YAML Functions for Identity and Region Atmos now includes four AWS YAML functions that retrieve identity and region information directly in stack configurations: `!aws.account_id`, `!aws.caller_identity_arn`, `!aws.caller_identity_user_id`, and `!aws.region`. ## What's New These functions use the AWS STS GetCallerIdentity API to retrieve information about the current AWS credentials: | Function | Returns | Example Output | |----------|---------|----------------| | `!aws.account_id` | AWS account ID | `123456789012` | | `!aws.caller_identity_arn` | Full ARN of caller | `arn:aws:iam::123456789012:user/deploy` | | `!aws.caller_identity_user_id` | Unique user identifier | `AIDAEXAMPLE123456789` | | `!aws.region` | Current AWS region | `us-east-1` | ## Usage ```yaml components: terraform: s3-bucket: vars: # Pass account ID and region for bucket naming in Terraform aws_account_id: !aws.account_id aws_region: !aws.region iam-policy: vars: # Reference caller identity in policies deployer_arn: !aws.caller_identity_arn ``` ## Use Cases **Dynamic Resource Naming**: Include account IDs in S3 bucket names, DynamoDB tables, or other resources that require globally unique names. **Audit and Logging**: Capture the ARN or user ID of the identity running deployments for audit trails. **Cross-Account References**: Build ARNs dynamically when referencing resources across accounts. **Region-Aware Configuration**: Configure resources based on the current AWS region without hardcoding values. ## Caching and Performance All four functions share a single cached STS API call per CLI invocation. The first function call fetches the identity; subsequent calls use the cached result. This means using multiple AWS functions in the same configuration adds no extra API overhead. ## Authentication Integration When using [Atmos Authentication](/cli/commands/auth/usage), these functions automatically use the credentials from the configured auth context. This works with AWS SSO, IAM roles, and other credential sources supported by the AWS SDK. ## Comparison with Terragrunt These functions provide equivalent functionality to Terragrunt's built-in helpers: | Atmos | Terragrunt | |-------|------------| | `!aws.account_id` | `get_aws_account_id()` | | `!aws.caller_identity_arn` | `get_aws_caller_identity_arn()` | | `!aws.caller_identity_user_id` | `get_aws_caller_identity_user_id()` | | `!aws.region` | Similar to region from `get_aws_caller_identity()` | ## Learn More - [`!aws.account_id`](/functions/yaml/aws.account-id) - Full documentation - [`!aws.caller_identity_arn`](/functions/yaml/aws.caller-identity-arn) - Full documentation - [`!aws.caller_identity_user_id`](/functions/yaml/aws.caller-identity-user-id) - Full documentation - [`!aws.region`](/functions/yaml/aws.region) - Full documentation - [YAML Functions Overview](/functions/yaml/) - All available YAML functions --- ## Azure AKS and ACR Authentication: Native kubectl and Docker Access Without the Azure CLI Getting `kubectl` and `docker` working against Azure resources has always meant a side trip through the Azure CLI. `az aks get-credentials` writes a kubeconfig entry — and for AAD-enabled clusters, the modern default, that entry calls out to a separate `kubelogin` binary just to mint a token. `az acr login` does the same dance for container registries. Both assume you're already logged into `az`, which may not match the identity you just authenticated with in Atmos. ## The Problem Atmos already solves this for AWS: authenticate once with [`atmos auth login`](/cli/commands/auth/login), and linked EKS clusters and ECR registries are configured automatically. Azure had no equivalent — every AKS or ACR session meant switching tools and re-authenticating outside of Atmos, and every extra tool is another thing to install and keep in sync with the credentials you're actually using. ## The Fix Atmos now extends its `auth.integrations` system to Azure AKS and ACR. Authenticate with an Azure identity and Atmos provisions kubeconfig and Docker credentials for any linked clusters and registries in the same step: ```yaml auth: identities: azure-dev: kind: azure/subscription via: provider: azure-device-code principal: subscription_id: 11111111-1111-1111-1111-111111111111 integrations: dev/aks: kind: azure/aks via: identity: azure-dev spec: cluster: name: dev-cluster resource_group: dev-rg dev/acr: kind: azure/acr via: identity: azure-dev spec: registry: name: myregistry ``` ```bash $ atmos auth login azure-dev ✓ AKS kubeconfig: dev-cluster → ~/.config/atmos/kube/config ✓ ACR login: myregistry.azurecr.io (expires in 2h59m) $ kubectl get pods $ docker pull myregistry.azurecr.io/myimage:latest ``` No `az` CLI, no `kubelogin` binary — kubectl calls [`atmos azure aks token`](/cli/commands/azure/azure-aks-token) for a fresh token the same way it would call `kubelogin`, and ACR credentials land in the standard Docker config location. ## How to Use It Both integrations also work as standalone commands, independent of `atmos auth login`: ```bash # AKS: write or refresh a kubeconfig entry atmos azure aks update-kubeconfig --integration dev/aks # ACR: log Docker into a registry atmos azure acr login dev/acr # Or drive either from an identity directly atmos azure aks update-kubeconfig --cluster-name dev-cluster --resource-group dev-rg --identity azure-dev atmos azure acr login --identity azure-dev ``` ### Running kubectl Run standalone, `update-kubeconfig` writes to Atmos's own kubeconfig (`~/.config/atmos/kube/config` by default). A command can't set environment variables in your shell, so point `kubectl` at that file — or let Atmos launch `kubectl` with `KUBECONFIG` already set for you: ```bash # Option A — use the Atmos kubeconfig directly atmos azure aks update-kubeconfig --integration dev/aks export KUBECONFIG=~/.config/atmos/kube/config kubectl get nodes # Option B — let Atmos inject KUBECONFIG into the command it runs atmos auth exec --identity azure-dev -- kubectl get nodes ``` ```console NAME STATUS ROLES AGE VERSION aks-system-64934532-vmss000000 Ready 2h v1.35.6 aks-system-64934532-vmss000001 Ready 2h v1.35.6 ``` Either way, every `kubectl` call mints its token through `atmos azure aks token` against the Atmos-managed identity — no `az` CLI and no `kubelogin`. Prefer to merge into your standard kubeconfig instead? Pass `--kubeconfig ~/.kube/config` and skip the `export`. See the [Azure AKS](/cli/commands/azure/aks/update-kubeconfig) and [Azure ACR](/cli/commands/azure/acr-login) command docs for the full set of flags and configuration options. ## Get Involved Atmos is open source and we'd love your feedback. Join the conversation in the [Cloud Posse community](https://cloudposse.com/slack) or open an issue on [GitHub](https://github.com/cloudposse/atmos). --- ## Native Azure Authentication Support We're thrilled to announce native Azure authentication support in Atmos! You can now authenticate to Azure using [`atmos auth login`](/cli/commands/auth/login) with device code flow, OIDC, and service principals - working identically to `az login` with full Terraform provider compatibility. ## What's New Atmos now provides first-class Azure authentication with three authentication methods: - **Device Code Flow**: Browser-based authentication for interactive developer sessions - **OIDC**: Workload identity for GitHub Actions, GitLab CI, and Azure DevOps pipelines - **Service Principals**: Client credential authentication for automation and service accounts All authentication methods write credentials to the Azure CLI MSAL cache (`~/.azure/msal_token_cache.json`), ensuring 100% compatibility with Terraform's Azure providers: azurerm, azuread, and azapi. ## Quick Start Configure Azure device code authentication in your `atmos.yaml`: ```yaml auth: providers: azure-dev: kind: azure/device-code spec: tenant_id: "12345678-1234-1234-1234-123456789012" subscription_id: "87654321-4321-4321-4321-210987654321" location: "eastus" identities: azure-dev-subscription: default: true kind: azure/subscription via: provider: azure-dev principal: subscription_id: "87654321-4321-4321-4321-210987654321" location: "eastus" ``` Then authenticate: ```bash atmos auth login ``` Atmos will: 1. Display a device code and verification URL 2. Open your browser to https://microsoft.com/devicelogin 3. Prompt you to enter the code and sign in 4. Cache credentials for all Azure providers Now you can use Terraform with Azure: ```bash atmos terraform plan my-component -s my-stack atmos terraform apply my-component -s my-stack ``` ## Why This Matters ### Works Exactly Like `az login` Atmos Azure authentication is designed to work identically to `az login`. When you run `atmos auth login`, it: - Writes credentials to `~/.azure/msal_token_cache.json` (Azure CLI MSAL cache) - Updates `~/.azure/azureProfile.json` with subscription configuration - Sets `ARM_USE_CLI=true` for Terraform providers - Provides all three token scopes required for full Azure functionality: - `https://management.azure.com/.default` (Azure Resource Manager) - `https://graph.microsoft.com/.default` (Azure AD operations) - `https://vault.azure.net/.default` (Azure KeyVault operations) This means your existing Terraform code works without any changes - Atmos authentication is a drop-in replacement for `az login`. ### Full Terraform Provider Compatibility All three major Terraform Azure providers work seamlessly: **azurerm Provider** - Including KeyVault operations: ```hcl provider "azurerm" { features {} # Atmos automatically sets ARM_USE_CLI=true } resource "azurerm_key_vault" "example" { name = "my-keyvault" location = "eastus" # KeyVault operations work because Atmos provides KeyVault token scope } ``` **azuread Provider** - For Azure Active Directory: ```hcl provider "azuread" { # Uses Graph API token from Atmos } resource "azuread_group" "example" { display_name = "My Group" security_enabled = true } ``` **azapi Provider** - Alternative Azure management: ```hcl provider "azapi" { # Works with Atmos authentication out of the box } ``` ### CI/CD with OIDC Use workload identity federation for passwordless authentication in pipelines: ```yaml auth: providers: azure-ci: kind: azure/oidc spec: tenant_id: "YOUR_TENANT_ID" client_id: "YOUR_APP_CLIENT_ID" subscription_id: "YOUR_SUBSCRIPTION_ID" identities: azure-prod-ci: kind: azure/subscription via: provider: azure-ci principal: subscription_id: "YOUR_SUBSCRIPTION_ID" ``` **GitHub Actions Example:** ```yaml name: Deploy Infrastructure on: push: branches: [main] permissions: id-token: write # Required for OIDC contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install Atmos uses: cloudposse/github-action-setup-atmos@v2 - name: Authenticate to Azure run: atmos auth login --identity azure-prod-ci env: AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Deploy run: atmos terraform apply my-component -s prod ``` ### Service Principals for Automation Service principals provide non-interactive authentication for automated workflows, CI/CD pipelines, and scheduled jobs: ```yaml auth: providers: azure-automation: kind: azure/service-principal spec: tenant_id: "YOUR_TENANT_ID" client_id: "YOUR_SERVICE_PRINCIPAL_CLIENT_ID" subscription_id: "YOUR_SUBSCRIPTION_ID" location: "eastus" identities: azure-automation-prod: kind: azure/subscription via: provider: azure-automation principal: subscription_id: "YOUR_SUBSCRIPTION_ID" ``` **Create a service principal:** ```bash # Create service principal and assign Contributor role az ad sp create-for-rbac \ --name "atmos-automation" \ --role Contributor \ --scopes /subscriptions/YOUR_SUBSCRIPTION_ID # Output includes: # { # "appId": "YOUR_CLIENT_ID", # "displayName": "atmos-automation", # "password": "YOUR_CLIENT_SECRET", # "tenant": "YOUR_TENANT_ID" # } ``` **Authenticate using environment variables:** ```bash # Set credentials in environment export AZURE_CLIENT_ID="YOUR_CLIENT_ID" export AZURE_CLIENT_SECRET="YOUR_CLIENT_SECRET" export AZURE_TENANT_ID="YOUR_TENANT_ID" export AZURE_SUBSCRIPTION_ID="YOUR_SUBSCRIPTION_ID" # Authenticate atmos auth login --identity azure-automation-prod # Run Terraform atmos terraform apply my-component -s prod ``` **Common use cases:** - **Scheduled Jobs**: Cron jobs or Azure DevOps scheduled pipelines - **CI/CD Pipelines**: Jenkins, GitLab CI, CircleCI (when OIDC not available) - **Automation Scripts**: Python/Go scripts for infrastructure management - **Multi-tenant SaaS**: Isolated credentials per customer environment **Security best practices:** - Store `client_secret` in secure secret management (Azure Key Vault, HashiCorp Vault) - Use least-privilege RBAC roles (avoid Owner, prefer specific roles like Network Contributor) - Rotate credentials regularly (90-day rotation recommended) - Use certificate-based authentication for production (more secure than secrets) - Prefer OIDC over service principals when available (passwordless) ## Multi-Subscription Workflows Work with multiple Azure subscriptions seamlessly: ```yaml auth: providers: azure-main: kind: azure/device-code spec: tenant_id: "YOUR_TENANT_ID" subscription_id: "DEV_SUBSCRIPTION_ID" identities: azure-dev: kind: azure/subscription via: provider: azure-main principal: subscription_id: "DEV_SUBSCRIPTION_ID" location: "eastus" azure-staging: kind: azure/subscription via: provider: azure-main principal: subscription_id: "STAGING_SUBSCRIPTION_ID" location: "westus" azure-prod: kind: azure/subscription via: provider: azure-main principal: subscription_id: "PROD_SUBSCRIPTION_ID" location: "eastus2" ``` **Switch between subscriptions effortlessly:** ```bash # Deploy to dev atmos terraform apply my-component -s dev --identity azure-dev # Deploy to staging atmos terraform apply my-component -s staging --identity azure-staging # Deploy to prod atmos terraform apply my-component -s prod --identity azure-prod ``` ## Implementation Details ### Complete Token Support One of the key challenges in implementing Azure authentication was ensuring we provide all the token scopes that `az login` provides. Many implementations only provide the Azure Resource Manager token, which breaks KeyVault operations. Atmos Azure authentication provides all three required token scopes: 1. **Management Token** (`https://management.azure.com/.default`) - Used by: azurerm, azapi providers - Purpose: Azure Resource Manager operations (VMs, networks, storage, etc.) 2. **Graph API Token** (`https://graph.microsoft.com/.default`) - Used by: azuread provider - Purpose: Azure Active Directory operations (users, groups, service principals) 3. **KeyVault Token** (`https://vault.azure.net/.default`) - Used by: azurerm provider for KeyVault operations - Purpose: Azure KeyVault secrets, keys, and certificates management This comprehensive token support ensures that all Terraform resources work correctly, including KeyVault certificate contacts, secret management, and AD group operations. ### MSAL Cache Compatibility Atmos writes credentials to the same MSAL cache format that the Azure CLI uses, with proper token entries for each scope: ```json { "AccessToken": { "homeAccountId-login.microsoftonline.com-accesstoken-clientId-tenantId-https://management.azure.com/.default": { "credential_type": "AccessToken", "secret": "eyJ0eXAi...", "home_account_id": "objectId.tenantId", "environment": "login.microsoftonline.com", "client_id": "04b07795-8ddb-461a-bbee-02f9e1bf7b46", "target": "https://management.azure.com/.default", "token_type": "Bearer" }, "homeAccountId-login.microsoftonline.com-accesstoken-clientId-tenantId-https://graph.microsoft.com/.default": { ... }, "homeAccountId-login.microsoftonline.com-accesstoken-clientId-tenantId-https://vault.azure.net/.default": { ... } }, "Account": { ... } } ``` This ensures perfect compatibility with Terraform providers that read from the Azure CLI credential cache. ## Migration from az login Already using `az login`? Atmos is a drop-in replacement: **Before:** ```bash az login az account set --subscription "YOUR_SUBSCRIPTION_ID" terraform apply ``` **After:** ```bash atmos auth login --identity azure-dev atmos terraform apply my-component -s my-stack ``` Both write to the same files, so your Terraform code needs zero changes. ## Security Features ### Secure Credential Storage - Credentials are cached in your system keyring (Keychain on macOS, Secret Service on Linux, Credential Manager on Windows) - Tokens are also written to Azure CLI MSAL cache for Terraform compatibility - Both storages are secured by your operating system ### Token Expiration Handling - Azure tokens typically expire after 1 hour - Atmos automatically caches valid tokens and reuses them - Re-authenticate when expired: `atmos auth login` - Check status anytime: [`atmos auth whoami`](/cli/commands/auth/whoami) ### Least Privilege Access Configure appropriate RBAC roles for your identities: ```bash # Example: Grant Contributor role to a service principal az role assignment create \ --assignee YOUR_APP_CLIENT_ID \ --role Contributor \ --scope /subscriptions/YOUR_SUBSCRIPTION_ID ``` ## Documentation We've created comprehensive documentation for Azure authentication: - **[Azure Authentication Guide](/tutorials/azure-authentication)** - Complete tutorial covering all authentication methods - **[Auth Login Command](/cli/commands/auth/login)** - Updated with Azure examples - **[Auth Commands Reference](/cli/commands/auth/usage)** - All authentication commands ## What's Next This is just the beginning of Azure support in Atmos. Future enhancements include: - Azure Managed Identity support - Azure Government Cloud support - Azure CLI credential migration tools - Enhanced Azure-specific debugging and logging ## Try It Out Update to the latest version of Atmos and try Azure authentication: ```bash # Configure Azure authentication in atmos.yaml # (See examples above) # Authenticate to Azure atmos auth login # Check your authentication status atmos auth whoami # Use with Terraform atmos terraform plan my-component -s my-stack # List all configured identities atmos auth list # Get environment variables for an identity atmos auth env --identity azure-dev # Spawn a shell with Azure credentials atmos auth shell azure-dev ``` We're excited to bring Azure authentication to Atmos and look forward to seeing how you use it in your multi-cloud infrastructure workflows! ## Feedback Have feedback or issues? Please let us know: - [GitHub Issues](https://github.com/cloudposse/atmos/issues) - [Slack Community](https://slack.cloudposse.com) --- Special thanks to the community for requesting Azure support and providing feedback during development! --- ## Automatic Azure Backend Provisioning for Terraform State Bootstrapping Terraform state on Azure has always meant a detour outside Terraform: before you can run a single `plan`, you have to hand-create a resource group, a storage account (with the right TLS, public-access, and auth settings), and a blob container — by portal, `az` script, or a one-off Terraform component you cold-start and then migrate. AWS users have had one-line automatic backend provisioning for a while. Azure users had a checklist. Not anymore. ## The Problem Atmos could already generate `backend.tf.json` for an `azurerm` backend and read its state in-process — but it couldn't _create_ the backend. Turn on backend auto-provisioning against an `azurerm` backend and nothing happened; it silently skipped, because provisioning only knew how to make S3 buckets. So every new subscription hit the same chicken-and-egg: you need remote state to run Terraform, but you need something _other_ than Terraform to create that remote state first. The workaround was a bespoke storage-account component that runs on local state, then migrates its own state into the account it just created. It works, but it's ceremony every team re-invents — and it's exactly the friction AWS users don't have. ## The Fix Atmos now provisions `azurerm` backends automatically, the same way it does for S3. Point a component at an `azurerm` backend, enable provisioning, and Atmos creates what's missing — the resource group, the storage account, and the container — with secure defaults, before `terraform init` runs. Everything is created with opinionated, hardcoded best practices: - **TLS 1.2 minimum** and **HTTPS-only** traffic - **Public blob access blocked**; the state container is **private** - **Blob versioning enabled** — the direct analog of S3 versioning, so every state write is recoverable - **Soft delete** (blob + container) with 30-day retention as a safety net - **Entra ID hardening**: when your backend sets `use_azuread_auth: true`, the storage account is created with shared-key access disabled — no account keys to leak And one thing Atmos deliberately does **not** create: a lock table. On Azure, state locking is built into Blob Storage — the `azurerm` backend takes an exclusive **blob lease** on the state blob during each operation, so concurrent runs are serialized with no extra resource. (On AWS that role is played by a DynamoDB table or native S3 lockfiles; on Azure there's simply nothing to provision.) ## How to Use It Add `provision.backend.enabled: true` to a component that uses an `azurerm` backend: ```yaml components: terraform: vpc: auth: providers: azure: type: azure/interactive identity: platform backend_type: azurerm backend: resource_group_name: rg-tfstate-cus storage_account_name: stexampletfstateplatformcus container_name: tfstate key: vpc.terraform.tfstate use_azuread_auth: true provision: backend: enabled: true ``` Then run Terraform as usual: ```bash atmos terraform apply vpc -s platform-cus # Backend resource group, storage account, and container are created if missing, then init/apply proceed. ``` That's it. Atmos checks whether the backend is fully provisioned, creates only what's missing, and continues. It's idempotent — safe to leave enabled and safe to re-run. A few things worth knowing: - **You don't put `location` in the backend block.** It isn't a valid `azurerm` backend argument, so Atmos takes the region from your active Azure identity — or, if the resource group already exists, from the group itself. Pre-create the resource group and you don't need to configure a location at all. - **Subscription** comes from `backend.subscription_id` if set, otherwise from your active Azure identity. - **It composes with inheritance.** Enable provisioning once at the org or environment level and override per component — on in dev/qa, off in prod where state storage is module-managed. ### Managing the backend explicitly The same lifecycle commands that work for S3 now work for `azurerm`: ```bash # Create the backend explicitly (e.g. in a CI bootstrap stage) atmos terraform backend create vpc -s platform-cus # Tear it down (deletes the storage account and all state in it — resource group is preserved) atmos terraform backend delete vpc -s platform-cus --force ``` Deletion always requires `--force`, and it removes the storage account (and therefore every state file in it, just as deleting an S3 bucket does), while leaving the resource group in place. ## Not for Production As-Is Like the S3 provisioner, this is built for fast, secure bootstrapping — dev, test, CI, and cold-starts — not to replace a production-grade module. It doesn't set up customer-managed keys, private endpoints, network ACLs, geo/zone redundancy, or lifecycle policies. When you're ready to harden, import the resource group, storage account, and container into a managed module (such as `Azure/avm-res-storage-storageaccount`) and keep using the same backend — no state migration needed, because the account keeps its name and contents. You can even leave provisioning enabled; Atmos detects the resources exist and skips. For usage and configuration, see [Backend Provisioning](/stacks/components/provision/backend). ## Get Involved Give it a try on your Azure subscriptions and let us know how it goes. Questions or ideas? Start a thread in [GitHub Discussions](https://github.com/cloudposse/atmos/discussions), or open an issue in the [issue tracker](https://github.com/cloudposse/atmos/issues). --- ## Azure Blob Storage Support for !terraform.state Function Atmos now supports Azure Blob Storage backends in the [`!terraform.state`](/functions/yaml/terraform.state) YAML function. Read Terraform outputs directly from Azure-backed state files without initializing Terraform—bringing the same blazing-fast performance to Azure that S3 users already enjoy. ## What's New The `!terraform.state` YAML function now supports **Azure Blob Storage (azurerm)** backends, joining existing support for S3 and local backends. This means you can retrieve Terraform outputs from Azure-backed state files at lightning speed—without the overhead of Terraform initialization. ### Why This Matters Before this feature, if you were using Azure Blob Storage as your Terraform backend, you had two options for reading remote state: 1. **[`!terraform.output`](/functions/yaml/terraform.output)** - Slow but reliable. Requires full Terraform initialization, provider downloads, and varfile generation. 2. **[`!store`](/functions/yaml/store)** - Fast but requires extra setup. You had to manually configure external secret stores. Now you can use **`!terraform.state`** with Azure backends—getting **10-100x faster performance** compared to `!terraform.output` by reading directly from blob storage. ## How It Works ### Backend Configuration Configure your Terraform component with an `azurerm` backend: ```yaml components: terraform: vpc: backend_type: azurerm backend: azurerm: storage_account_name: "mystorageaccount" container_name: "tfstate" key: "vpc.terraform.tfstate" ``` ### Reading State Use the `!terraform.state` function to read outputs: ```yaml components: terraform: eks-cluster: vars: # Get vpc_id output from vpc component in current stack vpc_id: !terraform.state vpc vpc_id # Get private subnet IDs subnet_ids: !terraform.state vpc private_subnet_ids # Get first subnet using YQ expression subnet_id: !terraform.state vpc .private_subnet_ids[0] ``` ### Cross-Stack References Reference components from different stacks: ```yaml components: terraform: tgw: vars: # Get VPC ID from production stack vpc_id: !terraform.state vpc plat-ue2-prod vpc_id # Use template for dynamic stack names vpc_id: !terraform.state vpc {{ printf "net-%s-%s" .vars.environment .vars.stage }} vpc_id ``` ## Authentication The Azure Blob Storage integration uses **Azure DefaultAzureCredential**, which supports multiple authentication methods automatically: 1. **Environment variables** - `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` 2. **Managed Identity** - When running in Azure (AKS, VMs, Functions) 3. **Azure CLI credentials** - `az login` 4. **Visual Studio Code credentials** - Authenticated VS Code sessions No additional configuration needed—just authenticate using your preferred method. ## Workspace Handling Azure Blob Storage uses a specific naming convention for workspaces: - **Default workspace**: Uses the key as-is (e.g., `terraform.tfstate`) - **Non-default workspaces**: Appends workspace as suffix (e.g., `terraform.tfstateenv:dev`) Atmos handles this automatically—you don't need to worry about the naming convention. ### Example If you have: - Key: `apimanagement.terraform.tfstate` - Workspace: `dev-wus3-apimanagement-be` Atmos will look for: `apimanagement.terraform.tfstateenv:dev-wus3-apimanagement-be` ## Performance Benefits ### Before: Using `!terraform.output` ```bash $ time atmos terraform plan eks-cluster -s plat-ue2-dev # Must initialize Terraform for each dependency Initializing vpc component... Downloading providers... Generating backend config... Generating varfiles... Reading outputs... real 2m34.521s ``` ### After: Using `!terraform.state` ```bash $ time atmos terraform plan eks-cluster -s plat-ue2-dev # Direct blob storage access Reading state from Azure Blob Storage... real 0m3.142s ``` **~50x faster** in this example—and the speedup grows with infrastructure complexity. ## Advanced Features ### YQ Expressions Use YQ expressions for complex data extraction: ```yaml vars: # Get nested map values db_endpoint: !terraform.state database .config_map.endpoint # String concatenation jdbc_url: !terraform.state 'postgres .master_hostname | "jdbc:postgresql://" + . + ":5432/events"' # Default values for unprovisioned components username: !terraform.state config .username // "default-user" ``` ### Caching Results are cached in memory per CLI execution: ```yaml vars: # All three calls use the same cached result sg_id_1: !terraform.state vpc security_group_id sg_id_2: !terraform.state vpc security_group_id sg_id_3: !terraform.state vpc {{ .stack }} security_group_id ``` The first call reads from Azure; subsequent calls return cached data instantly. ### Error Handling - **Blob not found (404)**: Returns `null` (component not provisioned yet) - **Permission denied (403)**: Returns clear error message - **Network errors**: Automatically retries up to 2 times with exponential backoff ## Technical Details ### Implementation Highlights - **Azure SDK for Go** - Uses official `github.com/Azure/azure-sdk-for-go/sdk/storage/azblob` package - **Client caching** - Azure Blob clients are cached per storage account/container - **Retry logic** - Automatic retry with exponential backoff for transient failures - **Nil safety** - Robust error handling prevents panics - **Test coverage** - Comprehensive unit tests with mocked Azure SDK - **Cross-platform** - Works on Linux, macOS, and Windows ### Backend Configuration Options All standard Azure backend options are supported: ```yaml backend: azurerm: storage_account_name: "mystorageaccount" # Required container_name: "tfstate" # Required key: "terraform.tfstate" # Optional (default: terraform.tfstate) # Authentication happens via DefaultAzureCredential ``` ## Migration Guide ### From `!terraform.output` to `!terraform.state` The syntax is identical—just replace `!terraform.output` with `!terraform.state`: ```yaml # Before vpc_id: !terraform.output vpc vpc_id # After vpc_id: !terraform.state vpc vpc_id ``` ### From `!store` to `!terraform.state` Simplify your configuration by removing store setup: ```yaml # Before: Required store configuration vpc_id: !store azurekeyvault plat-ue2-dev vpc vpc_id # After: Direct state access vpc_id: !terraform.state vpc vpc_id ``` ## Examples ### Basic Usage ```yaml components: terraform: app: vars: # String output security_group_id: !terraform.state security-group id # List output subnet_ids: !terraform.state vpc private_subnet_ids # Map output config: !terraform.state config config_map ``` ### Cross-Region References ```yaml components: terraform: replication: vars: # Reference component from different region primary_db: !terraform.state database {{ printf "%s-use1-%s" .vars.tenant .vars.stage }} endpoint ``` ### Disaster Recovery Scenarios ```yaml components: terraform: failover: vars: # Primary region primary_vpc: !terraform.state vpc plat-ue2-prod vpc_id # DR region with default fallback dr_vpc: !terraform.state vpc plat-uw2-prod .vpc_id // "vpc-mock-dr" ``` ## Considerations - **Secrets exposure**: Using `!terraform.state` with secrets will expose them in [`atmos describe`](/cli/commands/describe/usage) output - **Permission scoping**: Ensure your Azure credentials have access to all referenced storage accounts - **Cross-region access**: Consider latency when reading state across regions - **Cold starts**: Components not yet provisioned return `null` (use YQ default values to handle this) ## Try It Now Upgrade to the latest Atmos release and start using Azure Blob Storage backends: ```bash # Check your version atmos version # Describe a component using Azure backend atmos describe component vpc -s plat-ue2-dev # Use !terraform.state in your stack configs # (See examples above) ``` ## Documentation - **[!terraform.state Function Reference](/functions/yaml/terraform.state)** - Complete usage documentation - **[Terraform Backends](/components/terraform/backends)** - Backend configuration guide - **[Remote State](/stacks/remote-state)** - Data sharing patterns ## Get Involved We're building Atmos in the open and welcome your feedback: - 💬 **Discuss** - Share thoughts in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions). - 🐛 **Report Issues** - Found a bug? [Open an issue](https://github.com/cloudposse/atmos/issues). - 🚀 **Contribute** - Want to add features? Review our [contribution guide](https://atmos.tools/community/contributing). --- **Next up**: Google Cloud Storage (GCS) backend support for `!terraform.state`. Stay tuned! --- ## Azure Interactive Browser Authentication Atmos now supports the `azure/interactive` provider — the same interactive browser login `az login` uses (authorization code + PKCE on a localhost redirect). One command, [`atmos auth login`](/cli/commands/auth/login), opens your browser, signs you in, and sets up everything Terraform and the `az` CLI need. ## Why This Matters **Device code is getting blocked**: Microsoft-managed Conditional Access policies now block the device code flow in many tenants (error `AADSTS530035`), so `azure/device-code` fails there. The interactive browser flow carries full Conditional Access context, so it keeps working. **One command instead of two**: `azure/cli` requires a pre-existing `az login` session. With `azure/interactive`, `atmos auth login` is the only command you run. **Silent repeat logins**: Atmos tries silent acquisition from the persisted MSAL cache first. Refresh tokens make repeat logins silent — no browser after the first sign-in. **Drop-in for az login**: Atmos writes the Azure CLI-compatible cache files (MSAL token cache and `azureProfile.json`), so after `atmos auth login` the `az` CLI works without ever running `az login`. Guest/B2B users are handled correctly using the real MSAL home account ID. ## How to Use It ```yaml auth: providers: azure-browser: kind: azure/interactive spec: tenant_id: "12345678-1234-1234-1234-123456789012" subscription_id: "87654321-4321-4321-4321-210987654321" location: eastus identities: azure-dev: default: true kind: azure/subscription via: provider: azure-browser principal: subscription_id: "87654321-4321-4321-4321-210987654321" location: eastus ``` Then authenticate: ```shell atmos auth login ``` Atmos opens your default browser for sign-in (with MFA if configured), acquires Management, Graph, and Key Vault tokens, and caches them for azurerm, azuread, and azapi. Chain identities via `azure/subscription` exactly like the other Azure providers. The spec shape is identical to `azure/device-code`: `tenant_id` (required), plus optional `subscription_id`, `location`, `client_id` (defaults to the Azure CLI public client), and `cloud_environment` (`public`, `usgovernment`, or `china`). The flow requires an interactive terminal and a browser. For headless environments and CI/CD, keep using `azure/oidc`; where a browser can't be opened (e.g., SSH sessions), `azure/device-code` remains available. ## Get Involved - Read the [provider documentation](/cli/configuration/auth/providers) and the [Azure Authentication tutorial](/tutorials/azure-authentication) - Report issues on [GitHub](https://github.com/cloudposse/atmos/issues) --- ## Azure OIDC/Workload Identity Federation Provider Atmos now supports Azure OIDC/Workload Identity Federation for secure, secretless authentication in CI/CD pipelines. ## What Changed We've added the `azure/oidc` provider to the Atmos auth system, enabling Azure Workload Identity Federation for CI/CD environments like GitHub Actions and Azure DevOps. This completes the Azure authentication story alongside the existing `azure/cli` and `azure/device-code` providers. ## Why This Matters **Secretless Authentication**: No more storing Azure client secrets in CI/CD pipelines. The OIDC provider exchanges short-lived federated tokens for Azure credentials, following zero-trust security principles. **GitHub Actions Integration**: The provider automatically detects GitHub Actions environment and retrieves OIDC tokens using the `id-token: write` permission. **Terraform Compatibility**: Sets `ARM_USE_OIDC=true` for seamless integration with azurerm, azapi, and azuread Terraform providers. ## How to Use It ### Configuration ```yaml auth: providers: azure-oidc: kind: azure/oidc spec: tenant_id: "your-tenant-id" client_id: "your-client-id" subscription_id: "your-subscription-id" # Optional: custom audience audience: "api://AzureADTokenExchange" # Optional: path to federated token file token_file_path: "/path/to/token" ``` ### GitHub Actions Workflow ```yaml jobs: deploy: permissions: id-token: write # Required for OIDC contents: read steps: - uses: actions/checkout@v6 - name: Deploy with Atmos run: atmos terraform apply vpc -s prod ``` For usage and configuration, see [Azure Authentication](/tutorials/azure-authentication). ## Get Involved - Read the [Auth documentation](/stacks/auth) - Report issues on [GitHub](https://github.com/cloudposse/atmos/issues) --- ## Vendor Update Pull Requests, Now on Azure DevOps Automating the last mile of a dependency bump — branch, commit, push, open a pull request — only pays off if it works wherever the repository actually lives. A team standardized on Azure DevOps Repos instead of GitHub gets none of that: every vendor sync still ends in a manual PR, or a one-off script bolted on just to close the gap. ## The Problem The Component Updater (`atmos vendor update --pull-request`) already discovers component updates and opens or refreshes a pull request for them, but it only spoke to GitHub. Projects hosted in Azure DevOps Repos couldn't use the flag at all — every automated sync there still ended with someone opening the PR by hand. ## The Fix The `ci.pull_request.provider` field now accepts `azuredevops` alongside the existing `github` value. It follows the same create-or-update contract: Atmos looks for an already-open pull request between the same source and target branches first, updates that pull request's title and description in place when one exists, and only opens a new one when none does — so re-running a vendor sync never piles up duplicate PRs, regardless of which provider is configured. ## How to Use It Azure DevOps addresses a repository with three parts — organization, project, and repository — rather than GitHub's owner/repository pair, so those are explicit config fields instead of being derived from the Git remote: ```yaml vendor: ci: pull_request: provider: azuredevops organization: my-org project: my-project repository: my-repo branch_prefix: atmos/component-updater title: "chore(components): update {{ .scope.name }}" labels: [component-update] reviewers: [alice, bob] ``` Authenticate with a personal access token in `AZURE_DEVOPS_EXT_PAT`, granted Code (Read & Write) on the target project, then run the update exactly as before: ```shell atmos vendor update --pull-request ``` Labels and reviewers apply the same way they do on GitHub. Assignees don't — Azure DevOps pull requests have no assignee concept, so setting `ci.pull_request.assignees` with `provider: azuredevops` fails loudly instead of silently dropping the configuration; use `reviewers` there instead. ## Get Involved Questions about the Azure DevOps provider, or requests for another forge, are welcome in the [Atmos GitHub repository](https://github.com/cloudposse/atmos) and the community Slack. --- ## Background Container Services in Atmos Workflows Atmos workflows can now start long-running container services in the background, wait for them to become healthy, and tear them down automatically. Bring up an emulator, database, or registry with `background: true`, gate the next step on its container health check, and stop it with a `cancel` step — no shell scripts, no background jobs, no orphaned containers. ## The Problem Plenty of workflows need a service running _alongside_ the steps, not as a step. End-to-end tests need a cloud emulator. Integration steps need a database or a local registry. Until now, standing up that dependency meant leaving the workflow and dropping into shell: ```shell docker run -d --name emulator -p 4566:4566 localstack/localstack until curl -sf http://localhost:4566/_localstack/health; do sleep 2; done # ... run the real steps ... docker rm -f emulator ``` That works until it doesn't: - Readiness is a hand-rolled `until curl … sleep` loop that's different in every workflow. - A failed or interrupted run leaks the container — the cleanup line never runs. - The dependency is invisible in the workflow file; it lives in shell glue. - Local workflows and CI drift apart because each reinvents the same bring-up dance. A workflow runner should be able to say "start this service, wait until it's healthy, run my steps, then clean it up" as part of the workflow itself. ## What's New A [`container`](/workflows/steps/type/container#background-services) step with `background: true` starts a long-running service detached and lets the workflow continue. Three pieces work together: - **`background: true`** on an `action: run` step starts the service detached. - **`healthcheck`** (under `with:`) gates readiness — Atmos blocks until the container is healthy before the next step runs. - **[`cancel`](/workflows/steps/type/cancel)** stops and removes the service; if you never cancel it, Atmos tears it down automatically when the workflow ends. There are also two readiness-gating control steps: [`wait`](/workflows/steps/type/wait) blocks on named services, and `wait-all` blocks on every background service started so far. ## A Background Emulator Start an emulator, run Terraform against it, then tear it down — all in one declarative workflow: ```yaml title="stacks/workflows/e2e.yaml" workflows: e2e: steps: - name: emulator type: container action: run background: true with: image: localstack/localstack ports: - host: 4566 container: 4566 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"] interval: 5s retries: 10 start_period: 30s - name: apply type: atmos command: terraform apply vpc -s dev - type: cancel for: emulator ``` Atmos starts the emulator, blocks until its health check reports healthy, runs `apply`, then `cancel` stops and removes the container. Here it is running end to end: [View the full example](/examples/background-steps) ## Readiness Reuses the Health Check The key idea is that readiness is not a new concept — it's the container's own `healthcheck`, the same shape you already use for [container components](/changelog/container-components). When a background service declares a health check, Atmos blocks until it's healthy before continuing. No `sleep` guesses, no polling scripts. For a service, "wait" means _until healthy_, never _until exit_. A long-running service never exits on its own, so its health check defines readiness. You can gate readiness implicitly (the step after a healthchecked background service waits automatically) or explicitly with a `wait` step: ```yaml - type: wait for: [emulator] ``` Start several services and wait for all of them at once with `wait-all`: ```yaml - type: wait-all ``` The ordinary [`needs`](/workflows/steps) field is unchanged — it expresses step ordering, while `wait`/`wait-all` express service readiness. ## Teardown You Don't Have to Remember The most common shell-script bug is the cleanup line that never runs. Background services fix that by default: if you never `cancel` a service explicitly, Atmos tears down all background services when the workflow ends — **including on failure**. A crashed or short-circuited workflow does not leave orphaned containers behind. Use `cancel` when you want to free a service early, before the rest of the workflow finishes, or simply to make teardown explicit in the file: ```yaml - type: cancel for: emulator ``` ## Why This Matters Background services turn "stand up a dependency for these steps" into a first-class, declarative part of the workflow: - Readiness is the container health check, not a bespoke polling loop. - Teardown is automatic, so failed runs don't leak containers. - The dependency is visible in the workflow file, not hidden in shell glue. - The same workflow runs locally, in CI, or inside a larger runbook. This is especially useful for end-to-end tests against emulators, integration steps that need a database or registry, and any workflow where a service has to be up _while_ the real work runs. Atmos's built-in emulator drivers cover AWS (`floci/aws`, `ministack/aws`, `localstack/aws`), GCP (`floci/gcp`), Azure (`floci/az`), Kubernetes (`k3s`), Vault/OpenBao (`openbao`, `vault`), OCI/Terraform registries (`registry`), and a Mockoon-backed 1Password Connect server (`mockoon/1password-connect`). See the [emulator component reference](/stacks/components/emulator#supported-drivers--targets) for the current driver and target list. For usage and configuration, see [Using Containers](/components/container). ## Get Involved For the full reference, see [background services](/workflows/steps/type/container#background-services) on the `container` step page, and the [`wait`](/workflows/steps/type/wait) and [`cancel`](/workflows/steps/type/cancel) step types. Try it on a real end-to-end workflow and tell us where the readiness and teardown semantics feel right — or where you want more control. --- ## Breaking Change: Empty base_path No Longer Defaults to Current Directory Starting with Atmos v1.202.0, empty or omitted `base_path` values in `atmos.yaml` now trigger git root discovery instead of defaulting to the current directory. Users with multiple Atmos projects in a single repository, or where the Atmos project root differs from the git root, must explicitly set `base_path: "."`. ## What Changed The interpretation of `base_path` in `atmos.yaml` has changed: **Before (v1.201.x and earlier):** ```yaml # Both treated as "current directory" base_path: "" base_path: "." ``` **After (v1.202.0+):** ```yaml # Triggers git root discovery with fallback base_path: "" # Explicitly uses config file directory relative to the location of the `atmos.yaml` base_path: "." ``` These are no longer equivalent. An empty `base_path` now means "find the git repository root and use that", while `"."` explicitly means "use the directory where `atmos.yaml` is located". ## Who Is Affected You may be affected if: 1. **Multiple Atmos projects in one repository** - Each project has its own `atmos.yaml` in a subdirectory 2. **Atmos project root differs from git root** - Your `atmos.yaml` is not at the repository root 3. **Using refarch-scaffold or similar patterns** - Where `atmos.yaml` is in a nested directory ### Symptoms If affected, you'll see errors like: ```text The atmos.yaml CLI config file specifies the directory for Atmos stacks as stacks, but the directory does not exist. ``` Or: ```text atmos exited with code 1 ``` ## How to Fix ### Option 1: Explicitly Set base\_path (Recommended) Update your `atmos.yaml` to explicitly specify the base path: ```yaml # Before (no longer works as expected) base_path: "" # After (explicit current directory) base_path: "." ``` ### Option 2: Use Relative Paths If your stacks and components are relative to the config file: ```yaml base_path: "." stacks: base_path: "stacks" components: terraform: base_path: "components/terraform" ``` ### Option 3: Navigate to Project Root If you prefer empty `base_path`, ensure you run Atmos from the git repository root where `stacks/` and `components/` directories exist. ## Path Resolution Semantics For reference, here's how different `base_path` values are now interpreted: | `base_path` value | Resolves to | Use case | |-------------------|-------------|----------| | `""` (empty/unset) | Git repo root, fallback to config dir | Default - single project at repo root | | `"."` | Directory containing `atmos.yaml` | Explicit config-relative paths | | `".."` | Parent of config directory | Config in subdirectory | | `"./foo"` | config-dir/foo | Explicit relative path | | `"foo"` | git-root/foo with fallback | Simple relative path | | `"/absolute/path"` | As specified | Absolute path override | ## Why This Change? This change was made to support running `atmos` commands from anywhere within a repository, similar to how `git` commands work. The git root discovery enables: - Running [`atmos terraform plan vpc -s dev`](/cli/commands/terraform/plan) from any subdirectory - Consistent behavior regardless of current working directory - Better alignment with developer workflows For users with non-standard project layouts, the explicit `base_path: "."` provides the previous behavior. ## References - [PR #1872: Correct base path resolution semantics](https://github.com/cloudposse/atmos/pull/1872) - [PR #1868: Fix base path resolution and fallback order](https://github.com/cloudposse/atmos/pull/1868) - [Issue #1858: Path resolution regression](https://github.com/cloudposse/atmos/issues/1858) - [CLI Configuration Documentation](https://atmos.tools/cli/configuration) For usage and configuration, see [CLI Configuration](/cli/configuration). --- ## Browser-Based Authentication for AWS IAM Users Atmos now supports browser-based OAuth2 authentication as an automatic fallback for `aws/user` identities. When no static credentials or keychain entries are available, Atmos opens your browser for interactive sign-in using the same AWS console flow you already know. ## What Changed The `aws/user` identity type gains a new third-tier authentication fallback. When YAML credentials and keychain credentials are both unavailable, Atmos automatically initiates an OAuth2 PKCE flow via the AWS sign-in service. This provides the same convenient web-based authentication that SSO users already enjoy, without requiring static access keys. The flow supports both interactive terminals (browser opens automatically with a spinner) and non-interactive environments (displays a URL for manual authentication). ## How It Works No configuration is required. Browser authentication is enabled by default for all `aws/user` identities. When triggered, Atmos: 1. Starts a local callback server on an ephemeral port 2. Opens your browser to the AWS sign-in authorization endpoint 3. Exchanges the authorization code for temporary credentials using PKCE 4. Caches a refresh token for 12-hour session reuse Subsequent authentications within the 12-hour window reuse the cached refresh token, avoiding repeated browser prompts. Credentials refresh automatically every 15 minutes. ```yaml # No changes needed - browser auth is enabled by default identities: my-user: kind: aws/user # credentials: # webflow_enabled: false # Set to false to disable browser auth ``` ## Why This Matters Many teams are moving away from static IAM access keys for security reasons. Browser-based authentication eliminates the need to generate, store, and rotate long-lived credentials. Users authenticate with their existing AWS console credentials, and Atmos handles the rest. For usage and configuration, see [atmos auth user configure](/cli/commands/auth/user/configure). ## Get Involved Have feedback on the browser authentication flow? Open an issue on [GitHub](https://github.com/cloudposse/atmos). --- ## Record and Render Terminal Sessions with Atmos Cast Atmos can now record terminal sessions as asciicast files and render them into shareable formats for documentation, demos, and CI artifacts. ```bash atmos cast render demo.cast --output=demo.gif atmos cast render demo.cast --output=demo.html atmos cast render demo.cast --output=demo.out --format=html ``` The `render` command turns a recording into a rendered artifact — here it's converted straight to a GIF: Replaying a recording in the terminal with `play` reproduces it exactly as it was captured: And any command can capture its own session as it runs with `--cast`, no separate recording step required: ## What Changed The [`atmos cast`](/cli/commands/cast/usage) command now has a render interface built around `--output` and `--format`. Atmos infers the format from common output extensions such as `.gif`, `.mp4`, `.html`, `.ascii`, `.png`, `.jpg`, and `.jpeg`, while `--format` handles custom output filenames. The same recording and rendering engine is also available to workflow and custom command steps. That means a runbook can capture the exact terminal session it executes, then turn the recording into an artifact without a separate wrapper script. ## Workflow-Friendly Captures Cast support is designed for repeatable automation: - Terminal output is recorded through the shared Atmos execution paths. - Final terminal state can be rendered as static text, HTML, or images. - Animated outputs are available for demos and walkthroughs. - Render options are explicit, so CI and local commands use the same interface. This is useful when a workflow needs to publish a proof-of-run artifact, attach a terminal demo to documentation, or keep a reproducible recording of an interactive command. ## Why It Matters Teams often rely on external tools and custom shell scripts to capture terminal demos. Bringing cast recording and rendering into Atmos makes those captures part of the same command and workflow system that runs the work. The result is easier to automate, easier to review, and easier to reproduce. --- ## Gate custom command steps on their own flags and arguments A custom command often needs to behave differently depending on how it was called — skip the destructive step on `--dry-run`, only run a cleanup step for a particular `environment` argument, or branch on whichever component the caller targeted. Doing that meant wrapping the command in a shell script that inspected `$@` itself, because a step's `when:` condition had no visibility into the command's own `--flag` or positional argument values. ## The Problem Custom command steps already supported `when:` conditions built on CEL — `ci`, `stack`, `component`, and more — but a step could never see the values the user actually passed to the command that's running it: ```yaml commands: - name: deploy flags: - name: dry-run type: bool steps: - type: shell command: terraform apply # No way to reference --dry-run here. ``` The flag values were already being extracted for Go/gomplate templates as `{{ .Flags.dry_run }}`, but that data never reached the CEL evaluator, so `when:` conditions couldn't use it. ## The Fix `when:` expressions can now read `flags` and `arguments`, mirroring the same data already available to steps in templates. `component` is also resolved for custom commands now, the same way it already was for component hooks — via a semantic-typed flag or argument. ## How to Use It ```yaml commands: - name: deploy flags: - name: dry-run type: bool steps: - type: shell command: terraform apply when: !cel '!flags["dry-run"]' ``` See the [custom command steps](/cli/configuration/commands/steps#conditional-steps) docs for the full list of available `when:` facts. ## Get Involved Have feedback on this feature? [Open an issue](https://github.com/cloudposse/atmos/issues) or join the conversation in the [Cloud Posse community Slack](https://cloudposse.com/slack). --- ## Config Isolation with --chdir Flag The [`--chdir`](/cli/global-flags#directory-change-examples) flag now correctly isolates configuration loading when changing to a directory with its own Atmos configuration. ## What Changed When using `atmos --chdir path/to/project`, Atmos now correctly uses only the configuration from the target directory. Previously, configuration from parent directories and the git repository root would be merged with the target directory's config, leading to unexpected behavior. The fix ensures that when you change to a directory with its own `atmos.yaml`, Atmos behaves exactly as if you had run the command directly from that directory - searching parent directories and git root are fallback mechanisms that only apply when no local config exists. ## Why This Matters This fix is particularly important for: - **Monorepos** with multiple independent Atmos projects in subdirectories - **Testing scenarios** where isolated configuration is essential - **CI/CD pipelines** that use `--chdir` to target specific project directories Without this fix, running `atmos --chdir projects/team-a terraform plan vpc -s prod` might unexpectedly pick up configuration from the repository root, causing the wrong component paths or stack settings to be used. ## Technical Details The config loading order in Atmos is: 1. Embedded defaults 2. System directory (`/usr/local/etc/atmos/atmos.yaml`) 3. Home directory (`~/.atmos/atmos.yaml`) 4. Parent directory search (**now skipped if local config exists**) 5. Git repository root (**now skipped if local config exists**) 6. Current working directory (`./atmos.yaml`) 7. Environment variable (`ATMOS_CLI_CONFIG_PATH`) 8. CLI argument ([`--config-path`](/cli/global-flags#core-global-flags)) The key change is that steps 4 and 5 are now properly treated as fallback mechanisms - they only run when the current working directory does NOT have any Atmos configuration indicator (`atmos.yaml`, `.atmos.yaml`, `.atmos/`, `.atmos.d/`, or `atmos.d/`). ## How to Use It No changes to your workflow are required. The `--chdir` flag now works as expected: ```bash # Changes to examples/demo-stacks and uses ONLY its config atmos --chdir examples/demo-stacks describe config # Short form works the same way atmos -C examples/demo-stacks describe config ``` ## Restoring Previous Behavior If you relied on the previous behavior where parent/repo-root configurations were merged, you can explicitly import the parent configuration in your local `atmos.yaml`: ```yaml import: - path: "../../atmos.yaml" ``` This gives you explicit control over which configurations are inherited, rather than relying on implicit directory traversal. ## Get Involved Have questions or feedback? Join us on [Slack](https://cloudposse.com/slack) or open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Chunked Uploads for Large Stack Payloads Atmos now automatically chunks large payloads when uploading affected stacks and instances to Atmos Pro, eliminating HTTP 413 errors for large infrastructure repositories. ## What Changed When running [`atmos describe affected --upload`](/cli/commands/describe/affected) or [`atmos list instances --upload`](/cli/commands/list/list-instances), the CLI now checks the serialized payload size before sending. If the payload exceeds the configurable threshold (default 4MB), Atmos splits the data into multiple smaller requests, each tagged with batch metadata (`batch_id`, `batch_index`, `batch_total`) for server-side reassembly. ### Key Improvements - **Automatic chunking** - Payloads are split transparently when they exceed the size threshold - **Compact JSON** - Upload payloads now use compact JSON serialization, reducing size by ~30% - **Configurable threshold** - The `max_payload_bytes` setting in `atmos.yaml` lets you tune the chunk size - **Backward compatible** - Small payloads send exactly as before; old CLI versions continue to work with updated servers ## Why This Matters Organizations with large infrastructure footprints (hundreds of stacks and components) were hitting Vercel's serverless function body size limit (~4.5MB) when uploading stack data to Atmos Pro. The existing `StripAffectedForUpload` optimization reduces payloads by 70-75%, but that was not enough for the largest repositories. With chunked uploads, there is no practical upper limit on the number of stacks or instances that can be uploaded. ## How to Use It Chunked uploads work automatically with no configuration required. To customize the chunk size threshold, add `max_payload_bytes` to the `pro` section of your `atmos.yaml`: ```yaml settings: pro: max_payload_bytes: 4194304 # 4MB (default) ``` Set a lower value if you're behind a reverse proxy with a smaller body size limit, or a higher value if your server supports larger payloads. ## Get Involved - Report issues at [github.com/cloudposse/atmos/issues](https://github.com/cloudposse/atmos/issues) - Join the discussion in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) --- ## Faster CI: Atmos Caches Your Toolchain, Providers, and Modules Atmos now plugs into your CI provider's native cache so your infrastructure pipelines run faster — automatically. Flip one setting and Atmos caches the toolchain it installs, the OpenTofu and Terraform providers your stacks download, the modules and vendored components they pull, remote stack imports, and everything else it would otherwise re-fetch from the internet on every run. Cold CI jobs go warm: the same pipeline stops waiting on the same downloads, and stops going red when an upstream registry has a bad minute. ## Why This Matters Every CI job that starts cold re-downloads the same tools, providers, and modules from the same upstream sources — the [OpenTofu Registry](https://registry.opentofu.org) (`registry.opentofu.org`), OpenTofu releases, GitHub releases and the GitHub API, OCI registries, and more. That cold start is invisible until it isn't: a registry has a bad minute, GitHub rate-limits your token, a download stalls, and a pipeline that "just works" locally goes red in CI. Warming the cache changes that for you directly: - **Your jobs run faster.** Install the toolchain, download providers, and pull modules once — then reuse all of it across jobs and runs. The slow part of most Atmos runs is fetching providers and modules you already have; a warm cache skips it. - **Provider and module caches come along for free.** Atmos already enables a [shared provider plugin cache](/cli/configuration/components/terraform) by default (`TF_PLUGIN_CACHE_DIR` under `~/.cache/atmos`), and vendored components and remote modules land under the same root. Because `ci.cache` archives that root, your providers and modules are cached with zero extra wiring. - **Fewer flaky failures.** The majority of transient CI errors are upstream network blips. A warm cache simply doesn't make those requests, so there's nothing to flake. - **Reproducible, resilient builds.** A preserved cache is a durable, self-contained copy of every provider and module your stacks depend on. If an upstream version is yanked, a module repository disappears, or a registry has an outage, your pipelines keep building from the cache instead of failing — the same inputs produce the same result, whether or not the internet cooperates that day. Preserve the cache and you can keep shipping even when upstream artifacts go away entirely. - **Less traffic to external sites.** You stop hammering the OpenTofu Registry, GitHub, and registry mirrors on every run — gentler on the services you depend on and on any egress/bandwidth budgets you pay for. - **Fewer hits to the GitHub API — and less rate-limit risk.** When you fan out across many concurrent jobs, repeated upstream and GitHub API calls are exactly what trips rate limits. Caching collapses thousands of redundant requests into a handful, so you stop burning quota on downloads. - **You reuse immutable, already-verified artifacts.** Cache entries are write-once, and the key is derived from `toolchain.lock.yaml` plus the runner's OS/arch — so a cache hit gives you back the exact bytes you pulled and verified before, instead of re-pulling from the internet on every run. That's a smaller supply-chain attack surface, not just a speedup. None of this requires you to think about cache keys, paths, or eviction. You flip two settings; Atmos handles the rest. And at fleet scale — hundreds to thousands of OpenTofu and Terraform jobs per hour — these add up from "nice" to "the difference between a pipeline that scales and one that fights the platforms it runs on." ## What Changed Atmos installs a toolchain (via [`atmos toolchain`](/cli/commands/toolchain/usage)) and downloads other regenerable artifacts — vendored components, remote stack-import clones, provider and plugin caches — into a well-known cache root (`~/.cache/atmos`, honoring `XDG_CACHE_HOME`). In CI, every job re-fetches all of it from scratch. The new `ci.cache` configuration lets Atmos compute _what_ to cache — a stable key derived from `toolchain.lock.yaml` plus the runner's OS/arch, the cache root, and restore-key fallbacks — so a native cache can persist it across jobs and runs. Define it once: ```yaml title="atmos.yaml" ci: cache: enabled: true key: "atmos-toolchain-{{.OS}}-{{.Arch}}-v1" restore_keys: - "atmos-toolchain-{{.OS}}-{{.Arch}}-" ``` ## One Step in GitHub Actions The recommended way to use it is the Atmos-provided composite action — Atmos supplies the key and paths, native `actions/cache` does the storage, and it exposes **no runtime token** to your job: ```yaml - uses: cloudposse/atmos/actions/cache@v1 - run: atmos toolchain install --default helm/helm@v3.16.0 ``` Prefer to wire it yourself? [`atmos ci cache paths --format=github`](/cli/commands/ci/cache/paths) emits the `key`, `path`, and `restore-keys` as step outputs for a plain `actions/cache` step. ## Atmos-Managed Restore/Save Atmos can also _own_ the storage with its own backend against the GitHub Actions **Cache Service v2** (the same service `actions/cache` uses): ```bash atmos ci cache restore # restore the cache root from the cache service atmos ci cache save # archive the root and upload it under the key atmos ci cache list # list cache entries (optionally by key prefix) atmos ci cache delete # delete a cache entry by exact key ``` `restore` and `save` transfer cache content, so they — and the automatic `auto: both` lifecycle — need the runner's cache credentials (`ACTIONS_RUNTIME_TOKEN` / `ACTIONS_RESULTS_URL`), which GitHub withholds from `run:` steps. The Atmos [`github-runtime`](https://github.com/cloudposse/atmos/tree/main/actions/github-runtime) action exposes them (as scoped step outputs, or optionally as ambient env). Saves are write-once and idempotent — a cache hit on restore skips the save instead of re-uploading unchanged content. `list` and `delete`, by contrast, administer the cache over the public caches API, so they run **from your workstation** too — no runner required, just a GitHub token (`GITHUB_TOKEN` / `ATMOS_GITHUB_TOKEN`, or `gh auth login`). Use them to inspect or prune cache entries locally. **Which should you use?** The `actions/cache` path exposes no token and is the most secure; the Atmos-managed path hands you the cache-service credentials when you want Atmos in control. See the [four documented options and security ranking](/cli/configuration/ci/cache#github-actions-integration). ## How to Use It - **Recommended:** add `cloudposse/atmos/actions/cache@v1` (one step) — no runtime token, native `actions/cache` storage. - **Explicit:** `atmos ci cache paths --format=github` → your own `actions/cache` step. - **Atmos-managed:** `cloudposse/atmos/actions/github-runtime@v1` + [`atmos ci cache restore`](/cli/commands/ci/cache/restore)/`save` (or `auto: both`). - The cache is **off by default** — opt in per repository with `ci.cache.enabled: true`. - The live backend is **GitHub Actions** today. Outside a cache-capable CI provider, the automatic cache lifecycle (`auto`) is a graceful no-op (with a debug log), so the same config is safe to run locally — but explicit commands like `save` and `restore` return a cache-unavailable error when not running in a supported CI runner. A generic backend for other providers is planned. --- ## Toggle CI PR Comments with an Environment Variable You can now enable or disable CI PR comments per-pipeline using the `ATMOS_CI_COMMENTS_ENABLED` environment variable — no config file changes needed. ## What Changed A new `ATMOS_CI_COMMENTS_ENABLED` environment variable overrides the `ci.comments.enabled` setting in `atmos.yaml`. When set, it takes precedence over the YAML configuration. ``` ATMOS_CI_COMMENTS_ENABLED=true # enable PR comments ATMOS_CI_COMMENTS_ENABLED=false # disable PR comments ``` The variable accepts any standard boolean value (`true`, `false`, `1`, `0`). Invalid values log a warning and leave the YAML setting unchanged. ## Why This Matters Some CI workflows need different PR comment behavior depending on the context. For example: - **Scheduled drift-detection runs** should suppress PR comments (there's no PR to comment on) - **PR preview pipelines** should post plan summaries as PR comments - **Staging deploys** triggered by merge should skip comments Previously, you had to maintain separate `atmos.yaml` files or use conditional YAML templating to toggle comments. Now it's a single environment variable. ## How to Use It Set the variable in your CI workflow: ```yaml # Post plan summaries as PR comments - name: atmos terraform plan env: ATMOS_CI_COMMENTS_ENABLED: "true" run: | atmos terraform plan "$COMPONENT" --stack "$STACK" # Suppress comments for scheduled runs - name: atmos terraform plan (drift detection) env: ATMOS_CI_COMMENTS_ENABLED: "false" run: | atmos terraform plan "$COMPONENT" --stack "$STACK" ``` The environment variable follows the same precedence as other `ATMOS_*` variables: CLI flags > environment variables > config files > defaults. For usage and configuration, see [CI Pull Request Comments](/cli/configuration/ci/comments). ## Get Involved Found an issue or have a feature request? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Use Multiple GitHub Tokens in CI Workflows When you're using a GitHub App token to manage GitHub resources with Terraform, you probably still want CI commit statuses to use the workflow's own token. Now you can — just set `ATMOS_CI_GITHUB_TOKEN`. ## What Changed A new `ATMOS_CI_GITHUB_TOKEN` environment variable lets you use a separate token for Atmos CI operations (commit statuses, artifacts) while keeping `GITHUB_TOKEN` for Terraform. Token precedence: ``` ATMOS_CI_GITHUB_TOKEN > GITHUB_TOKEN > GH_TOKEN ``` Atmos also now gives you actionable hints when the GitHub Status API returns a 404 or 403, instead of a cryptic error with no guidance. ## Why This Matters If you're Terraforming GitHub repositories, you probably mint a GitHub App token with repo management permissions and set it as `GITHUB_TOKEN`. That works great for Terraform — but that App token likely doesn't have `statuses: write` permission, so CI commit status updates fail with a mysterious 404. You might think adding `permissions: statuses: write` to your workflow fixes it, but that only applies to the **default** workflow token — not your GitHub App token. Until now, there was no way to use both tokens concurrently: one for Terraform, one for CI. ## How to Use It Set `ATMOS_CI_GITHUB_TOKEN` to the workflow's default token, and let `GITHUB_TOKEN` stay as your App token for Terraform: ```yaml - name: atmos terraform plan env: ATMOS_CI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ steps.github-app-token.outputs.token }} run: | atmos terraform plan "$COMPONENT" --stack "$STACK" ``` Terraform uses `GITHUB_TOKEN` (the App token with repo permissions), and Atmos CI uses `ATMOS_CI_GITHUB_TOKEN` (the workflow token with `statuses: write`). Both work concurrently without conflict. For usage and configuration, see [CI Configuration](/cli/configuration/ci). --- ## Collapsible CI Log Groups for Workflows and Custom Commands A workflow fails in CI. You open the run and you're staring at two thousand lines of undifferentiated output — `terraform init`, `plan`, and `apply` from every step mashed into one endless scroll, with no marker for where one step ends and the next begins. Finding the step that actually broke means scrolling and squinting. Atmos now folds each workflow and custom-command step into its own collapsible, named log group. You see the list of steps up front and expand only the one you care about. ## The Problem Workflows and custom commands run a sequence of steps, and in CI all of that output landed in one flat log. The CI provider had no idea where one step stopped and the next started, so it couldn't help you navigate. The usual fix is to hand-write `echo "::group::..."` around each `run:` step — but Atmos steps aren't `run:` steps you control line by line, so there was no clean place to put those markers. You scrolled. ## The Fix Atmos now folds its output into the active CI provider's log groups. On GitHub Actions that's the `::group::` / `::endgroup::` workflow commands, so the run log renders as a list of expandable sections instead of a wall of text. A single `ci.groups.mode` picks the granularity — and because CI providers can't nest groups, the mode is the one knob you need: - **`auto`** (default) — the finest grouping per command. A **workflow** renders one group per step; a direct **[`atmos terraform plan`](/cli/commands/terraform/plan)** renders one group per phase (`init`, then `plan`): ```text ▸ deploy network ▸ terraform init ▾ deploy cluster ▾ terraform plan atmos terraform apply… ... the plan you actually want ... ▸ smoke test ``` - **`invocation`** — one group around each whole `atmos ` run, including bare commands you invoke directly from CI YAML. - **`off`** — no grouping. A step that runs [`atmos terraform apply`](/cli/commands/terraform/apply) is one group, not three — the outermost group always wins. You write nothing extra: no markers in your workflows, custom commands, or CI YAML. ## How to Use It It rides on the existing [`ci`](/cli/configuration/ci) configuration. Turn on CI integration and grouping defaults to `auto`: ```yaml ci: enabled: true # master switch groups: mode: auto # auto | invocation | off (default: auto) ``` Grouping is emitted only when **both** `ci.enabled` is `true` **and** a grouping-capable CI provider is detected (GitHub Actions). Outside CI it's a complete no-op, so local runs look exactly as before. You can also set it per-run with `ATMOS_CI_GROUPS_MODE`. A couple of details keep it unobtrusive: - **No nested groups.** When a command invokes `atmos` again (a custom command calling another, a nested workflow, or a workflow step running `atmos terraform apply`), the child detects the open group and stays flat — you always get one level of grouping. - **Secret-safe labels.** Group labels go through the same masking layer as the rest of Atmos output, so a secret resolved into a step command or flag can't leak into the label. See the [`ci.groups`](/cli/configuration/ci/groups) reference for the full configuration. ## Get Involved Atmos is open source and we'd love your feedback. Join the conversation in the [Cloud Posse community](https://cloudposse.com/slack) or open an issue on [GitHub](https://github.com/cloudposse/atmos). --- ## See CI and Atmos Pro Status at a Glance GitHub Actions is the most popular way teams run Atmos in CI, and a lot of those pipelines are still calling into the original `cloudposse/github-action-atmos-*` actions. Those actions were the recommendation for years. They're no longer where investment goes — [Native CI](/ci) replaced them with a more capable, end-to-end-tested integration. Plenty of teams don't know it exists, let alone that the actions they're on are effectively on life support. Even teams already on Native CI run into a quieter version of the same problem: a setting that doesn't take effect looks exactly like one that does. Maybe `ci.enabled` is set on one profile but not another, or an Atmos Pro workspace never got configured in the first place — nothing errors either way. The pipeline finishes green, and the first symptom is a status check or an upload that isn't there, discovered days later. ## The Problem Confirming any of it meant checking for side effects after the fact: did the expected status check show up, did the drift upload happen, is this pipeline still wired to a marketplace action. None of that was visible from the CI run itself, so a misconfigured pipeline was only ever discovered because something else broke first. ## The Fix Atmos now prints a short status banner the moment it detects it's running inside a CI provider: the version, whether Native CI mode is on or off, and whether Atmos Pro is configured. If it's invoked from one of the older marketplace actions, it also warns directly in the run log and points at migrating to Native CI — which has more features, gets tested end-to-end as part of the Atmos core test suite, and is where all future investment is going. Teams still on the legacy actions should treat this as a prompt to move now, not eventually. ```text ▶ Atmos version 1.228.0 linux/amd64 ✓ Atmos CI is enabled; learn more at https://atmos.tools/ci ✗ Atmos Pro is disabled; learn more at https://atmos.tools/pro ``` ```text ⚠ Detected legacy action cloudposse/github-action-atmos-terraform-plan; migrate to Native CI for better performance — learn more at https://atmos.tools/ci ``` ## How to Use It There's nothing to configure — the banner appears automatically at the start of every `atmos` command you run inside a detected CI provider, and it's a complete no-op on a local machine. It reads the same [`ci`](/cli/configuration/ci) and [`settings.pro`](/cli/configuration/settings/pro) configuration you already have, so what it reports always matches your actual setup. ## Get Involved Atmos is open source and we'd love your feedback. Join the conversation in the [Cloud Posse community](https://cloudposse.com/slack) or open an issue on [GitHub](https://github.com/cloudposse/atmos). --- ## Validate GitHub Actions Workflows with Atmos A workflow can be valid YAML and still fail only after GitHub Actions tries to run it: a misspelled trigger filter, an invalid expression, or an action reference that does not make sense in context. Those failures are slow to discover and usually arrive after a push. Atmos now includes GitHub Actions workflow validation as an experimental native-CI command. Run it from your workstation or in the workflow that it checks: ```shell atmos ci validate # Equivalent validation-oriented alias atmos validate ci ``` ## Validate Before the Push With no arguments, atmos ci validate recursively checks every .yml and .yaml file in the current working directory's .github/workflows. It uses the built-in [actionlint](https://github.com/rhysd/actionlint) integration, so the linting capability ships with Atmos rather than requiring a separately installed binary. A successful run gives a compact confirmation: ```text ✓ Validated 3 GitHub Actions workflow file(s) in .github/workflows. ``` Findings include the workflow path, line, column, and actionlint rule. They fail the command, which makes validation suitable for both a pre-push check and a CI gate. ## Check a Fixture or Another Directory Sometimes the workflows you want to validate do not live in the current directory's default location — for example, when testing generated workflows or fixtures. Use \--workflow-path to point at that directory. Atmos recursively finds workflow YAML beneath it: ```shell atmos ci validate --workflow-path tests/fixtures/scenarios/invalid-github-actions-workflows/.github/workflows ``` That repository fixture intentionally uses branch instead of branches in a push trigger. The command reports the line and exits with status 1, making it a quick end-to-end smoke test of the feature. You can also select specific files directly: ```shell atmos ci validate .github/workflows/plan.yml .github/workflows/apply.yml ``` \--workflow-path and explicit workflow-file arguments are intentionally separate selectors and cannot be combined. This keeps the validation scope unambiguous. ## Put It in GitHub Actions Add a validation step after checkout: ```yaml title=".github/workflows/validate.yml" name: Validate workflows on: pull_request: jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: cloudposse/github-action-setup-atmos@v2 - run: atmos ci validate ``` When native CI is enabled, Atmos turns findings into line-anchored GitHub Actions annotations: ```yaml title="atmos.yaml" ci: enabled: true ``` Annotations are enabled by default once ci.enabled is true; set ci.annotations.enabled: false to suppress them. Outside GitHub Actions, the same command has no provider side effects and simply renders its diagnostics. ## SARIF Is Explicit For a separate Code Scanning upload or an artifact, request SARIF explicitly: ```shell atmos ci validate --format=sarif > actionlint.sarif ``` Atmos does not upload SARIF automatically. That avoids duplicate pull-request feedback and keeps security-events: write unnecessary unless your workflow deliberately adds a SARIF upload step. The validator respects .github/actionlint.yaml and .github/actionlint.yml. Its optional ShellCheck and Pyflakes integrations are disabled for now, so the command stays deterministic and does not depend on tools installed on the runner. ## Learn More - [atmos ci validate](/cli/commands/ci/validate) — usage, flags, and output formats - [Native CI](/ci) — native CI behavior and configuration - [CI configuration](/cli/configuration/ci) — enable annotations and other CI features --- ## Component-Aware Stack Tab Completion Tab completion for the `--stack` flag is now context-aware, filtering suggestions based on the component you specify. ## What Changed When running Terraform commands like [`atmos terraform plan --stack `](/cli/commands/terraform/plan), the shell completion now intelligently filters stack suggestions to only show stacks that contain the specified component. **Before:** ```bash $ atmos terraform plan vpc --stack dev prod staging test-1 test-2 # All stacks shown ``` **After:** ```bash $ atmos terraform plan vpc --stack dev prod # Only stacks containing the vpc component ``` ## Why This Matters This enhancement improves the developer experience by: - **Scales with your infrastructure** - In large organizations with hundreds of stacks, you only see the handful that are relevant - **Reducing cognitive load** - No more scrolling through dozens or hundreds of irrelevant stacks - **Preventing errors** - Impossible to select an invalid stack/component combination - **Faster navigation** - Only see applicable stacks, not your entire infrastructure - **Better discoverability** - Instantly see which stacks include a particular component ## How It Works The completion system leverages Atmos's existing `list stacks --component ` functionality. When you provide a component argument, the `--stack` flag completion automatically filters the stack list based on which stacks define that component. If no component is specified, all stacks are shown (preserving the original behavior). ## Example Given this stack configuration: ```yaml # stacks/dev.yaml components: terraform: vpc: vars: {...} myapp: vars: {...} # stacks/prod.yaml components: terraform: vpc: vars: {...} ``` Tab completion will now show: - `atmos terraform plan vpc --stack ` → `dev` and `prod` - `atmos terraform plan myapp --stack ` → `dev` only This works across all Terraform commands: `plan`, `apply`, `deploy`, `destroy`, and more. ### Scale Matters In organizations with large infrastructures, this becomes invaluable: ```bash # Without filtering: overwhelming $ atmos terraform plan vpc --stack dev-us-east-1 dev-us-west-2 dev-eu-west-1 dev-ap-south-1 staging-us-east-1 staging-us-west-2 staging-eu-west-1 prod-us-east-1 prod-us-west-2 prod-eu-west-1 prod-ap-south-1 sandbox-alice sandbox-bob sandbox-charlie ... (100+ more stacks) # With filtering: manageable $ atmos terraform plan vpc --stack dev-us-east-1 staging-us-east-1 prod-us-east-1 # Only the 3 stacks that actually have the vpc component ``` --- ## Document Components with metadata.description Component `metadata` now supports an optional `description` field, so you can document what a component is for right next to its configuration. Atmos preserves `description` as component metadata — it does not change how the component is processed, planned, or applied. ## What Changed You can now add a human-readable `description` to any component's `metadata` section: ```yaml components: terraform: vpc-prod: metadata: component: vpc description: "Production VPC with public and private subnets" vars: environment: prod ``` The Atmos manifest JSON schema was updated to allow the new field, so editors with schema support get auto-completion and validation for `metadata.description`. ## Why This Matters Stacks often define many components that point at the same Terraform root module with different configurations. A short `description` makes it obvious at a glance what each one is for, without forcing readers to reverse-engineer intent from variables. It keeps documentation co-located with the configuration it describes. ## How to Use It Add `description` under any component's `metadata`: ```yaml components: terraform: vpc-isolated: metadata: component: vpc description: "Isolated VPC without an internet gateway" vars: vpc_cidr: "10.1.0.0/16" ``` The field is purely informational and additive — existing stacks are unaffected. See the [component metadata reference](/stacks/components/component-metadata) for details. For usage and configuration, see [description](/stacks/components/component-metadata#description). --- ## Smarter Component Selection in Interactive Prompts Interactive component selection now filters out non-deployable components. ## What Changed When using [`atmos terraform plan -s stack-name`](/cli/commands/terraform/plan) without specifying a component, the interactive "Choose a component" menu now correctly filters the component list: - **Abstract components** (`metadata.type: abstract`) are hidden - they're templates, not deployable - **Disabled components** (`metadata.enabled: false`) are hidden - they can't be deployed - **Stack-scoped filtering** - only components in the specified stack appear ## Why This Matters Previously, users would see all components from all stacks, including abstract base components that serve as templates. This was confusing and could lead to errors when selecting a component that couldn't actually be deployed. ## Shell Completion Too Tab completion for component arguments also uses the same filtering logic, so you'll only see valid, deployable components when completing `atmos terraform plan -s stack `. For usage and configuration, see [atmos terraform](/cli/commands/terraform/usage). --- ## Native Pull Requests for Vendored Component Updates Keeping vendored components current across dozens of repositories doesn't scale as a manual habit. Someone has to notice a new upstream release, edit the right `version:` field without breaking a comment or a template, then commit, push, and open a pull request. That has to happen for every component, on some kind of schedule, forever. Most teams either let it slip until something forces an update, or bolt on a third-party GitHub Action just to automate the commit-and-PR part. ## The Problem The [`atmos vendor update`](/cli/commands/vendor/vendor-update) command already finds and writes newer versions locally. Turning that into a reviewable pull request meant scripting Git branch/commit/push logic and a GitHub API client yourself, or reaching for an external action with its own permissions model, its own config format, and its own release cadence to track. ## The Fix The [`--pull-request`](/cli/commands/vendor/vendor-update#flags) flag does the whole cycle natively: discover available updates, write them with the same format-preserving editor `vendor update` already uses, create or reuse a branch, commit, push, and open or update a pull request — through a provider-neutral Git registry (GitHub today; GitLab and Bitbucket can register without any command changes). ```shell atmos vendor update --pull-request ``` Nothing happens unless there's actually an update: no updates means no branch, no commit, no push, no PR. Atmos fetches the base branch but never writes to it. It reuses an existing feature branch for the same scope and pushes it fast-forward only — repeated runs update the same PR instead of piling up duplicates. ## How to Use It Scope updates to a named group instead of updating everything at once: ```yaml vendor: update: groups: platform: include: ["terraform/vpc", "terraform/eks/*"] exclude: ["terraform/eks/legacy"] ci: pull_request: branch_prefix: atmos/component-updater title: "chore(components): update {{ .scope.name }}" labels: [component-update] ``` ```shell atmos vendor update --group platform --pull-request ``` For a scheduled run, the official container image is all a workflow needs — no third-party action performs the update, commit, push, or PR creation: ```yaml on: schedule: [{ cron: "17 3 * * 1" }] permissions: contents: write pull-requests: write jobs: update: runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} steps: - uses: actions/checkout@v6 - run: atmos vendor update --pull-request env: { GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} } ``` Set `execution.mode: worktree` when you'd rather the whole discover-branch-commit-push cycle ran in an isolated checkout instead of the workflow's own working tree — useful if other steps in the same job depend on an unmodified checkout while the update runs. In GitHub Actions, every run also appends a summary showing scope, counts, and the resulting PR link, independent of whether an update was found. ## Triggering Downstream Workflows The example above uses the default `GITHUB_TOKEN`. This token is fine for opening the PR, but GitHub deliberately excludes its own default Actions token from re-triggering `on: pull_request`/ `on: push` workflows. As a result, a plan/validate workflow that's supposed to run against the new PR won't fire. Pair the Component Updater with the [`github/sts`](/cli/configuration/auth#github-sts-atmos-pro) auth integration to get a token that does trigger downstream Actions — a just-in-time GitHub App installation token, minted through [Atmos Pro STS](/changelog/atmos-pro-github-sts) and exported as `ATMOS_PRO_GITHUB_TOKEN`. The `--pull-request` flag already prefers this token over `GITHUB_TOKEN`: ```shell atmos auth exec --identity github-sts -- atmos vendor update --pull-request ``` No other flags or config changes are required. [`atmos auth exec`](/cli/commands/auth/exec) mints and exports the token for the wrapped command's environment. The Component Updater's GitHub client picks it up automatically. ## Get Involved Questions about scopes, groups, or CI publishing are welcome in the [Atmos GitHub repository](https://github.com/cloudposse/atmos) and the community Slack. --- ## Component Workdir Isolation: The Foundation for Concurrent Terraform Operations If you've ever had two component instances pointing to the same base component, you've likely encountered the frustration: file conflicts, unexpected overwrites, and mysterious errors when running Terraform operations. Today, we're introducing **Component Workdir Isolation**—a foundational feature that eliminates these conflicts and unlocks powerful new capabilities for Atmos. ## The Problem: Shared Component Directories Atmos allows you to create multiple instances of the same component across different stacks. For example, you might have a `vpc` component deployed to both `dev` and `prod` environments: ```yaml # stacks/dev.yaml components: terraform: vpc: vars: cidr_block: "10.0.0.0/16" # stacks/prod.yaml components: terraform: vpc: vars: cidr_block: "10.1.0.0/16" ``` Both instances point to the same component directory: `components/terraform/vpc/`. When you run [`atmos terraform plan vpc -s dev`](/cli/commands/terraform/plan), Atmos generates a `dev-vpc.terraform.tfvars.json` file in that directory. Run `atmos terraform plan vpc -s prod` in another terminal, and it generates `prod-vpc.terraform.tfvars.json` in the same place. **Here's where things get messy:** - The `.terraform/` directory is shared between both operations - Lock files can conflict - Running `terraform init` for one stack can interfere with another - Parallel CI/CD pipelines stepping on each other This isn't just an inconvenience—it's a fundamental limitation that prevents concurrent Terraform operations. ## The Solution: Isolated Working Directories With Component Workdir Isolation, each component instance can now run in its own isolated directory: ``` project/ ├── components/terraform/vpc/ # Original component (untouched) │ └── main.tf └── .workdir/terraform/ # Isolated execution directories ├── dev-vpc-bb03116d/ # Instance 1 │ ├── main.tf # Copied from component │ ├── .terraform/ # Isolated state │ └── dev-vpc.terraform.tfvars.json └── prod-vpc-0e327247/ # Instance 2 ├── main.tf ├── .terraform/ └── prod-vpc.terraform.tfvars.json ``` Each instance gets its own: - `.terraform/` directory with isolated provider state - Terraform lock files - Generated varfiles and planfiles - Complete isolation from other instances ## How It Works ### Opt-In for Local Components For local components, enable workdir isolation with the provision config: ```yaml components: terraform: vpc: provision: workdir: enabled: true # Enable isolated workdir vars: cidr_block: "10.0.0.0/16" ``` When `provision.workdir.enabled: true` is set, Atmos: 1. Creates `.workdir/terraform/--/` before `terraform init` 2. Copies the component files to the workdir 3. Runs all Terraform commands in the isolated directory 4. Keeps the original component directory pristine ### Automatic for Remote Sources (Coming Soon) When we introduce JIT (Just-In-Time) vendoring support, workdir isolation will be automatic for any component with a `source`: ```yaml components: terraform: vpc: source: "github.com/cloudposse/terraform-aws-vpc?ref=v1.0.0" vars: cidr_block: "10.0.0.0/16" ``` Remote sources **require** workdir isolation because different component instances might reference different versions of the same source. ## Why This Matters ### Concurrent Terraform Operations With isolated workdirs, you can finally run multiple Terraform operations in parallel without conflicts. This is essential for CI/CD pipelines where multiple jobs may operate on the same component simultaneously. ### Concurrent Workflows This unlocks the ability to run workflow steps in parallel, dramatically reducing deployment times when you need to apply changes across multiple environments. ### Foundation for JIT Vendoring This is the prerequisite for our upcoming JIT vendoring feature, which will allow you to reference remote component sources directly in your stack configuration—no upfront vendoring required. ## Cleaning Up Workdirs are ephemeral and can be cleaned up at any time: ```bash # Clean workdir for a specific component atmos terraform workdir clean vpc --stack dev # Clean all workdirs in the project atmos terraform workdir clean --all ``` The `.workdir/` directory should be added to your `.gitignore`: ```gitignore # Atmos workdirs .workdir/ ``` ## What's Next Component Workdir Isolation is the foundation for several exciting features on our roadmap: - **JIT Vendoring**: Reference components directly from GitHub, S3, or any go-getter-supported source - **Concurrent Workflows**: Run workflow steps in parallel for faster deployments - **Improved CI/CD**: Native support for parallel Terraform operations in pipelines ## Get Started Workdir isolation is available now. Simply add `provision.workdir.enabled: true` to any component that needs isolation: ```yaml components: terraform: my-component: provision: workdir: enabled: true ``` For more details, see the [Component Configuration documentation](/cli/configuration/components). For usage and configuration, see [Workdir Provisioning](/stacks/components/provision/workdir). --- ## Comprehensive Terraform Documentation and Enhanced Help System This release brings documentation improvements to Atmos, making it easier to understand and use Terraform commands. We've focused on comprehensive command documentation and automated screengrab generation. ## Complete Terraform Command Documentation We've added comprehensive documentation for all Terraform commands integrated with Atmos. Each command now includes: - **Detailed usage examples** with real-world scenarios - **Complete flag reference** with descriptions and defaults - **Atmos-specific behavior** explanations - **Visual screengrabs** showing actual CLI output - **Backend configuration** details and limitations Key additions include: - Terraform planfile workflow documentation with security considerations - All Terraform subcommands (`plan`, `apply`, `destroy`, `validate`, `fmt`, `output`, etc.) - Best practices for using Terraform with Atmos. [View Terraform Documentation](/cli/commands/terraform/usage) ## Screengrab Generation Infrastructure We've built a complete system for generating accurate, up-to-date CLI help screengrabs: - **Automated generation** - All help screengrabs generated from actual CLI output - **Color preservation** - ANSI colors converted to HTML for documentation - **Docker/Podman support** - Cross-platform screengrab generation - **CI/CD integration** - GitHub Actions workflow for automatic updates This ensures our documentation always reflects the actual CLI behavior. ## Documentation Fixes Numerous documentation improvements including: - Fixed broken links across the documentation site - Corrected Terraform command examples - Updated helmfile sync description to accurately reflect behavior - Added security warnings for credential handling in planfiles - Improved Markdown formatting consistency ## Contributors This release includes contributions from the Atmos team and community. Thank you to everyone who provided feedback, reported issues, and contributed code! For the complete list of changes, see the [GitHub release notes](https://github.com/cloudposse/atmos/releases). --- Have questions or feedback? Join us on [Slack](https://slack.cloudposse.com/) or open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## New Comprehensive Version Management Documentation When you deploy infrastructure across multiple environments—dev, staging, production—you need a way to manage which version of each component runs where. Maybe your VPC module in dev is testing new CIDR ranges, while production stays on the stable version until you're confident the changes work. That's **version management**: deciding how different versions of your infrastructure components flow through your environments. The obvious answer—pin every version in every environment—turns out to optimize for the wrong thing. Strict pinning creates divergence by default: environments drift apart unless you constantly update pins. It weakens feedback loops because lower environments stay on old versions, hiding cross-environment impacts. And at scale, you face PR storms from automated dependency updates. So what's the right approach? It depends. We've documented these strategies as **design patterns**—proven approaches that optimize for different goals. Some prioritize convergence and fast feedback; others prioritize control and reproducibility. The best choice depends on your organization's culture, team size, and how you already think about software delivery. ## What's New The new documentation provides a complete framework for understanding and implementing version management in Atmos, including: ### 🎯 Clear Strategy Recommendations **[Continuous Version Deployment](/design-patterns/version-management/continuous-version-deployment)** is our recommended default pattern. This trunk-based approach: - **Promotes convergence** across all environments through progressive rollout - **Simplifies operations** with no complex version tracking or branch management - **Enables easy previews** to see change impacts across dependent environments - **Supports rapid iteration** with confident, frequent deployments ### 📚 Comprehensive Pattern Documentation All version management patterns are fully documented under a unified [Version Management](/design-patterns/version-management) section: #### Deployment Strategies **[Continuous Version Deployment](/design-patterns/version-management/continuous-version-deployment)** - The recommended trunk-based approach where all environments converge to the same version through automated progressive rollout. As LaunchDarkly puts it, "Decoupling deploy from release increases speed and stability when delivering software." Atmos achieves this through CI/CD gates that control when environments receive changes. **[Git Flow: Branches as Channels](/design-patterns/version-management/git-flow-branches-as-channels)** - Long-lived branches map to release channels for teams that need prolonged divergence or already practice Git Flow workflows. Use when you need version control to represent current state versus desired state. #### Folder Organization Approaches Within Continuous Version Deployment, choose how to organize component folders: **[Folder-Based Versioning](/design-patterns/version-management/folder-based-versioning)** - Simple, explicit folder structures (`vpc/`, `eks/`, `rds/`). What you see is what you get. **[Release Tracks/Channels](/design-patterns/version-management/release-tracks-channels)** - Named release channels (`alpha/vpc`, `beta/vpc`, `prod/vpc`) where environments subscribe to moving tracks. **[Strict Version Pinning](/design-patterns/version-management/strict-version-pinning)** - Explicit SemVer versions (`vpc/1.2.3`, `vpc/2.0.0`) for vendored components and shared libraries. #### Complementary Techniques **[Vendoring Component Versions](/design-patterns/version-management/vendoring-components)** - Automate copying component versions from external sources with manifest tracking. Works with any deployment strategy. ### 💡 When to Use Each Pattern The documentation includes clear guidance on choosing the right pattern for your organization: **Use Continuous Version Deployment when:** - You embrace trunk-based development - All environments should eventually converge to the same version - You want preview capabilities across all environments - You have strong CI/CD automation **Use Git Flow when:** - Your organization already practices Git Flow branch management - You need prolonged divergence between environments - You're comfortable with cherry-picking and merge strategies - You want version control to represent current vs. desired state ### 🛠️ Practical Improvements Throughout the documentation, you'll find: - **Code examples first** - Developers absorb patterns faster through examples - **Complete workspace\_key\_prefix coverage** - Critical for Terraform state management across version changes - **Go template documentation** - Understand `{{.Component}}`, `{{.Version}}`, and other template variables - **Stack-level base\_path alternative** - DRY alternative to repeating metadata.component paths - **Anti-patterns section** - Learn what to avoid (vendoring to same path, inconsistent conventions, etc.) ### 📊 Comparison Tables Quick-reference tables help you understand trade-offs: | Strategy | Development Model | Convergence | Automation | Best For | |----------|------------------|-------------|------------|----------| | **Continuous Version Deployment** | Trunk-based | Very High | Required | Most teams - simple, automated convergence | | **Git Flow** | Branch-based | Medium | Optional | Legacy systems with established branch workflows | ## Getting Started Start with the [Version Management overview](/design-patterns/version-management) to understand all available patterns, then dive into [Continuous Version Deployment](/design-patterns/version-management/continuous-version-deployment) for our recommended default approach. The documentation includes: - Real-world examples with complete configurations - Step-by-step implementation guides - Troubleshooting sections for common issues - Migration paths between patterns ## Key Takeaway **The best strategy is one that follows how your team already thinks about software delivery.** As the Thoughtworks Technology Radar notes, "More-frequent deployments reduce the risk associated with change, while business stakeholders retain control over when features are released." If your team has established Git Flow practices, extend them to infrastructure—keeping the mental model consistent matters. If you embrace trunk-based development with strong automation, Continuous Version Deployment is your simplest path forward. Ready to explore? Check out the new [Version Management documentation](/design-patterns/version-management) today! ## Share Your Experience Have you found another versioning strategy that works well for your organization? We'd love to hear about it! Share your approach in our [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) or [open an issue](https://github.com/cloudposse/atmos/issues) to help us expand this documentation with real-world patterns from the community. --- ## Config Editions: Pin Your Defaults to a Date So Upgrades Never Surprise You You upgrade a CLI tool and something changes that you never asked for. Output that used to page now scrolls past. An integration that used to run automatically now doesn't. Nothing in your configuration changed — a _default_ did, somewhere in the release notes you didn't read, and now you're bisecting versions to figure out which upgrade moved the furniture. Every tool with evolving defaults forces this trade-off: either the project never improves its out-of-the-box behavior, or every upgrade is a small gamble. Atmos editions resolve that trade-off the way Rust did: defaults evolve for new projects, and existing projects opt into change on their own schedule. Pin your project to a date, and upgrading Atmos never silently changes a default again. ## The Problem Atmos ships sane defaults, and those defaults improve over time. The built-in pager default changed from on to off. The Helmfile EKS integration went from automatic to opt-in. Each of those changes made Atmos better for new projects — and each one silently altered behavior for existing projects the moment they upgraded. Until now there was no middle ground: - **Freezing the binary version** meant giving up bug fixes and new features just to keep stable behavior. - **Explicitly setting every default you depend on** meant knowing which defaults you depend on — which you typically discover only after one changes underneath you. - **Reading every release's changelog** doesn't scale, and it puts the burden on every user for every upgrade. ## The Fix Every change to a previously shipped default is now journaled with the date it changed, the old and new values, and the pull request that changed it. Your project can pin itself to a date — an _edition_ — and Atmos keeps the defaults as they stood on that date, no matter how new the binary is: ```yaml title="atmos.yaml" edition: "2026-01" ``` Anchors accept a year, a month, or an exact day, and partial dates round to the _end_ of the period — "the 2026 edition" includes everything shipped during 2026, matching Rust's edition semantics. The pin can also be set per-invocation with the `--edition` global flag or the `ATMOS_EDITION` environment variable (flag beats env beats config). The semantics are deliberately narrow: - **Only changed defaults roll back.** Defaults that changed after your pinned date keep their pre-change values; nothing else is touched. - **New features still work.** Only _changes_ to previously shipped defaults are journaled — never new keys — so a feature introduced after your pin loads with its initial defaults. - **Your explicit configuration always wins.** A value set in `atmos.yaml` beats the pin, exactly as it beats the latest defaults. - **No pin, no change.** Projects without an `edition` key follow the latest defaults — and the section below lists exactly which defaults move in this release for those projects. ## Defaults That Change in This Release Editions ship alongside a batch of default changes, and projects without a pin pick these up on upgrade: - **The default log level is now Warning.** This change was announced back in September 2025 ([PR #1430](https://github.com/cloudposse/atmos/pull/1430)), but a lower configuration layer kept the effective default at Info the whole time. This release removes that shadow, so for unpinned projects informational messages stop printing by default. To keep the old verbosity, pin `edition: "2025-08"` or set `logs.level: Info` explicitly. - **Component descriptions show provenance by default.** Running [`atmos describe component`](/cli/commands/describe/component) now annotates each value with the stack file that set it. Disable per-invocation with `--provenance=false`, per-environment with `ATMOS_DESCRIBE_PROVENANCE=false`, or per-project with `describe.provenance: false` in `atmos.yaml`. - **Component descriptions show only what stack manifests define.** The output of `atmos describe component` is now scoped to the sections a stack manifest can declare — vars, settings, env, backend, metadata, and friends — instead of every internal field Atmos computes along the way. Set `describe.component.filter: full` (or `ATMOS_DESCRIBE_COMPONENT_FILTER=full`) to see everything again; queries with `--query` always run against the full data. Every one of these changes is journaled, so a single pin — any date before the change you want to avoid — rolls it back. ## Fixed Along the Way Building the journal meant auditing every layer where a default can live, and that audit surfaced real bugs. All of them are fixed in this release: - Terminal output was artificially clamped in several places. Tables capped columns at fixed widths even on wide terminals, bare help output stopped at 120 columns, and a startup race could freeze the detected width at 78 columns. Rendering now uses the full detected terminal width everywhere, and `settings.terminal.max_width` acts only as an explicit, opt-in ceiling — unlimited by default. - Table cells wrap cleanly at column boundaries instead of overflowing the terminal edge mid-word, narrow columns keep their content on one line before wide columns take the remaining space, and the description column aligns exactly with its header (markdown rendering had smuggled in a leading space that survived trimming because it was wrapped in color codes). - The authentication provider and identity tables sized their columns at compile time and truncated URLs at 32 characters regardless of terminal width; they now size to content. - Three defaults had silently drifted between configuration layers, so the declared default never took effect — the Helmfile EKS integration, the pager, and the log level each said one thing in one layer and another thing in the layer that actually won. The layers now agree, the declared values are finally real, and each transition is journaled so a pin restores the old behavior. ## How to Use It Browse the journal of default changes — or diff two editions to see exactly what an upgrade changes: ```shell atmos list editions atmos list editions --from=2025 --to=2026 ``` With only `--from`, the same command answers "what changes if I unpin?": ```shell atmos list editions --from=2025-09 ``` Pin the project, then inspect exactly what the pin does: ```yaml title="atmos.yaml" edition: "2025-09" ``` ```shell atmos describe edition ``` The output shows whether a pin is active, where it came from (flag, env, or config), the resolved anchor date and its granularity, and every default the pin rolls back — with the pinned value alongside the latest default you'd get by unpinning. With the example pin above, the project predates eight of the ten journaled changes, so it keeps the pager on, the Helmfile EKS integration enabled, and the pre-2026 describe and list behavior — everything as it stood at the end of September 2025. Editions are **experimental**: with the default `settings.experimental: warn`, a pinned project prints an experimental notice, and `settings.experimental: disable` blocks the feature entirely. See the [edition configuration reference](/cli/configuration/edition) for details. ## Get Involved The journal already reaches back to February 2025 — ten entries covering everything from the log destination to listing filters and output scope — and it grows only when a shipped default actually changes. If a past default change bit you that isn't journaled yet, or you have opinions on how edition boundaries should be versioned going forward, [open an issue](https://github.com/cloudposse/atmos/issues) and tell us about it. --- ## Smarter Type Handling for `atmos config set` and `atmos stack set` Editing a config value from the command line is supposed to be the easy path. Type `atmos stack set vars.replicas 5`, expect a `5`, move on. Except a value like that used to come back out the other side as the string `"5"` unless you remembered to pass `--type=int` — and for [`atmos stack set`](/cli/commands/stack/stack-set) specifically, that was true every single time, no matter what was already there. A `true` became `"true"`. A number stayed a number only if you told the CLI so yourself. ## The Problem [`atmos config set`](/cli/commands/config/config-set) already tried to guess a value's type from the Atmos config schema, but only for fields the schema knows about — everything else, including the free-form `vars` and `settings` sections most people actually edit, fell back to a plain string with no warning. `atmos stack set` didn't even have that: it always wrote a string unless you passed [`--type`](/cli/commands/stack/stack-set#flags) explicitly, since component variables have no fixed schema to infer from in the first place. On top of that, editing a value that came from an imported catalog file — the normal way Atmos stacks are organized, defaults in one file, per-environment overrides in another — could fail outright with an error that pointed at the wrong manifest, leaving no clue where the real value actually lived. ## The Fix `--type` now defaults to `auto` on both commands, and `auto` tries harder before it gives up. `config set` checks the Atmos config schema first, then the type of the value already at that path. `stack set` checks the component's own declared Terraform variable type for `vars.*` paths first — Atmos already parses `variables.tf` while resolving the component, so this costs nothing extra — then the existing value. If the declared type disagrees with what's already stored (a number saved as a quoted string, say), Atmos retypes it and tells you so, instead of leaving the mismatch in place. Only after all of that does either command fall back to the value's own shape: `5` infers as an int, `true` infers as a bool, `3.14` infers as a float. A value doesn't get silently downgraded to a quoted string just because nothing else had an opinion on it anymore — that fallback (with a warning) is now reserved for values that genuinely don't look like anything but a string. Editing values that live only in an imported catalog file is also fixed: `atmos stack set`, `get`, `delete`, and `list` now correctly resolve to the file that actually defines the value, instead of misattributing it to whichever stack manifest happened to import it. ## How to Use It ```shell # vars.replicas is already 1 (an int) in the manifest -- auto keeps it an int. atmos stack set vars.replicas 5 -s plat-ue2-prod -c vpc # If variables.tf declares vars.quota as a number, auto uses that -- and retypes an # existing value that disagrees, e.g. one stored as a quoted string. atmos stack set vars.quota 10 -s plat-ue2-prod -c vpc # A brand-new vars.new_flag with nothing existing to infer from now infers straight # from the value's shape: "true" is written as a bool, not the string "true". atmos stack set vars.new_flag true -s plat-ue2-prod -c vpc # Works even when the value is only defined in an imported catalog file. atmos stack set vars.region us-west-2 -s plat-ue2-prod -c vpc ``` `--type` still accepts `string`, `int`, `bool`, `float`, `null`, or `yaml` explicitly whenever you want to skip inference entirely. ## Get Involved Run into a case where inference guesses wrong, or a value that still won't resolve? Open an issue on [GitHub](https://github.com/cloudposse/atmos) — this is exactly the kind of day-to-day CLI friction we want to keep sanding down. --- ## Remote Build Caching for Container Builds Container image builds get slower as a Dockerfile grows, and CI runners rarely keep a warm local cache between runs. Teams work around this with a handful of `docker/setup-buildx-action` and `docker/build-push-action` steps that provision a builder and wire up a remote cache — glue that lives outside the rest of the pipeline and has to be reproduced in every workflow that builds an image. The native `container` build step can now provision that builder and cache directly, so a build's caching strategy lives next to the rest of its configuration instead of in separate Actions. ## The Problem A `docker buildx build` that starts cold rebuilds every layer, even when nothing meaningful changed. Reusing a remote cache and a purpose-built builder solves this, but doing it by hand means composing several pieces: creating a builder with the right driver, pointing it at a registry-backed cache, and keeping all of that in sync with the rest of the build step. ## The Fix `action: build` now accepts `driver` and `cache`: ```yaml - name: build type: container action: build provider: docker with: engine: buildx context: . dockerfile: Dockerfile tags: - registry.example.com/app:latest driver: docker-container # shorthand — just the driver cache: from: - type: registry ref: registry.example.com/app:buildcache to: - type: registry ref: registry.example.com/app:buildcache mode: max ``` Atmos creates the named builder if it doesn't already exist and reuses it on later runs, so the builder's own cache persists across builds on the same host instead of starting cold every time. `driver` also accepts a full form when you need driver-specific options, such as pointing Buildx at a mirrored BuildKit image to avoid Docker Hub rate limits: ```yaml with: driver: name: atmos provider: docker-container opts: image: mirror.gcr.io/moby/buildkit:buildx-stable-1 ``` ## How to Use It Add `driver` and `cache` under an existing `action: build` step wherever you use `engine: buildx`. If you build with `bake` instead, keep `cache-from`/`cache-to` in the bake file itself — Bake already supports them natively — and use `driver` for builder selection either way. See the [`container` step documentation](/workflows/steps/type/container#driver) for the full field reference, including the three ways to avoid Docker Hub rate limits. For usage and configuration, see [Using Containers](/components/container). ## Get Involved Try `driver` and `cache` in a build step and share feedback through [GitHub issues](https://github.com/cloudposse/atmos/issues). --- ## Container Build Paths Depended on Where You Ran Atmos From A container component's `build.context` and `dockerfile` looked like ordinary relative paths, but they weren't resolved against anything in particular — they resolved against whatever directory your shell happened to be in the moment you ran [`atmos container build`](/cli/commands/container/usage). Run it from the repo root and it worked. Run it from a subdirectory, a CI job with a different working directory, or a script that changes directories first, and the build silently picked up the wrong Dockerfile or found nothing at all. Setting `components.container.base_path` didn't help — that option was accepted but quietly ignored. ## The Problem Terraform, Helmfile, Kubernetes, and Helm components have always resolved their working directory the same way: a configurable `base_path` per component type, joined with the component's own name, computed once and used consistently no matter where `atmos` is invoked from. Container components never got wired into that mechanism. `build.context` and `build.dockerfile` were passed straight through to `docker build`/`podman build` with no anchoring at all, and `run.mounts[].source` anchored only to the bare project root — never to the component itself. `components.container.base_path` existed in the config schema, but nothing ever read it. ## The Fix Container components now resolve `build.context`, `build.dockerfile`, and `run.mounts[].source` the same way every other component type does: relative to `components.container.base_path` (default `components/container`) joined with the component's own name, independent of the directory `atmos` happens to be invoked from. `components.container.base_path` is now a real, working setting. Container components also gained the same just-in-time source provisioning as Terraform and Helmfile components — a component that declares `source:` is now auto-vendored into a workdir, and that workdir becomes the anchor for build and mount paths instead of the static base path. ## Breaking Change If your container component's `build.context`, `build.dockerfile`, or `run.mounts[].source` are relative paths, they now resolve against `//` instead of the previous CWD-dependent (build) or bare-project-root (mounts) behavior. Update your setup one of two ways: move build assets under `components/container//` to match the same convention Terraform components already use, or point `components.container.base_path` at wherever your container build assets currently live. ## How to Use It ```yaml # atmos.yaml components: container: base_path: components/container # default ``` ```yaml components: container: api: build: context: app # resolves to components/container/api/app dockerfile: Dockerfile # resolves to components/container/api/app/Dockerfile ``` For usage and configuration, see [Using Containers](/components/container). ## Get Involved Try it out with an existing container component or a fresh one. Questions or ideas? Start a thread in [GitHub Discussions](https://github.com/cloudposse/atmos/discussions), or open an issue in the [issue tracker](https://github.com/cloudposse/atmos/issues). --- ## Container Components and Compositions Atmos now has a first-class **container component kind**. Where a `type: container` workflow step is a procedural `docker run --rm`, a `components.container` entry is declarative, stack-scoped infrastructure: one component is one service, with an image artifact Atmos builds/pushes/pulls and an optional long-running named container you operate with [`atmos container up/ps/logs/exec/restart/stop/rm/down`](/cli/commands/container/usage). A new `compositions` section groups the components that make up a system. This is the stack-scoped counterpart to the [native container steps](/changelog/native-container-steps) shipped earlier. The step is ephemeral and workflow-scoped; the component is addressable infrastructure that lives in your stacks alongside Terraform and Helmfile — same imports, inheritance, catalogs, deep-merge, templating, and secrets. ## One Component, One Container Container components use **first-class sections** (`image`, `build`, `run`) — consistent with the container step, NOT nested under `vars`: ```yaml components: container: api: composition: storefront # composition membership (optional) image: nginx:alpine env: NGINX_ENTRYPOINT_QUIET_LOGS: "1" run: ports: - host: 8080 container: 80 restart: policy: unless-stopped # Docker Compose-style restart policy healthcheck: # first-class health check test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost/ || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 10s ``` `build`, `run.command`, `mounts`, `ports`, `user`, `restart`, and `healthcheck` all map onto the Docker Compose shapes you already know. Inheritance ([`metadata.inherits`](/stacks/components/component-metadata#inherits)), abstract base components, catalogs, and deep-merge work exactly like every other component kind, so shared `run`/`build` defaults live in one place. ## Operated by Labels, Not State Files Each instance is named and labeled from its canonical address `/container/` (e.g., `atmos-dev-container-api`). Lifecycle commands discover the container by label — there are no local state files to drift or lose: ```shell atmos container build api -s dev # build the image from `build` atmos container up api -s dev # create/start the long-running container (build-on-missing) atmos container list # all container components + running/health state atmos container ps|logs|exec api -s dev atmos container down api -s dev # stop + rm ``` ## Compositions A composition groups components into a system. Declare membership on the component with the `composition` field; the top-level `compositions` section declares the closed set of services: ```yaml compositions: storefront: description: Storefront system services: [api, worker, database] ``` Run [`atmos composition validate storefront -s dev`](/cli/commands/composition/usage) to see which declared services are fulfilled vs. not yet provided in a given stack — a closed contract for membership, open for fulfillment. [View the full example](/examples/compositions) ## Try It The new `examples/container-component` example defines an abstract base, a long-running web service with a health check, and a built-from-Dockerfile worker — all under one composition: ```shell cd examples/container-component atmos container up api -s local atmos container list ``` [View the full example](/examples/container-component) Container components are experimental while the surface settles. The goal is unchanged: run the same declared system locally, in CI, and against real environments — without one-off shell scripts. For usage and configuration, see [Using Containers](/components/container). --- ## A Typo in a Container Step's `with:` Block Used to Just... Do Nothing You add `platforms: [linux/amd64]` to a container build step, run it, and nothing happens differently. No error, no warning — the field is just gone, like it was never written. You spend ten minutes checking your YAML indentation before realizing the field name was never real to begin with. ## The Problem `type: container` steps take their configuration under `with:` (and, for the driver, a nested `driver:` block) as a typed mapping — `run`, `build`, `push`, and `inspect` actions each have a fixed set of real fields. But nothing checked that the keys you wrote were actually among them. A typo'd field name, a field copied from Docker Compose that doesn't exist in Atmos, or a straight-up nonexistent option all decoded the same way: silently discarded, with the rest of the mapping loaded normally. The step would run — just without the setting you thought you'd configured. ## The Fix `with:` and `driver:` blocks on container steps now reject unknown fields outright, in both standalone workflow files and custom commands. A typo like `platforms:` (not a real field) now fails the step with a clear decode error naming the field, instead of quietly doing nothing. ## Breaking Change If a `with:` or `driver:` block on a `type: container` step currently has a field that isn't one of the real, documented fields, it will now fail to load instead of being silently ignored. Check your container steps for typos or leftover fields from a different tool's config format before upgrading. ## Also in This Release: Local Backend State Was Being Deleted on Re-Provision If you used a `local` Terraform backend on a component with just-in-time (JIT) workdir provisioning enabled, your state was gone after the second run. Not corrupted — deleted, cleanly, with no error. `apply` would create it, and the very next `plan` would silently start over with an empty state, because the workdir provisioner's incremental sync treated the state file the same way it treats any file that isn't part of your component's source: not present in source, so remove it. `terraform.tfstate` was never protected from that cleanup the way provider lock files already were. The workdir provisioner now leaves `terraform.tfstate`, `terraform.tfstate.backup`, and the transient `.terraform.tfstate.lock.info` marker alone, in both directions — never copied in from source, never deleted from the workdir. If you were working around this by avoiding JIT workdir provisioning for local-backend components, that workaround is no longer necessary. ## Also in This Release: Workdir Path Encoding Separately, the internal `.workdir/` directory Atmos uses for JIT component provisioning now encodes component and stack names more carefully, to guarantee two differently named components (e.g. one named `app/local` and another named `app-local`) can never accidentally resolve to the same on-disk directory and share files or state. This changes the on-disk directory name for any component whose name contains a literal `-`, `/`, or `\`. Atmos migrates a workdir it finds at the old location automatically the next time that component is provisioned, so this should be transparent for most setups. For usage and configuration, see [Using Containers](/components/container). --- ## Containers, Emulators, and Run Steps Now Resolve Each Other by Name Bring up two containers in the same environment and the first thing you hit is that they can't find each other. Docker's default bridge network hands out a private IP to each container but gives you no way to resolve a sibling by name, so you either hardcode IPs that change on every restart, or reach for `docker network create` and wire up the aliases yourself. Docker Compose solved this years ago by giving every project its own network and naming each service after itself. Atmos containers had no equivalent — every one landed on the default bridge, reachable only through published host ports. ## The Problem A container component's `run` config could publish ports to the host, but two container components in the same stack — or a container and a [local emulator](/stacks/components/emulator) — had no way to talk to each other directly. A workflow step that spun up a one-shot container to run tests against those services hit the same wall. The only fix was manual: create a network by hand, or fall back to routing everything through the host. ## The Fix Every container component, one-shot container run, and stack-scoped workflow `type: container` step now automatically joins a shared network for its stack and gets a predictable DNS alias — no configuration required. It's the same idea as the network Docker Compose creates for a project, scoped to your Atmos stack instead. - A component named `api` in stack `dev` is reachable at `dev-api`. - An emulator and a container component in the same stack land on the same network, so either can resolve the other by name. - A workflow `run` step that resolves a stack (its own `stack:` field, or the workflow's `--stack`/`ATMOS_STACK`) joins that same network too, so a test-runner step can hit `http://dev-api:80` directly. It's best-effort: if the container runtime can't create or join a network, everything still runs — you just lose the ability to resolve peers by name, and host-published ports keep working exactly as before. ## How to Use It Nothing to turn on. Bring up two services in the same stack and reference one from the other by its `-` alias: ```yaml components: container: api: image: localhost:5001/api:latest run: ports: - host: 8080 container: 80 worker: image: localhost:5001/worker:latest run: command: ./worker --api-url=http://dev-api:80 ``` ```bash atmos container up api -s dev atmos container up worker -s dev # worker resolves dev-api on the shared network — no host port juggling needed. ``` The same alias works from a workflow `run` step scoped to the same stack: ```yaml steps: - name: smoke type: container action: run stack: dev with: image: curlimages/curl command: curl -f http://dev-api:80/health ``` For usage and configuration, see [Using Containers](/components/container). ## Get Involved Try it out with a couple of container components or an emulator in the same stack. Questions or ideas? Start a thread in [GitHub Discussions](https://github.com/cloudposse/atmos/discussions), or open an issue in the [issue tracker](https://github.com/cloudposse/atmos/issues). --- ## Copy any docs page as Markdown — atmos.tools now serves a raw .md alternate for every page Every page on atmos.tools is now available as raw Markdown. Append `.md` to any docs URL and you'll get a clean, MDX-component-aware Markdown file ready to paste into an LLM, a ticket, or another doc. ## What Changed - **Per-page `.md` routes.** Every doc page is now mirrored to `.md`. For example, [`/cli/commands/terraform/apply`](https://atmos.tools/cli/commands/terraform/apply) is also served as [`/cli/commands/terraform/apply.md`](https://atmos.tools/cli/commands/terraform/apply.md) with `Content-Type: text/markdown`. - **"Copy Markdown" / "View Markdown" buttons** on every doc page. One click copies the raw Markdown to your clipboard; the other opens the `.md` source in a new tab. - **`` in every page's ``** so crawlers, LLM tools, and other automation can discover the Markdown alternate without scraping HTML. - **MDX components are normalized**, not stripped. Custom components like ``, ``/``, ``, ``, ``, ``, and definition lists round-trip into portable Markdown: tabs become headings, code blocks stay fenced, flag tables become bulleted lists. - **`llms-full.txt` got a quality bump too.** The same MDX→Markdown normalizer now powers the LLM corpus file, so previously-stripped content (tabs, flag definitions, intros) is preserved. ## Why This Matters When you're debugging with an AI assistant, pasting in screenshots of docs is awkward and a HTML copy is even worse (full of layout chrome and broken inline components). A raw Markdown alternate makes our docs first-class context for any LLM workflow — and gives the docs themselves a more durable, portable surface area. The `rel="alternate"` link also lets agentic crawlers and indexers find the Markdown source without HTML scraping heuristics. ## How to Use It On any doc page, click **Copy Markdown** above the title — or just append `.md` to the URL: ```text https://atmos.tools/cli/commands/terraform/apply https://atmos.tools/cli/commands/terraform/apply.md ← the same page, raw Markdown ``` You can fetch it programmatically too: ```shell curl https://atmos.tools/cli/commands/terraform/apply.md ``` ## Get Involved The MDX-component normalizer is open source under [`website/plugins/docusaurus-plugin-llms-txt/`](https://github.com/cloudposse/atmos/tree/main/website/plugins/docusaurus-plugin-llms-txt). If you spot a component that renders awkwardly in the `.md` output — or if you want richer handling for a particular Atmos component — open an issue or PR with the page URL and the expected Markdown. --- ## Boolean Flags with Default Values for Custom Commands Custom commands now support boolean flags with configurable default values. You can define `type: bool` flags that default to `true` or `false`, making it easier to handle special behavior triggers. ## What Changed Previously, boolean flags in custom commands always defaulted to `false`. Now you can specify any default value: ```yaml commands: - name: deploy flags: - name: auto-approve type: bool default: true # Now supported! - name: dry-run type: bool default: false steps: - | {{ if .Flags.auto-approve }} terraform apply -auto-approve {{ else }} terraform apply {{ end }} ``` ## Using Boolean Flags Boolean values render directly as `true` or `false` in templates: ```yaml steps: # Direct usage - renders as "true" or "false" - echo "Auto-approve is {{ .Flags.auto-approve }}" # Conditional logic - | {{ if .Flags.verbose }} set -x {{ end }} ``` ## String Flag Defaults String flags also support default values now: ```yaml flags: - name: environment default: "development" - name: format default: "json" ``` ## Documentation See the updated [Custom Commands documentation](/cli/configuration/commands/flags#boolean-flags) for more patterns and examples. --- ## Introducing 25+ Interactive Step Types for Workflows and Custom Commands Atmos now includes 25+ interactive step types for both workflows and custom commands. Build interactive CLI wizards, collect user input, display rich output, and control execution flow—all directly in your `atmos.yaml` without external scripts. ## What's New We've built a unified execution engine (`pkg/runner`) that powers both workflows and custom commands with the same rich step types: - **Interactive**: `input`, `confirm`, `choose`, `filter`, `file`, `write` - **UI Messages**: `success`, `info`, `warn`, `error`, `markdown` - **Output**: `spin`, `table`, `pager`, `format`, `join`, `style`, `linebreak`, `log` - **Terminal**: `alert`, `title`, `clear`, `env`, `exit` - **Command**: `shell`, `atmos` The `shell` and `atmos` types continue to work exactly as before—this is purely additive. ## Why This Matters Previously, building interactive CLI experiences required shell scripts and external tools like `gum` or `fzf`. Now you can create guided deployment wizards, configuration tools, and interactive runbooks natively in Atmos. **For workflows:** Define multi-step automation with user prompts, confirmations, and rich feedback. **For custom commands:** Users discover your interactive commands via [`atmos help`](/cli/commands/help) and get a polished experience. ## How to Use It ### Workflow Example ```yaml title="stacks/workflows/deploy.yaml" workflows: deploy: description: Interactive deployment workflow steps: - name: env type: choose prompt: "Select target environment" options: [dev, staging, prod] default: dev - name: confirm type: confirm prompt: "Deploy to {{ .steps.env.value }}?" - name: deploy type: atmos command: terraform apply vpc -s {{ .steps.env.value }} - type: success content: "Deployment to {{ .steps.env.value }} completed!" ``` Run with: [`atmos workflow deploy -f deploy`](/cli/commands/workflow) ### Custom Command Example ```yaml title="atmos.yaml" commands: - name: deploy-wizard description: Interactive deployment wizard steps: - name: env type: choose prompt: "Select target environment" options: [dev, staging, prod] default: dev - name: prod_warning type: warn content: "You are about to deploy to PRODUCTION!" - name: confirm type: confirm prompt: "Deploy to {{ .steps.env.value }}?" default: false - name: deploy type: atmos command: terraform apply vpc -s {{ .steps.env.value }} - name: done type: success content: "Deployment to {{ .steps.env.value }} completed!" ``` Run with: `atmos deploy-wizard` ### Multi-Select Components ```yaml commands: - name: deploy-components description: Select and deploy multiple components steps: - name: components type: filter prompt: "Select components to deploy" multiple: true options: [vpc, eks, rds, s3, lambda] - name: summary type: markdown content: | ## Deployment Summary You selected **{{ len .steps.components.values }}** components: {{ range .steps.components.values }} - {{ . }} {{ end }} - name: confirm type: confirm prompt: "Proceed with deployment?" - type: shell command: | {{ range .steps.components.values }} echo "Deploying {{ . }}..." {{ end }} ``` ### Input Collection ```yaml commands: - name: create-ticket description: Create a deployment ticket steps: - name: title type: input prompt: "Ticket title" placeholder: "Brief description of the change" - name: description type: write prompt: "Detailed description" - name: priority type: choose prompt: "Priority level" options: [low, medium, high, critical] default: medium - type: success content: | Ticket created: Title: {{ .steps.title.value }} Priority: {{ .steps.priority.value }} ``` ## Variable Passing Access previous step results using Go templates: - `{{ .steps..value }}` - Primary value (string) - `{{ .steps..values }}` - Multiple values (for multi-select) - `{{ .steps..metadata. }}` - Metadata like `exit_code`, `stdout`, `stderr` - `{{ .env. }}` - Environment variables ## Get Involved For the complete step types reference, see the [Custom Commands documentation](/cli/configuration/commands/steps#extended-step-types) and [Workflows documentation](/workflows/steps/type). Have feedback or ideas? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues) or join our [Slack community](https://slack.cloudposse.com). --- ## Introducing Custom Component Types for Custom Commands Atmos custom commands can now define their own component types beyond `terraform`, `helmfile`, and `packer`. Use Atmos's stack configuration system with any tool: Ansible, Kubernetes manifests, shell scripts, CDK, and more. ## The Challenge Atmos has always excelled at managing Terraform, Helmfile, and Packer components through its powerful stack configuration system. But what if you want to manage other tools the same way? Previously, custom commands could access Terraform component configuration using `component_config`, but this was limited to the `terraform` component type. If you wanted to run Ansible playbooks or deploy Kubernetes manifests with the same configuration-driven approach, you were out of luck. ## The Solution: Custom Component Types Now you can define your own component types directly in custom commands. Here's a simple example: ```yaml commands: - name: script description: "Run script components" arguments: - name: component type: component # Semantic type - this provides the component name required: true flags: - name: stack shorthand: s semantic_type: stack # Semantic type - this provides the stack name required: true component: type: script # Your custom component type steps: - 'echo "App: {{ .Component.vars.app_name }}"' - 'echo "Version: {{ .Component.vars.version }}"' ``` Then define your component in stack manifests just like Terraform: ```yaml # stacks/deploy/dev.yaml components: script: # Matches component.type deploy-app: vars: app_name: "myapp" version: "1.0.0" ``` Run it: ```bash atmos script deploy-app -s dev # Output: # App: myapp # Version: 1.0.0 ``` Here's the `script` component type running end-to-end: [View the full example](/examples/custom-components) ## Key Features ### Typed Arguments and Flags Tell Atmos which argument provides the component name and which provides the stack name: - **Arguments**: Use `type: component` or `type: stack` - **Flags**: Use `semantic_type: component` or `semantic_type: stack` Note: For flags, we use `semantic_type` because `type` already specifies the data type (`string`, `bool`). ### Full Stack Inheritance Custom components inherit from catalog files just like Terraform components: ```yaml # stacks/catalog/script/deploy-app.yaml components: script: deploy-app: vars: app_name: "myapp" version: "1.0.0" replicas: 3 ``` ```yaml # stacks/deploy/dev.yaml import: - catalog/script/deploy-app components: script: deploy-app: vars: replicas: 1 # Override for dev ``` ### Global Vars Merged Automatically Global `vars`, `settings`, and `env` from your stack are automatically merged into custom components. ### Template Variables Access all component configuration through `{{ .Component.* }}`: | Variable | Description | |----------|-------------| | `{{ .Component.component }}` | Component name | | `{{ .Component.vars.* }}` | Component variables | | `{{ .Component.settings.* }}` | Component settings | | `{{ .Component.env.* }}` | Environment variables | ## Real-World Examples ### Ansible Playbook Runner ```yaml commands: - name: ansible description: "Run Ansible playbooks" arguments: - name: component type: component required: true flags: - name: stack shorthand: s semantic_type: stack required: true - name: check type: bool description: "Dry-run mode" component: type: ansible base_path: components/ansible steps: - | ansible-playbook {{ .Component.vars.playbook }} \ -i {{ .Component.vars.inventory }} \ {{ if .Flags.check }}--check{{ end }} ``` ### Kubernetes Manifest Deployer ```yaml commands: - name: manifest description: "Apply Kubernetes manifests" arguments: - name: component type: component required: true flags: - name: stack shorthand: s semantic_type: stack required: true component: type: manifest steps: - | kubectl apply \ --context {{ .Component.vars.cluster_context }} \ --namespace {{ .Component.vars.namespace }} \ -f {{ .Component.vars.manifest_path }} ``` ## Comparison with component\_config | Feature | `component:` (new) | `component_config:` (legacy) | |---------|-------------------|------------------------------| | Component types | Any custom type | Terraform only | | Component/stack source | Inferred from typed args/flags | Explicit templates | | Template variable | `{{ .Component.* }}` | `{{ .ComponentConfig.* }}` | The legacy `component_config` continues to work for backward compatibility. ## Getting Started 1. Update to the latest Atmos version 2. Define a custom command with `component.type` 3. Add typed arguments/flags with `type: component` and `semantic_type: stack` 4. Create component configurations under `components.` in stack manifests 5. Access configuration via `{{ .Component.* }}` in your command steps See the [Custom Component Types documentation](/cli/configuration/commands/component#custom-component-types) for complete details. For usage and configuration, see [Using Custom Component Types](/components/custom). --- ## Custom hooks: zero-config security & cost scanners Atmos hooks now have a **`kind`** system — the same `before.terraform.plan` / `after.terraform.plan` lifecycle you already know, but the dispatch is pluggable and built-in kinds ship for common tools. Two lines in a stack manifest gets you cost analysis from infracost, or SARIF scanning from checkov, trivy, or kics, with tools auto-installed via the Atmos toolchain. ```yaml components: terraform: vpc: dependencies: tools: checkov: "3.2.529" hooks: security: events: [after.terraform.plan] kind: checkov ``` That's the whole config. No scanner binary on PATH, no custom command wrapper, no GitHub Actions glue — [`atmos terraform plan vpc -s prod`](/cli/commands/terraform/plan) auto-installs checkov via the toolchain, runs it against the component, parses the SARIF, renders the findings as a markdown table in your terminal, and (when Atmos Pro is connected) ships the same body to the run page. ## What's new ### Built-in kinds for the common cases ```yaml hooks: cost: { events: [after.terraform.plan], kind: infracost } security: { events: [after.terraform.plan], kind: trivy } ``` Built-in kinds ship in this release: - **`command`** — generic engine. Runs any binary with `ATMOS_*` env vars so your custom tool plugs in without writing Go. - **`infracost`** — cost diff card with per-resource breakdown. - **`checkov`** / **`trivy`** / **`kics`** — SARIF findings viewers sharing one parser. New SARIF-emitting tools slot in trivially. Each renders a **single markdown body** that shows up identically in the terminal and on the Pro run page. Same bytes, every surface. ### Override any default The defaults are good for the common case, but every field on a built-in kind — `command`, `args`, `env`, `on_failure` — is overridable. Set the field on your hook and your value wins. Useful when you want to tighten the severity threshold, point at a custom config, or make a finding block the run: ```yaml hooks: security: events: [after.terraform.plan] kind: trivy on_failure: fail # default is warn — fail the run on a finding args: # full replacement, not merge - config - --format - sarif - --output - $ATMOS_OUTPUT_FILE - --severity - HIGH,CRITICAL # added on top of the default args - --quiet - $ATMOS_COMPONENT_PATH ``` `args` and `env` are full replacement, not merge — when you override `args` you restate every arg you want. If you only need to tweak one flag, copy the kind's default arg list from the [docs](/stacks/hooks#overriding-kind-defaults) and add your own. ### Bring your own command If Atmos doesn't ship a named kind for your tool yet, use `kind: command` and point it at any binary or script. Atmos injects the same `ATMOS_*` environment variables built-in kinds use, including `$ATMOS_COMPONENT_PATH`, `$ATMOS_STACK`, `$ATMOS_COMPONENT`, `$ATMOS_OUTPUT_FILE`, and `$ATMOS_OUTPUT_DIR`. ```yaml components: terraform: demo: hooks: notify: events: [after.terraform.plan] kind: command command: python3 args: - scripts/notify.py format: markdown on_failure: warn ``` The script can stream progress to stdout/stderr in real time. If it writes markdown to `$ATMOS_OUTPUT_FILE`, Atmos renders that body in the terminal just like the built-in scanner summaries. ### `dependencies.tools` triggers auto-install Declare the tools your hooks need under `dependencies.tools` on the component (same surface ansible components and workflows already use) and Atmos installs them automatically before the hook runs: ```yaml components: terraform: vpc: dependencies: tools: infracost: "0.10.44" trivy: "0.70.0" hooks: cost: { events: [after.terraform.plan], kind: infracost } security: { events: [after.terraform.plan], kind: trivy } ``` A **pre-flight check** runs once per [`atmos terraform …`](/cli/commands/terraform/usage) invocation — it resolves and installs every component dependency, then verifies each hook's binary is on the resulting PATH. If something is missing or the declared tool can't be found, you find out **before terraform runs**, not after a 90-second plan. The error includes a hint pointing you at the relevant config: ``` Error: command not found Hook "security" (kind trivy) requires "trivy", which is not installed and not on PATH 💡 Declare it in dependencies.tools (e.g. `trivy: ""`) to auto-install before terraform runs 💡 Or install it manually so it appears on PATH ``` ### `--skip-hooks` runtime escape hatch Sometimes you just want plan/apply to run without the scan: ```bash atmos terraform plan vpc -s prod --skip-hooks # skip all atmos terraform plan vpc -s prod --skip-hooks=cost,security # skip specific ATMOS_SKIP_HOOKS=true atmos terraform apply vpc -s prod # env form ``` Skipped hooks are logged at `INFO` so it's visible in CI output. Per invocation only — doesn't propagate to nested commands or workflows. ### Workdir compatible When the `component-workdir` feature is enabled, hooks scan the same directory terraform actually runs in (the provisioned workdir), not the in-repo source path. `tool` and `terraform` see identical state. ## Examples Four scanner/cost examples live under `examples/`: - [`hooks-infracost`](/examples/hooks-infracost) — cost analysis (`kind: infracost`) - [`hooks-trivy`](/examples/hooks-trivy) — security scanning (`kind: trivy`) - [`hooks-checkov`](/examples/hooks-checkov) — policy scanning (`kind: checkov`) - [`hooks-kics`](/examples/hooks-kics) — IaC scanning (`kind: kics`) Each uses dummy AWS provider config so `tofu plan` succeeds offline — no real credentials needed to see the hooks fire. ## What's next This is the first cut. On deck: - **Atmos Pro upload** — the engine already produces a typed `Summary` envelope and an `Artifact` blob per hook invocation. The Pro backend picks them up when a Pro instance is connected. This feature is **experimental** — the YAML shape is stable for v1 but may grow new fields as we add Pro integration. Pin your Atmos version if you don't want to track changes. ## Try it ```bash git clone https://github.com/cloudposse/atmos cd atmos/examples/hooks-trivy atmos terraform plan bucket -s test ``` You'll see trivy auto-install via the toolchain, scan the intentionally- misconfigured S3 bucket, and render the findings in your terminal — all without any cloud credentials. **Read the docs** Full reference for hook kinds, override semantics, lifecycle events, tool auto-install, and the `--all` / `--skip-hooks` flags. --- ## Custom Secrets Masking Patterns Provably safe secrets masking with custom patterns, comprehensive output coverage, and configurable replacement strings. ## Why This Matters Safe secrets management requires provably complete masking - if even one output channel bypasses the masking layer, secrets can leak. This release establishes the foundation for comprehensive secrets management by: 1. **Ensuring all output channels route through masking** - terraform output, shell commands, logs, auth commands, help text, and error messages 2. **Enabling custom patterns** - extend built-in patterns with organization-specific formats 3. **Providing configurable replacement** - customize masked output for compliance requirements ## What Changed ### Comprehensive Output Coverage All CLI output now routes through the masking layer: - Terraform/Helmfile command output (stdout/stderr) - Shell command execution - Logger output - Auth command displays - Documentation rendering - Error messages and help text ### Custom Pattern Configuration Define patterns in `atmos.yaml` to mask organization-specific secrets: ```yaml settings: terminal: mask: enabled: true replacement: "[REDACTED]" patterns: - 'demo-key-[A-Za-z0-9]{16}' - 'internal-[a-f0-9]{32}' - 'tkn_(live|test)_[a-zA-Z0-9]{24}' literals: - "super-secret-demo-value" - "my-api-key-12345" ``` ### Built-in Protection Atmos includes 120+ patterns from the Gitleaks library covering: - AWS keys and session tokens - GitHub/GitLab tokens - API keys and passwords - JWT tokens and private keys ## Try It Out See a masked `terraform plan` in action: [View the full example](/examples/secrets-masking) See the [secrets-masking example](https://github.com/cloudposse/atmos/tree/main/examples/secrets-masking) for a complete demo. For usage and configuration, see [Secret Masking Configuration](/cli/configuration/settings/mask). ## Get Involved - [Open an issue](https://github.com/cloudposse/atmos/issues/new) for feature requests - Join our [Slack community](https://slack.cloudposse.com/) for discussions --- ## Customize List Command Output to Explore Your Cloud Architecture Atmos lets you model your cloud architecture, so why shouldn't you be able to easily explore that? This is especially a pain point for people new to a team who just want to see what exists without having to understand your complete cloud architecture. Atmos List makes that possible. We've enhanced all column-supporting list commands (`instances`, `components`, `stacks`, `workflows`, `vendor`) to support customizable output columns via `atmos.yaml` configuration. ## The Problem When exploring a new infrastructure codebase, you're often overwhelmed with questions: - What components are deployed in production? - Which stacks use a specific component? - What's the region and environment for each deployment? Running [`atmos list instances`](/cli/commands/list/list-instances) gives you raw data, but not the specific view you need to answer these questions quickly. ## The Solution Configure custom columns in `atmos.yaml` to show exactly what your team needs: ```yaml # atmos.yaml components: list: columns: - name: Stack value: "{{ .stack }}" - name: Component value: "{{ .component }}" - name: Region value: "{{ .vars.region }}" - name: Environment value: "{{ .vars.environment }}" - name: Stage value: "{{ .vars.stage }}" - name: Description value: "{{ .metadata.description }}" ``` Now `atmos list instances` shows a clean, team-specific view: ```shell atmos list instances ┌─────────────────────┬───────────┬───────────┬─────────────┬──────┬────────────────────────┐ │ Stack │ Component │ Region │ Environment │ Stage│ Description │ ├─────────────────────┼───────────┼───────────┼─────────────┼──────┼────────────────────────┤ │ plat-ue2-prod │ vpc │ us-east-2 │ ue2 │ prod │ Production VPC │ │ plat-ue2-prod │ eks │ us-east-2 │ ue2 │ prod │ Production EKS cluster │ │ plat-uw2-staging │ vpc │ us-west-2 │ uw2 │ stage│ Staging VPC │ └─────────────────────┴───────────┴───────────┴─────────────┴──────┴────────────────────────┘ ``` ## Practical Examples ### Find All Production Infrastructure Filter by stack pattern and see critical details: ```shell atmos list instances --stack "*-prod" --columns "component,vars.region,enabled" ``` ### Explore Vendored Dependencies See what external components you're using: ```yaml # atmos.yaml vendor: list: columns: - name: Component value: "{{ .component }}" - name: Source value: "{{ .source | truncate 50 }}" - name: Version value: "{{ .version }}" ``` ```shell atmos list vendor ┌───────────┬──────────────────────────────────────────────────┬─────────┐ │ Component │ Source │ Version │ ├───────────┼──────────────────────────────────────────────────┼─────────┤ │ vpc │ github.com/cloudposse/terraform-aws-vpc │ 1.5.0 │ │ eks │ github.com/cloudposse/terraform-aws-eks │ 2.0.0 │ └───────────┴──────────────────────────────────────────────────┴─────────┘ ``` ### Audit Workflows See all available automation: ```yaml # atmos.yaml workflows: list: columns: - name: Workflow value: "{{ .name }}" - name: File value: "{{ .file }}" - name: Steps value: "{{ .steps | len }} steps" ``` ### Use Template Functions Transform data with built-in functions: ```yaml components: list: columns: - name: Component value: "{{ .component | upper }}" - name: Status value: "{{ if .enabled }}✓ Enabled{{ else }}✗ Disabled{{ end }}" - name: Short Description value: "{{ .metadata.description | truncate 40 }}" ``` ## Override from CLI Need a different view for a one-off query? Override columns via CLI: ```shell # Quick component-stack view atmos list instances --columns stack,component # Region-specific query atmos list instances --columns "component,vars.region,vars.account_id" ``` ## What's Supported Column customization is available for: - ✅ `atmos list instances` - All component instances across stacks - ✅ [`atmos list components`](/cli/commands/list/components) - Components in your project - ✅ [`atmos list stacks`](/cli/commands/list/stacks) - Stack configurations - ✅ [`atmos list workflows`](/cli/commands/list/list-workflows) - Available workflows - ✅ [`atmos list vendor`](/cli/commands/list/list-vendor) - Vendored dependencies Each command has access to its own template context with fields like `.stack`, `.component`, `.vars.*`, `.settings.*`, `.metadata.*`, and more. ## Learn More - [List Instances Documentation](/cli/commands/list/list-instances) - [List Components Documentation](/cli/commands/list/components) - [List Workflows Documentation](/cli/commands/list/list-workflows) - [List Vendor Documentation](/cli/commands/list/list-vendor) Make exploring your cloud architecture as easy as modeling it. Configure your columns once, and every team member gets the view they need. For usage and configuration, see [List Command Configuration](/cli/configuration/list). --- ## Declarative File Generation for Terraform Components Atmos now supports declarative file generation for Terraform components via the new `generate` section in stack configuration. See it in action: [View the full example](/examples/generate-files) ## What Changed A new `generate` section can be defined at multiple levels to declaratively specify files that should be generated alongside your Terraform components. This extends Atmos's existing pattern of generating backend configuration files to support arbitrary auxiliary files. ```yaml # Level 1: Global level (applies to all components) generate: "global-context.json": level: "global" # Level 2: Component type level (applies to all terraform components) terraform: generate: "terraform-context.json": level: "terraform-type" # Level 3-4: Base component and component levels components: terraform: vpc: vars: environment: prod generate: # Map values are serialized based on file extension locals.tf: locals: environment: "{{ .vars.environment }}" # String values are treated as Go templates README.md: | # VPC Component Environment: {{ .vars.environment }} # Level 5: Overrides level (highest priority, file-scoped) terraform: overrides: generate: "override-context.json": level: "overrides" ``` ### Key Features - **Extension-aware serialization**: `.json`, `.yaml`, `.yml` files are serialized in their respective formats; `.tf` and `.hcl` files generate valid HCL - **Go template support**: String values are processed as Go templates with full access to component context - **5-level inheritance**: The `generate` section follows Atmos's standard inheritance model with merge from lowest to highest priority: 1. **Global level** (`generate:` at stack root) 2. **Component type level** (`terraform.generate:`) 3. **Base component level** (via [`metadata.inherits`](/stacks/components/component-metadata#inherits)) 4. **Component level** (`components.terraform.vpc.generate`) 5. **Overrides level** (`terraform.overrides.generate:`) - file-scoped, highest priority - **CLI integration**: New [`atmos terraform generate files`](/cli/commands/terraform/generate/files) command with `--all`, `--dry-run`, and `--clean` flags - **Auto-generation**: Enable `auto_generate_files: true` in `atmos.yaml` to automatically generate files during terraform commands - **Clean integration**: Generated files are automatically cleaned up by [`atmos terraform clean`](/cli/commands/terraform/clean) ## Why This Matters Teams often need to generate auxiliary configuration files that accompany their Terraform components—files like `.tool-versions`, `terragrunt.hcl` shims for gradual migration, or environment-specific locals. Previously, this required external tooling or manual maintenance. The `generate` section brings this capability directly into Atmos's declarative configuration model, maintaining the principle that your infrastructure configuration should be fully described in YAML and reproducible from stack manifests. ## How to Use It ### Single Component ```bash atmos terraform generate files vpc -s prod-ue2 ``` ### All Components ```bash atmos terraform generate files --all ``` ### Preview Changes ```bash atmos terraform generate files --all --dry-run ``` ### Clean Generated Files ```bash atmos terraform generate files --all --clean ``` ### Automatic Generation Add to `atmos.yaml`: ```yaml components: terraform: auto_generate_files: true ``` ## Get Involved - Review the [generate files section documentation](/cli/commands/terraform/generate/files) for detailed configuration options - Share feedback or report issues on [GitHub](https://github.com/cloudposse/atmos/issues) --- ## Deferred YAML Function Evaluation in Merge We've improved how Atmos handles YAML functions during merges across configuration layers. Atmos now postpones merging YAML functions until after the regular merge is done. This avoids the type conflicts that used to happen when a stack layer replaced a plain value—like a string, map, or list—with a YAML function such as a template or an output reference. ## The Problem: Type Conflicts During Merge YAML functions (like [`!template`](/functions/yaml/template), [`!terraform.output`](/functions/yaml/terraform.output), [`!store.get`](/functions/yaml/store.get), and others) are represented as strings during configuration loading, but they resolve to different types after evaluation. When Atmos tried to merge a concrete value (like a string `"10.0.0.0/16"`) with a YAML function string (like `!template '{{ .settings.vpc_cidr }}'`), it encountered type conflicts. The standard merge process couldn't handle merging these different representations across nested stack layers. Consider this common scenario across multiple stack files: **Base catalog (`catalog/vpc/defaults.yaml`):** ```yaml components: terraform: vpc: vars: cidr_block: "10.0.0.0/16" enable_dns: true ``` **Environment-specific override (`stacks/prod/networking.yaml`):** ```yaml components: terraform: vpc: vars: cidr_block: !template '{{ .settings.vpc_cidr }}' # Template function availability_zones: 3 ``` When Atmos processed these files, it would: 1. Load the base configuration with `cidr_block` as a string 2. Try to merge with the override where `cidr_block` is a template function (different type) 3. Encounter a type conflict: string vs. template function The fundamental issue: **YAML functions were being processed before merging**, creating type mismatches that broke the merge operation. ### Why This Matters This problem appeared in several real-world scenarios: - **Multi-environment deployments** where production uses templates for dynamic values while dev uses static strings - **Team-specific configurations** where some teams use `!store.get` for secrets while others hardcode values - **Gradual migrations** from static to templated configurations - **Mixed configuration sources** combining vendored components (static) with custom overrides (templated) ## The Solution: Defer, Merge, Then Process The new deferred merge infrastructure introduces a three-phase approach: ### Phase 1: Defer YAML Functions Before merging, Atmos walks through each configuration file and identifies YAML functions: - `!template` - Go template rendering - `!terraform.output` - Output from other components - [`!terraform.state`](/functions/yaml/terraform.state) - State file queries - `!store.get` / [`!store`](/functions/yaml/store) - Store lookups - [`!exec`](/functions/yaml/exec) - Command execution - [`!env`](/functions/yaml/env) - Environment variable expansion These functions are temporarily replaced with `nil` placeholders and stored in a **deferred merge context** with their: - Original value - Path in the configuration tree - Precedence order (which file they came from) ### Phase 2: Merge Without Conflicts With YAML functions deferred, all values are simple types (strings, numbers, maps, lists). The normal merge process completes without type conflicts. ### Phase 3: Apply Deferred Merges After the standard merge completes, Atmos processes the deferred functions: - Sorts them by precedence (based on import order - base configurations have lower precedence, overrides have higher precedence) - Merges multiple values for the same path - Applies the final merged values back to the configuration The result: YAML functions work correctly across inheritance hierarchies without type conflicts. ## Real-World Example Here's a complete example showing the deferred merge in action: **Catalog defaults:** ```yaml # catalog/database/defaults.yaml components: terraform: rds: vars: engine: postgres storage: 100 config: max_connections: !template '{{ .settings.db_connections }}' ``` **Production override:** ```yaml # stacks/prod/databases.yaml import: - catalog/database/defaults components: terraform: rds: vars: storage: !template '{{ .settings.prod_storage }}' config: max_connections: !template '{{ .settings.prod_connections }}' backup_retention: 30 ``` **Processing flow:** 1. **Deferral phase:** - Catalog: `max_connections` → deferred (precedence 0) - Override: `storage` → deferred (precedence 1) - Override: `max_connections` → deferred (precedence 1) 2. **Merge phase:** ```yaml vars: engine: postgres # Simple value, merged normally storage: nil # Deferred, no conflict config: max_connections: nil # Deferred, no conflict backup_retention: 30 # Simple value, merged normally ``` 3. **Apply deferred phase:** - `storage` has one deferred value → applied directly - `max_connections` has two deferred values → precedence 1 (override) wins - Result: All templates preserved, hierarchy respected ## List Merge Strategy Support The deferred merge system fully respects Atmos's `list_merge_strategy` setting: ### Replace (Default) ```yaml # Base tags: !template '{{ .settings.base_tags }}' # Override tags: !template '{{ .settings.override_tags }}' # Result: Override wins tags: !template '{{ .settings.override_tags }}' ``` ### Append ```yaml settings: list_merge_strategy: append # Base security_groups: !template '{{ .settings.base_sgs }}' # Override security_groups: !template '{{ .settings.additional_sgs }}' # Result: Both templates evaluated and concatenated security_groups: [sg-base1, sg-base2, sg-add1, sg-add2] ``` ### Merge ```yaml settings: list_merge_strategy: merge # Base listeners: - port: !template '{{ .settings.http_port }}' protocol: HTTP # Override listeners: - port: !template '{{ .settings.https_port }}' protocol: HTTPS # Result: Deep merge by index listeners: - port: 443 # Override wins protocol: HTTPS # Override wins ``` For technical details on the implementation, see the [Deferred YAML Function Merge Handling PRD](https://github.com/cloudposse/atmos/blob/main/docs/prd/deferred-yaml-functions-evaluation-in-merge.md). For usage and configuration, see [Type-Aware Merging of YAML Functions](/reference/yaml-function-merging). ## Get Involved This infrastructure improvement enables more flexible configuration patterns in Atmos. We'd love to hear about: - Real-world scenarios where type conflicts prevented you from using YAML functions - Performance impact on your large-scale configurations - Use cases we should prioritize for the stack processor integration Share your feedback in [GitHub Discussions](https://github.com/cloudposse/atmos/discussions) or open an issue for bugs or feature requests. ## Learn More - [Deferred YAML Function Merge Handling PRD](https://github.com/cloudposse/atmos/blob/main/docs/prd/deferred-yaml-functions-evaluation-in-merge.md) - Complete technical specification and implementation details - [Atmos YAML Functions Documentation](https://atmos.tools/functions/yaml/) - Guide to using YAML functions in stack configurations --- ## Introducing Structured Component Dependencies Atmos now supports a new `dependencies.components` format for declaring explicit component dependencies with support for cross-type dependencies, file/folder watching, and stack templates. Declare component dependencies explicitly with the new structured format that supports cross-type dependencies, file/folder watching, and dynamic stack templates. ## What Changed The new `dependencies.components` section provides a structured way to declare component dependencies: ```yaml components: terraform: app: dependencies: components: - name: vpc - name: rds stack: "{{ .vars.tenant }}-{{ .vars.environment }}-prod" files: - configs/app.json ``` ### Key Features - **Cross-type dependencies**: Terraform components can depend on Helmfile components using the `kind` field - **File/folder watching**: Trigger rebuilds when config files change with `kind: file` or `kind: folder` - **Template support**: Dynamic stack references with Go templates - **Inheritance**: Dependencies are replaced during stack inheritance by default, or appended when `list_merge_strategy: append` is configured ### Cross-Type Dependencies The `kind` field allows you to declare dependencies on components of different types: ```yaml components: terraform: app: dependencies: components: - name: vpc # terraform (default) - name: nginx-ingress kind: helmfile # helmfile component stack: platform-stack ``` ### File and Folder Dependencies Track external files and folders that affect your component: ```yaml components: terraform: lambda: dependencies: components: - name: vpc files: - configs/lambda-settings.json folders: - src/lambda/handler ``` ## Coming Soon In Q1 2026, we're adding **automatic dependency detection** from YAML functions. Dependencies will be inferred automatically from [`!terraform.output`](/functions/yaml/terraform.output) and [`!terraform.state`](/functions/yaml/terraform.state) usage—no manual configuration needed. ## Get Started See the [Component Dependencies documentation](/stacks/dependencies/components) for complete details. --- ## File and Folder Dependencies Atmos now supports `dependencies.files` and `dependencies.folders` as first-class sibling keys for declaring path-based dependencies. Use them to mark a component as affected when shared files, generated assets, schemas, Lambda source, or other external paths change. ## What Changed Previously, path-based dependencies had to be declared inside `dependencies.components` with inline `kind: file` or `kind: folder` entries. That still works for backward compatibility, but the new sibling keys make the intent clearer: ```yaml components: terraform: lambda: dependencies: components: - name: vpc files: - configs/lambda-settings.json - schemas/event.json folders: - src/lambda/handler ``` When any listed file or folder changes, [`atmos describe affected`](/cli/commands/describe/affected) includes the declaring component in its results, even if the component directory and stack manifest did not change. ## Why This Matters Component dependencies and path watch rules now live in separate lists: - `dependencies.components` declares component ordering and impact propagation. - `dependencies.files` declares individual files that affect the component. - `dependencies.folders` declares directories that affect the component. This makes stack manifests easier to scan and keeps CI/CD change detection rules close to the component they affect. ## Get Started See the [Dependencies documentation](/stacks/dependencies) and [Component Dependencies reference](/stacks/dependencies/components) for examples and migration details. For usage and configuration, see [Component Dependencies](/stacks/dependencies/components#file-and-folder-dependencies). --- ## atmos describe affected now detects providers, generate, backend changes, and more The [`atmos describe affected`](/cli/commands/describe/affected) command now compares **every** provisioned component section between refs — including `providers`, `required_providers` (provider versions), `generate`, `backend`, `auth`, and `command`. Previously, changes to these sections were silently missed. ## What Changed The `atmos describe affected` command works by deep-merging the stack configuration on both refs and comparing each component's resulting sections. The comparison ran against a hand-maintained list of section names — and that list had drifted out of sync with what Atmos actually merges into a component. As a result, a change that only touched, say, a component's `providers` or `hooks` block was **not** reported as affected, so CI pipelines built on `describe affected` could skip a component that genuinely changed. The comparison now covers the full set of provisioned sections: The evaluated sections are now `vars`, `env`, `settings`, `metadata`, `providers`, `required_providers`, `required_version`, `generate`, `backend`, `backend_type`, `remote_state_backend`, `remote_state_backend_type`, `auth`, `command`, `dependencies`, `source`, and `provision`. Scalar sections such as `backend_type`, `required_version`, and `command` are now compared too (previously only map-valued sections were). ## Why This Matters If you use `describe affected` to drive plan/apply in CI, you were exposed to false negatives: bump a provider version in `required_providers` or change a `backend` setting, and the affected component might not show up. Those changes are now detected and reported with a precise reason (`stack.providers`, `stack.required_providers`, `stack.backend`, …). A few sections are intentionally **not** evaluated because they can't change what gets provisioned: `locals` (folds into rendered `vars`/`env`), `overrides` (folds into the merged sections), `inheritance` (a derived chain), and `retry` and `hooks` (execution-time behavior — `hooks` defines commands that run before/after an operation, such as policy or cost checks, not the infrastructure itself). If you do want hook changes to count as affected, opt in via `describe.affected.sections` (see below) and they report as `stack.hooks`. ## How to Use It Nothing to configure — the broader detection is on by default. If you need to customize the evaluated set (for example, to track a custom section or to narrow the list), use the new `describe.affected.sections` setting: ```yaml title="atmos.yaml" describe: affected: # Replaces the built-in defaults. `metadata` and `settings` are always evaluated. sections: - vars - env - providers - hooks - my_custom_section ``` See the [Evaluated sections](/cli/commands/describe/affected#evaluated-sections) documentation for the full list and the [`describe.affected.sections`](/cli/configuration/describe) configuration reference. --- ## Zero-Config CI with Describe Affected Auto-Detection The [`atmos describe affected`](/cli/commands/describe/affected) command now auto-detects the base commit in CI environments, eliminating the need for verbose flag wiring in your workflows. ## What Changed We introduced the [`--base`](/cli/commands/describe/affected#flags) flag as a unified replacement for [`--ref`](/cli/commands/describe/affected#flags) and [`--sha`](/cli/commands/describe/affected#flags), and added automatic base resolution when running in CI with `ci.enabled: true`. ### Before ```yaml - name: Describe affected run: | atmos describe affected \ --ref ${{ github.event.pull_request.head.sha }} \ --sha ${{ github.event.action == 'closed' && steps.get_parent.outputs.parent_commit || github.event.pull_request.base.sha }} ``` ### After ```yaml - name: Describe affected run: atmos describe affected ``` That's it. No flags needed. Atmos reads GitHub Actions environment variables and event payloads to determine the correct base commit for each event type. ## How It Works When `ci.enabled` is `true` in your `atmos.yaml` and no explicit `--base` flag is provided, each CI provider resolves the base commit automatically: | GitHub Actions Event | Base Resolution | |---------------------|----------------| | Pull request (open/sync) | `git merge-base(HEAD, origin/)` — auto-fetches the target branch in shallow CI checkouts so it works without `fetch-depth: 0` | | Pull request (closed/merged) | `git merge-base`, falling back to `HEAD~1` for merge-commit checkouts | | Push | Previous HEAD from event payload (`event.before`) | | Force push | Parent commit (`HEAD~1`) | | Merge group | Target branch from `GITHUB_BASE_REF` | `git merge-base` is the gold standard for PR base resolution: it returns the **fork point** of the PR branch regardless of how out of date the PR is with ``. If the PR was forked at commit `B` and main has since advanced to `E`, the diff is still computed against `B` — never against `E` — so commits on main that the PR hasn't pulled in do not show up as "affected". ## The `--base` Flag The new `--base` flag replaces both `--ref` and `--sha` with a single, intuitive flag that accepts either format: ```shell atmos describe affected --base main atmos describe affected --base refs/tags/v1.16.0 atmos describe affected --base 3a5eafeab90426bd82bf5899896b28cc0bab3073 ``` The old `--ref` and `--sha` flags still work but are now deprecated. ## Provider-Agnostic Architecture Base resolution is part of the CI provider interface — each provider implements its own `ResolveBase()` method. GitHub Actions is the first implementation, with the architecture ready for GitLab CI, Jenkins, and other providers. ## Get Involved Try it out in your GitHub Actions workflows and let us know how it works. Open an issue at [github.com/cloudposse/atmos](https://github.com/cloudposse/atmos) with any feedback. --- ## Fixed: Describe Affected Now Detects Component File Changes Atmos now correctly detects component file changes when running [`atmos describe affected`](/cli/commands/describe/affected). A regression introduced in v1.195.0 caused changes to Terraform, Helmfile, or Packer component files to not be detected when `atmos.yaml` was located in a subdirectory of the git repository (e.g., when using `atmos -C path/to/project`). ## The Problem When using `atmos describe affected` to identify components impacted by code changes, the command was only detecting stack configuration changes but missing actual component file changes. For example, modifying a `main.tf` file inside a Terraform component folder would not mark that component as affected. This issue occurred because git diff returns file paths relative to the git repository root, while the path resolution was using the current working directory. This caused path mismatches in the following scenarios: - **Using `-C` flag**: Running `atmos -C path/to/project describe affected` from a parent directory - **Subdirectory projects**: When `atmos.yaml` is located in a subdirectory of the git repository - **Monorepo setups**: When multiple Atmos projects share a single git repository ## The Fix We updated the changed files indexing logic to properly resolve relative paths against the git repository root instead of the current working directory. The fix ensures that: - Relative paths from git diff are correctly resolved against the git repo root - Component folder changes are detected regardless of where Atmos is executed from - Individual file changes within component directories are properly tracked ## Example Now when you modify component files, `atmos describe affected` correctly identifies them: ```bash # Modify a component file echo "# comment" >> components/terraform/vpc/main.tf # Run describe affected atmos describe affected --ref refs/heads/main # Output now includes the vpc component [ { "component": "vpc", "stack": "plat-ue2-dev", "affected": "component" } ] ``` ## Usage Upgrade to the latest version of Atmos and run: ```bash atmos describe affected --ref refs/heads/main ``` The command now correctly detects: - Changes to component folders (Terraform, Helmfile, Packer) - Changes to individual files within components - Stack configuration changes (as before) - Vendored components configured with `source` (even without an explicit `component` field) This fix ensures your CI/CD pipelines accurately identify which components need to be deployed based on actual code changes. --- ## Describe Affected Now Detects Deleted Components and Stacks Atmos now automatically detects components and stacks that have been deleted in your current branch compared to the target branch. This enables CI/CD pipelines to trigger `terraform destroy` workflows for removed infrastructure. ## The Problem Previously, [`atmos describe affected`](/cli/commands/describe/affected) only detected components that were **modified** between two Git commits. It worked by iterating over stacks in HEAD (current branch) and comparing them to BASE (target branch). This meant: - Components removed from a stack were invisible to the affected detection - Entire stacks that were deleted went unnoticed - CI/CD pipelines had no automated way to know which resources needed `terraform destroy` - Users had to manually identify and destroy removed components, risking resource leaks ## The Solution The `describe affected` command now performs a second pass that iterates over BASE stacks to detect deletions: 1. **Deleted components**: Components that exist in BASE but not in HEAD (within the same stack) 2. **Deleted stacks**: Entire stacks that exist in BASE but not in HEAD Deleted components are marked with new fields in the output: ```json { "component": "monitoring", "component_type": "terraform", "stack": "prod-us-east-1", "affected": "deleted", "deleted": true, "deletion_type": "component" } ``` ## New Output Fields | Field | Type | Description | |-----------------|---------|--------------------------------------------------------------------| | `deleted` | boolean | `true` if the component was deleted | | `deletion_type` | string | `component` (removed from stack) or `stack` (entire stack deleted) | ## New Affected Reasons | Reason | Description | |-----------------|------------------------------------| | `deleted` | Component was removed from a stack | | `deleted.stack` | Entire stack was deleted | ## Filtering Deleted vs Modified Components Use the `--query` flag or `jq` to separate deleted components from modified ones: ```shell # Get only deleted components (for destruction) atmos describe affected --query '[.[] | select(.deleted == true)]' # Get only modified components (for apply) atmos describe affected --query '[.[] | select(.deleted != true)]' ``` ## Using with List Affected The [`atmos list affected`](/cli/commands/list/affected) command also supports deleted detection, providing a human-readable table view: ```shell # List all affected components including deleted ones atmos list affected # Filter deleted components in JSON format atmos list affected --format json | jq '[.[] | select(.deleted == true)]' # Custom columns showing deletion status atmos list affected --columns "Component={{ .component }},Stack={{ .stack }},Deleted={{ .deleted }}" ``` The `deleted` and `deletion_type` fields are available for custom column templates. ## CI/CD Integration Example Here's how to separate apply and destroy workflows in GitHub Actions: :::warning **Review deletions carefully before destroying infrastructure.** The destroy job below uses `--auto-approve` for automation purposes. In production environments, consider adding a manual approval gate or requiring PR review before executing destroy operations to prevent accidental resource deletion. ::: ```yaml jobs: detect-changes: runs-on: ubuntu-latest outputs: modified: ${{ steps.affected.outputs.modified }} deleted: ${{ steps.affected.outputs.deleted }} steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: cloudposse/github-action-setup-atmos@v2 - name: Detect affected id: affected run: | atmos describe affected --format json > affected.json # Separate modified and deleted components jq '[.[] | select(.deleted != true)]' affected.json > modified.json jq '[.[] | select(.deleted == true)]' affected.json > deleted.json echo "modified=$(cat modified.json | jq -c)" >> $GITHUB_OUTPUT echo "deleted=$(cat deleted.json | jq -c)" >> $GITHUB_OUTPUT apply: needs: detect-changes if: needs.detect-changes.outputs.modified != '[]' strategy: matrix: include: ${{ fromJson(needs.detect-changes.outputs.modified) }} steps: - uses: actions/checkout@v6 - uses: cloudposse/github-action-setup-atmos@v2 - run: atmos terraform apply ${{ matrix.component }} -s ${{ matrix.stack }} destroy: needs: detect-changes if: needs.detect-changes.outputs.deleted != '[]' environment: production # Requires manual approval strategy: matrix: include: ${{ fromJson(needs.detect-changes.outputs.deleted) }} steps: # Check out BASE branch - deleted component config only exists there - uses: actions/checkout@v6 with: ref: ${{ github.base_ref }} - uses: cloudposse/github-action-setup-atmos@v2 - run: atmos terraform destroy ${{ matrix.component }} -s ${{ matrix.stack }} --auto-approve ``` ## Edge Cases - **Abstract components** (`metadata.type: abstract`) are not reported as deleted since they are blueprints and not provisioned - **Component renames** appear as both a deletion (old name) and a new component (new name) ## Related Documentation - [Describe Affected Command](/cli/commands/describe/affected) - Full command reference - [List Affected Command](/cli/commands/list/affected) - Human-readable table view - [Native CI in GitHub Actions](/ci) - CI/CD integration - [Component Inheritance](/design-patterns/inheritance-patterns/abstract-component) - Abstract component patterns --- ## describe affected --format=matrix auto-routes to GITHUB_OUTPUT [`atmos describe affected --format=matrix`](/cli/commands/describe/affected) now writes to `$GITHUB_OUTPUT` automatically when CI is enabled, matching the behavior already shipped for [`atmos list instances --format=matrix`](/cli/commands/list/list-instances). No more `--output-file=$GITHUB_OUTPUT` boilerplate in workflow YAML. ## What Changed When `ci.enabled: true` is set in `atmos.yaml` and `$GITHUB_OUTPUT` is present in the environment (i.e. you're running on GitHub Actions), the matrix JSON is written there automatically. The explicit `--output-file=$GITHUB_OUTPUT` flag is no longer required. Before: ```yaml - id: affected run: atmos describe affected --format=matrix --output-file=$GITHUB_OUTPUT ``` After: ```yaml - id: affected run: atmos describe affected --format=matrix ``` The same goes for `atmos list instances --format=matrix`, which already had this behavior. Both commands now resolve their output destination the same way: explicit `--output-file` flag wins; otherwise, fall back to `$GITHUB_OUTPUT` when CI is enabled; otherwise, write JSON to stdout. ## Why This Matters Before this change, the two `--format=matrix` commands behaved differently — `list instances` auto-detected, `describe affected` didn't. Workflow authors had to remember which one needed the explicit flag. With this change the two commands are symmetric, the docs are simpler, and copy-pasted workflows from one command to the other don't silently break. It's also one fewer thing to type. The matrix output is the canonical pattern for fan-out workflows in GitHub Actions, and the explicit redirect was always boilerplate. ## How to Use It Set `ci.enabled: true` in your `atmos.yaml`: ```yaml ci: enabled: true ``` Then use the matrix command without the `--output-file` flag in your workflow: ```yaml jobs: affected: runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} outputs: matrix: ${{ steps.affected.outputs.matrix }} steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - id: affected run: atmos describe affected --format=matrix deploy: needs: affected if: ${{ needs.affected.outputs.matrix != '' }} runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} strategy: matrix: ${{ fromJson(needs.affected.outputs.matrix) }} fail-fast: false steps: - uses: actions/checkout@v6 - env: COMPONENT: ${{ matrix.component }} STACK: ${{ matrix.stack }} run: atmos terraform deploy "$COMPONENT" -s "$STACK" ``` The explicit `--output-file` flag still works if you need it (writing to an arbitrary path, non-GitHub CI providers, etc.) — explicit always wins over auto-detection. ## Get Involved See the full CI workflow patterns at [Native CI](/ci), including the new "Deploy Affected" and "Deploy All" examples. The two reference repos — [`cloudposse-examples/atmos-native-ci`](https://github.com/cloudposse-examples/atmos-native-ci) and [`atmos-native-ci-advanced`](https://github.com/cloudposse-examples/atmos-native-ci-advanced) — show end-to-end working pipelines you can clone and adapt. --- ## Fixed: Describe Affected Now Detects Vendored Component Changes Atmos now correctly detects file changes in components that use the `source` attribute for [just-in-time vendoring](https://atmos.tools/cli/commands/terraform/source/). Previously, components vendored from remote sources were not properly tracked for affected detection. ## The Problem When using [Atmos vendoring](https://atmos.tools/vendor/) with the `source` attribute to pull components from remote repositories, the [`atmos describe affected`](/cli/commands/describe/affected) command was not detecting changes to those component files. This occurred because the affected detection logic required an explicit `component` field to determine which folder to monitor for changes. Components configured like this were not being tracked: ```yaml components: terraform: vpc-remote: source: uri: "github.com/cloudposse/terraform-aws-vpc//." version: "4.0.0" vars: enabled: true ``` ## The Fix The affected detection now defaults to using the component name (the YAML key, e.g., `vpc-remote`) as the component folder path when no explicit `component` field is specified. This ensures that: - Components using [`source`](https://atmos.tools/vendor/config/sources/) for vendoring are properly tracked - Components without explicit `component` field inheritance are detected - All component types (Terraform, Helmfile, Packer) benefit from this fix ## What About Workdir? If you use [`provision.workdir`](/cli/commands/terraform/workdir) with your vendored components, the detection still works correctly. The `describe affected` command tracks changes in the **source** files (`components/terraform//`), not the runtime workdir (`.workdir/`). Since `.workdir/` is in `.gitignore` and created at runtime, it doesn't affect change detection. ## Example Now when you modify vendored component files, `atmos describe affected` correctly identifies them: ```yaml # Stack configuration with source vendoring components: terraform: vpc-production: source: uri: "github.com/cloudposse/terraform-aws-vpc//." version: "4.0.0" included_paths: - "**/*.tf" vars: environment: "production" ``` After running [`atmos vendor pull`](https://atmos.tools/cli/commands/vendor/pull/) and making changes: ```bash # Modify a vendored component file echo "# update" >> components/terraform/vpc-production/main.tf # Run describe affected atmos describe affected --ref refs/heads/main # Output now includes the vpc-production component [ { "component": "vpc-production", "stack": "prod-us-east-1", "affected": "component" } ] ``` ## Related Documentation - [Vendoring Overview](https://atmos.tools/vendor/) - Learn about Atmos vendoring capabilities - [Source Configuration](https://atmos.tools/vendor/config/sources/) - Configure source URIs and protocols - [Just-in-Time Vendoring](https://atmos.tools/cli/commands/terraform/source/) - Inline source declarations - [Vendor Pull Command](https://atmos.tools/cli/commands/vendor/pull/) - Download vendored components --- ## Identity Flag Support for Describe Commands The [`atmos describe`](/cli/commands/describe/usage) family of commands now supports the [`--identity`](/cli/commands/describe/component#flags) flag, enabling runtime authentication when processing YAML template functions that access remote resources. This ensures that [`!terraform.state`](/functions/yaml/terraform.state) and [`!terraform.output`](/functions/yaml/terraform.output) functions work seamlessly without relying on ambient credentials. ## What Changed All `atmos describe` subcommands now accept the `--identity` flag for runtime authentication: - [`atmos describe stacks --identity `](/cli/commands/describe/stacks) - [`atmos describe component -s --identity `](/cli/commands/describe/component) - [`atmos describe affected --identity `](/cli/commands/describe/affected) - [`atmos describe dependents -s --identity `](/cli/commands/describe/dependents) This brings feature parity with [`atmos terraform`](/cli/commands/terraform/usage) and [`atmos workflow`](/cli/commands/workflow) commands, which already support identity-based authentication. ## The Problem We Solved By default, all `atmos describe` commands execute YAML template functions (`!terraform.state`, `!terraform.output`) and Go templates during stack processing. When these functions access remote Terraform state backends (S3, Azure Blob, GCS), they require authenticated cloud provider credentials. **Before this change**, users had to: 1. Manually run [`atmos auth login --identity `](/cli/commands/auth/login) before describe commands 2. Rely on ambient AWS credentials (environment variables, `~/.aws/credentials`) 3. Use EC2 instance profiles (not applicable for local development) **The failure scenario looked like this:** ```bash # Stack configuration contains YAML function # components: # terraform: # app: # vars: # vpc_id: !terraform.output vpc.vpc_id # Command fails with timeout $ atmos describe component app -s prod Error: context deadline exceeded (accessing S3 without credentials) ``` ## How It Works The `--identity` flag is now available on all describe commands via the parent `atmos describe` command. When specified, Atmos: 1. **Authenticates** using the specified identity before processing stacks 2. **Populates AuthContext** with cloud provider credentials 3. **Propagates credentials** to YAML function processors 4. **Executes template functions** with proper authentication The flag supports two modes: **Explicit identity:** ```bash atmos describe stacks --identity my-aws-identity ``` **Interactive selection:** ```bash atmos describe stacks --identity # Shows interactive selector to choose from configured identities ``` ## Examples ### Basic Usage ```bash # Describe stacks with specific identity atmos describe stacks --identity my-aws-identity # Describe component with identity (shorthand) atmos describe component vpc -s prod -i dev-admin # Describe affected components with authentication atmos describe affected --ref main --identity prod-readonly # Describe dependents with identity atmos describe dependents vpc -s prod --identity my-aws-identity ``` ### Interactive Selection ```bash # Use --identity without a value for interactive selection $ atmos describe stacks --identity # Atmos shows selector: > dev-admin prod-readonly staging-deploy ``` ### Combining with Other Flags ```bash # Authenticate and filter results atmos describe stacks --identity my-aws-identity --stack prod-use1 --format yaml # Authenticate and query specific values atmos describe component vpc -s prod -i dev-admin --query .vars.cidr_block # Authenticate when describing affected components atmos describe affected --identity prod-readonly --include-dependents --format json ``` ## Disabling YAML Functions If you want to see stack configurations **before** YAML functions are processed (without authentication), use the processing flags: ```bash # Disable YAML function processing atmos describe stacks --process-functions=false # Disable Go template processing atmos describe stacks --process-templates=false # Disable both atmos describe stacks --process-functions=false --process-templates=false ``` ## Use Cases ### 1. Multi-Account Development When working across multiple AWS accounts, authenticate with the appropriate identity: ```bash # Development environment atmos describe component app -s dev-use1 --identity dev-admin # Production environment (read-only) atmos describe component app -s prod-use1 --identity prod-readonly ``` ### 2. CI/CD Pipelines Authenticate in CI/CD before describing affected components: ```bash # In GitHub Actions, GitLab CI, etc. atmos describe affected \ --identity "$ATMOS_IDENTITY" \ --ref "$BASE_BRANCH" \ --format json > affected.json ``` ### 3. Debugging State Access When YAML functions access remote state, authenticate to avoid timeouts: ```bash # Component references outputs from another component # vars: # vpc_id: !terraform.output vpc.vpc_id # Describe with authentication atmos describe component app -s prod --identity my-aws-identity ``` ### 4. Stack Introspection Describe entire stacks with authentication for complete configuration: ```bash # Get all stack configurations with resolved YAML functions atmos describe stacks --identity my-aws-identity --format yaml > stacks.yaml ``` ## Migration Guide **No migration required!** This is a backward-compatible enhancement: ✅ **Existing workflows continue to work:** - Commands without `--identity` use ambient credentials (environment variables, AWS profiles) - Default identity configuration is respected - CI/CD pipelines are unaffected ✅ **Opt-in when needed:** - Add `--identity` flag when YAML functions require specific credentials - Use interactive selection for convenience during local development - Specify explicit identities in automation scripts ## Important Notes ### Default Behavior **By default, all `atmos describe` commands execute:** - ✅ YAML template functions (`!terraform.state`, `!terraform.output`, etc.) - ✅ Go templates (Gomplate functions, Atmos functions) **To disable processing:** - Use `--process-functions=false` to skip YAML functions - Use `--process-templates=false` to skip Go templates ### Error Handling **When authentication is not configured:** If your `atmos.yaml` does not have an `auth` section or has no identities configured, the behavior depends on whether you use the `--identity` flag: ✅ **Without `--identity` flag** - Commands work normally using ambient credentials (environment variables, AWS profiles, instance metadata) ```bash # Works when auth not configured - uses ambient credentials atmos describe stacks ``` ❌ **With `--identity` flag** - Commands fail with a clear error message ```bash # Fails when auth not configured but --identity provided atmos describe stacks --identity my-identity # Error: authentication not configured in atmos.yaml # the --identity flag requires authentication to be configured in atmos.yaml with at least one identity ``` This prevents confusing authentication failures and guides you to configure the `auth` section before using identity-based authentication. ### Authentication Flow When you specify `--identity`: 1. **Identity lookup** - Atmos finds the identity configuration in `atmos.yaml` 2. **Authentication** - Authenticates using the provider's method (AWS SSO, OIDC, etc.) 3. **Credential storage** - Stores temporary credentials in XDG-compliant locations 4. **Context propagation** - Makes credentials available to YAML functions 5. **Stack processing** - Executes describe operations with authenticated access ### CI/CD Considerations **For CI/CD pipelines**, we recommend: - Set `ATMOS_IDENTITY` environment variable instead of using `--identity` flag - Use explicit identity names (not interactive selection) - Ensure identity has appropriate permissions (read-only for describe operations) ```bash # Good: CI/CD with explicit identity export ATMOS_IDENTITY=ci-readonly atmos describe affected --ref main # Better: Use flag for clarity atmos describe affected --identity ci-readonly --ref main ``` ## Technical Details ### Implementation The `--identity` flag is implemented as a **PersistentFlag** on the parent `atmos describe` command, which means it automatically inherits to all subcommands: ```bash # These are equivalent: atmos describe stacks --identity my-aws-identity atmos describe --identity my-aws-identity stacks ``` ### AuthManager Propagation When `--identity` is specified: 1. Command layer creates `AuthManager` with identity configuration 2. Authenticates and populates `AuthContext` with credentials 3. Passes `AuthManager` to execution functions 4. Execution functions propagate `AuthContext` to `ConfigAndStacksInfo` 5. YAML function processors access credentials from context This ensures that `!terraform.state` and `!terraform.output` functions can access remote backends with proper authentication. ### Performance Authentication adds minimal overhead: - **First run**: ~2-3 seconds for AWS SSO authentication (with browser) - **Subsequent runs**: `<100ms` (uses cached credentials) - **Credential refresh**: Automatic when credentials expire ## Related Documentation - [Authentication Overview](/cli/commands/auth/usage) - [`atmos describe stacks` command](/cli/commands/describe/stacks) - [`atmos describe component` command](/cli/commands/describe/component) - [`atmos describe affected` command](/cli/commands/describe/affected) - [`atmos describe dependents` command](/cli/commands/describe/dependents) - [YAML Functions](/functions/yaml) ## Get Started Try it out with your existing Atmos configuration: ```bash # Interactive identity selection atmos describe stacks --identity # Specific identity atmos describe component -s --identity # In CI/CD atmos describe affected --identity $ATMOS_IDENTITY --ref main ``` Questions or feedback? Open an issue on [GitHub](https://github.com/cloudposse/atmos) or join our [community Slack](https://slack.cloudposse.com). --- ## Structured Diagnostics for Agentic Troubleshooting Human logs are useful while you are watching a command run, but they are not enough when you need to diagnose subprocess execution, CI failures, or agent runs after the fact. ## The Problem When a run fails, the important question is usually not "what did the terminal print?" It is "what command ran, with which arguments, from which directory, for how long, and how did it exit?" Humans can sometimes reconstruct that from logs. Agents and support tooling should not have to scrape terminal prose to find the root cause. ## The Change Atmos now supports an opt-in [diagnostics](/cli/configuration/diagnostics) stream: machine-readable JSONL events written to a file. ```yaml diagnostics: enabled: true file: .atmos/diagnostics.jsonl include_output: false ``` The same settings can be controlled with environment variables: ```shell ATMOS_DIAGNOSTICS_ENABLED=true \ ATMOS_DIAGNOSTICS_FILE=.atmos/diagnostics.jsonl \ atmos terraform plan vpc -s plat-ue2-dev ``` Because the output is JSONL, it is easy to inspect with tools like `jq`: ```shell jq 'select(.type == "process.exit")' .atmos/diagnostics.jsonl ``` ## Diagnostics vs. Logging Logs are human-readable status and narrative output. They explain what Atmos is doing for someone watching the run. Diagnostics are machine-readable event records for tooling, agents, and post-run inspection. They are designed to accelerate root-cause analysis by giving agents structured facts about subprocesses, exits, durations, cancellation, and failures without requiring them to parse terminal output. `diagnostics.include_output` can include masked subprocess stdout and stderr chunks, but it is disabled by default. Diagnostic output is masked before it is written. ## Why It Matters - **Agentic troubleshooting gets faster.** Agents can reason from structured events, identify the failing step, and move from diagnosis to remediation. - **CI artifacts become more useful.** Save the JSONL file with a failed job and inspect it after the terminal session is gone. - **Logs stay for people.** Diagnostics add a tooling-oriented layer without replacing human-readable logs. ## Get Involved Enable diagnostics when you need a structured troubleshooting artifact, especially for CI and agent-driven workflows. --- ## Disable Identity Authentication with --identity=false You can now disable Atmos identity authentication by setting `--identity=false`, allowing you to use cloud provider SDK credential resolution instead. ## What Changed The `--identity` flag now accepts `false` as a value to skip Atmos authentication entirely. When disabled, Atmos falls back to standard cloud provider SDK credential resolution. ## Why This Matters CI/CD environments often use their own authentication mechanisms (like GitHub Actions OIDC). Previously, you had to use workarounds like `yq` commands to remove auth configuration or maintain separate config files. Now you can simply disable Atmos authentication when needed. ## How to Use It ```bash # Via CLI flag atmos terraform plan mycomponent --stack=dev --identity=false # Via environment variable export ATMOS_IDENTITY=false atmos terraform plan mycomponent --stack=dev ``` ## Example: GitHub Actions ```yaml - name: Configure AWS credentials via OIDC uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole aws-region: us-east-1 - name: Deploy with Atmos env: ATMOS_IDENTITY: false run: atmos terraform apply mycomponent --stack=prod ``` ## Accepted Values All of these disable authentication: `false`, `0`, `no`, `off` (case-insensitive) ## Get Involved - [GitHub Pull Request](https://github.com/cloudposse/atmos/pull/1765) - Share your feedback in the [Atmos Community Slack](https://cloudposse.com/slack) --- ## Documentation Reorganization: Learn, Reference, How-To We've reorganized the Atmos documentation to better serve both newcomers and experienced users. Here's what changed and why. ## The Problem As Atmos has grown, so has its documentation. But growth without organization leads to confusion. New users struggled to find the right starting point, while experienced users had trouble locating specific configuration references. ## Our Approach We restructured the documentation around three user journeys: ### 1. Learn Atmos (Early Learning Journey) The "Learn Atmos" and "Your First Stack" sections focus exclusively on getting started. These pages introduce concepts progressively, avoiding information overload. When you're learning, you need just enough to be productive—not every edge case. ### 2. Reference Documentation (Advanced Usage) For experienced users, we've created comprehensive reference sections that document every configuration option: **CLI Configuration (`atmos.yaml`)**: Every section of `atmos.yaml` now has its own dedicated page: - `components` (with sub-pages for terraform, helmfile, packer) - `stacks` - `logs` - `settings` (with sub-pages for terminal, mask, markdown-styling, pro) - `workflows` - `vendor` - `schemas` - And more... **Stack Configuration**: Every section of stack YAML files is documented: - `import` - `vars` - `env` - `settings` (with `depends_on`) - `metadata` - `hooks` - `overrides` - `command` - `backend` - `providers` - `auth` - `components` (with terraform, helmfile, packer) This convention—documenting every single YAML section—makes it easy to find exactly what you need. ### 3. How-To Guides We introduced a dedicated How-To Guides section for task-oriented documentation: - Service Catalogs - Inheritance - Mixins These guides answer "How do I...?" questions with practical examples. ## The Convention Going forward, our documentation follows this principle: > Every section of YAML configuration—whether in `atmos.yaml` or stack files—gets its own dedicated documentation page. This makes the docs predictable. Looking for `settings.terminal.mask`? Check the mask page under settings. Looking for `components.terraform.backend`? Check the backend page under Stack Configuration. ## What's Next We'll continue refining based on feedback. If you find gaps or have suggestions, [open an issue](https://github.com/cloudposse/atmos/issues) or join us in [Slack](https://slack.cloudposse.com). For usage and configuration, see [YAML Configuration Reference](/reference/yaml). --- ## Dotenv Files with !include Atmos now supports using dotenv files directly with the [`!include`](/functions/yaml/include) YAML function. ## Explicit Dotenv Loading You can load a dotenv file directly into the CLI `env` section: ```yaml env: !include .env ``` Use a YAML merge key when you want to include dotenv values and also define inline overrides. The `<<` key is YAML's merge-key syntax, the same YAML mechanism commonly used with anchors and aliases: ```yaml env: <<: !include .env AWS_REGION: us-east-2 ``` If `.env` contains: ```dotenv AWS_REGION=us-east-1 AWS_SDK_LOAD_CONFIG=true ``` Atmos parses the file as dotenv data and merges it into `env`. Values written directly in `atmos.yaml` win, so `AWS_REGION` resolves to `us-east-2` in the example above. You can also layer multiple dotenv files: ```yaml env: <<: - !include .env.local - !include .env AWS_REGION: us-east-2 ``` YAML merge sequence precedence is earlier item wins. In the example above, `.env.local` has higher precedence than `.env`, so `.env.local` overrides `.env`. Inline keys in `env` still override all included dotenv values. Local dotenv paths follow the existing `!include` resolution rules. Use `./` or `../` for paths relative to the YAML file containing the include; bare local paths such as `.env` follow Atmos' normal current/base-path lookup for includes. Absolute paths are honored. ## Supported Filenames Atmos parses these filenames as dotenv files when they are used with `!include`: - `.env` - `.env.local`, `.env.production`, and other `.env.*` files - `foo.env` and other files ending exactly in `.env` Use [`!include.raw`](/functions/yaml/include.raw) when you want the raw file contents instead. ## Pin a Profile Per Project Because `ATMOS_`-prefixed variables in the `env` section configure Atmos itself, you can use a dotenv file to **pin a [profile](/cli/configuration/profiles) per project**. Drop an `ATMOS_PROFILE` into `.env`, include it in your base `atmos.yaml`, and every command in the project uses that profile automatically: ```dotenv # .env ATMOS_PROFILE=dev ``` ```yaml # atmos.yaml env: !include .env ``` No `--profile` flag, no exported variable — just [`atmos terraform plan vpc -s dev`](/cli/commands/terraform/plan). Atmos promotes the `ATMOS_*` values into its own environment before it resolves the active profile. An exported `ATMOS_PROFILE` (or `--profile`) still wins, so ad-hoc overrides like `ATMOS_PROFILE=ci atmos …` keep working. This is a great way to make sure everyone on a team — and CI — runs with the same defaults for a given repository. ## No Implicit Loading Atmos does not automatically load dotenv files, and it does not load or execute `.envrc`. This keeps dotenv support explicit in `atmos.yaml` and avoids shell-execution semantics. See the [`env` configuration documentation](/cli/configuration/env) for examples. --- ## ECR Authentication Integration: Automatic Docker Login for AWS Container Registries We're introducing ECR authentication integration - automatic Docker login for AWS Elastic Container Registry as part of your Atmos authentication workflow. Configure once, authenticate everywhere. ## The Problem Teams working with AWS ECR face repetitive authentication friction: ```bash # The manual dance everyone knows too well aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com ``` This breaks down when you have: - Multiple AWS accounts with different ECR registries - Different identities for different environments - Team members who forget the exact incantation - CI/CD pipelines that need consistent authentication ## The Solution: Integrations We've introduced a new `auth.integrations` section that handles **client-only credential materializations** - services like ECR and EKS that derive credentials from your AWS identity rather than being identities themselves. ### Configuration ```yaml auth: providers: company-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://company.awsapps.com/start/ identities: dev-admin: kind: aws/permission-set via: provider: company-sso principal: name: AdministratorAccess account: dev # Integrations specify which identity they use via "via.identity" # auto_provision defaults to true, so integrations auto-trigger on identity login integrations: dev/ecr/primary: kind: aws/ecr via: identity: dev-admin spec: registry: account_id: "123456789012" region: us-east-2 dev/ecr/secondary: kind: aws/ecr via: identity: dev-admin spec: registry: account_id: "123456789012" region: us-west-2 ``` ### Automatic Login When you authenticate with an identity, all integrations that reference that identity are triggered automatically (auto\_provision defaults to true): ```bash $ atmos auth login dev-admin Authenticating with identity: dev-admin Opening browser for SSO authentication... Successfully authenticated as dev-admin ✓ ECR login: 123456789012.dkr.ecr.us-east-2.amazonaws.com (expires in 11h59m) ✓ ECR login: 123456789012.dkr.ecr.us-west-2.amazonaws.com (expires in 11h59m) ``` ### Standalone ECR Login You can also trigger ECR login independently: ```bash # Using a named integration atmos aws ecr login dev/ecr/primary # Using an identity's linked integrations (all integrations referencing that identity) atmos aws ecr login --identity dev-admin # Explicit registries (ad-hoc) atmos aws ecr login --registry 123456789012.dkr.ecr.us-east-1.amazonaws.com ``` ## Design Decisions ### Why Integrations, Not Identities? ECR login and EKS kubeconfig aren't really "identities" - they're derived credentials: | Concept | IAM User | Docker Login (ECR) | EKS kubeconfig | |-----------------------------|----------|---------------------|----------------| | Stored identity object | Yes | No | No | | Policy attachment | Yes | No | No | | Server-side lifecycle | Yes | No | No | | Client-only materialization | No | Yes | Yes | Integrations are things that **use** an identity to materialize client-side credentials. ### Zero Configuration ECR credentials are written to the standard Docker config location (`~/.docker/config.json` or `$DOCKER_CONFIG/config.json` if set). This means Docker commands work immediately after login—no additional environment variables or configuration required. The token expiration time is displayed so you know when to re-authenticate. ### Non-Blocking Errors Integration failures during [`atmos auth login`](/cli/commands/auth/login) are logged but don't block authentication. Your identity credentials succeed even if ECR login fails - you can retry the integration separately. ## Use Cases ### Multi-Account Container Workflows ```yaml auth: identities: prod-deployer: kind: aws/permission-set via: provider: company-sso principal: name: ContainerDeploy account: production # auto_provision defaults to true - integrations trigger on identity login integrations: prod/ecr: kind: aws/ecr via: identity: prod-deployer spec: registry: account_id: "111111111111" region: us-east-1 shared/ecr: kind: aws/ecr via: identity: prod-deployer spec: registry: account_id: "999999999999" region: us-east-1 ``` ### CI/CD Pipelines ```bash # Single command authenticates identity AND logs into ECR atmos auth login prod-deployer # Now docker pull/push works docker pull 111111111111.dkr.ecr.us-east-1.amazonaws.com/my-app:latest ``` ## Implementation Details ### AWS SDK v2 Compatibility The implementation uses AWS SDK for Go v2 and follows AWS best practices: - **Universal Token**: ECR authorization tokens work for any registry your credentials can access. The deprecated `RegistryIds` parameter is no longer used. - **12-Hour Expiry**: Tokens expire after 12 hours (AWS-enforced). The expiration time is displayed so you know when to re-authenticate. ### Cross-Platform Support - Uses `homedir.Dir()` for reliable home directory resolution across platforms. - Environment variables read through Viper for consistent configuration handling. - File locking via `gofrs/flock` prevents concurrent modification of Docker config. ### Error Handling All errors use sentinel errors for consistent error checking: ```go errors.Is(err, errUtils.ErrECRAuthFailed) errors.Is(err, errUtils.ErrIntegrationNotFound) ``` ## What's Next The integration pattern extends naturally to other client-only credential materializations: - **EKS**: `aws eks update-kubeconfig` integration - **CodeArtifact**: npm/pip repository authentication - **Other registries**: GCR, ACR support ## Documentation - [Auth Command Overview](/cli/commands/auth/usage) - [Authentication Configuration](/cli/configuration/auth) For usage and configuration, see [ECR Authentication](/tutorials/ecr-authentication). ## Get Involved Have feedback on the integration pattern? Want to contribute EKS support? - Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues) - Share your use cases in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) --- ## ECR Public Registry Authentication Atmos now supports authenticated pulls from `public.ecr.aws` via the new `aws/ecr-public` integration kind, eliminating Docker rate limits on public ECR images. ## What Changed Atmos already supported private ECR authentication via the `aws/ecr` integration kind. This release adds its public counterpart: `aws/ecr-public`. Unauthenticated pulls from `public.ecr.aws` are subject to rate limits that can break CI workflows, especially when pulling BuildKit, binfmt, or other commonly used images. Authenticated pulls have significantly higher (or no) rate limits. The new integration uses the `ecr-public:GetAuthorizationToken` API to obtain a bearer token, then writes credentials to your Docker config for `public.ecr.aws`. Auth is always pinned to `us-east-1`, which is the only region AWS supports for ECR Public authentication. ## How to Use It Add an `aws/ecr-public` integration to your `atmos.yaml`: ```yaml auth: integrations: ecr-public: kind: aws/ecr-public via: identity: plat-dev/terraform spec: auto_provision: true ``` No `registry` block is needed since ECR Public is always `public.ecr.aws`. With `auto_provision: true`, logging in to the linked identity automatically authenticates to ECR Public: ```bash $ atmos auth login plat-dev/terraform ✓ Authenticated as arn:aws:sts::123456789012:assumed-role/DevRole/user ✓ ECR Public login: public.ecr.aws (expires in 12h) ``` Or trigger it explicitly with ambient AWS credentials — no integration config required: ```bash $ atmos aws ecr login --public ✓ ECR Public login: public.ecr.aws (expires in 12h) ``` To use a specific identity's credentials instead of the ambient ones, pair `--public` with `--identity`: ```bash $ atmos aws ecr login --public --identity plat-dev/terraform ✓ ECR Public login: public.ecr.aws (expires in 12h) ``` Once you're logged in, pull images directly — no [`atmos auth exec`](/cli/commands/auth/exec) wrapper needed: ```bash $ docker pull public.ecr.aws/docker/library/alpine:latest ``` The login writes credentials to your Docker config (`$DOCKER_CONFIG`, or `~/.docker/config.json` by default), so any subsequent `docker pull` from `public.ecr.aws` is authenticated. Tokens last roughly 12 hours — re-run the login to refresh. ## Why This Matters Every Docker build that pulls from `public.ecr.aws` hits rate limits without authentication. This is especially painful in CI, where the `cloudposse/github-action-docker-build-push` action pulls BuildKit and binfmt images from public ECR on every run. Previously, users had to add manual `docker/login-action` steps to their workflows. Now it's a single config block. ## Get Involved Have feedback or ideas? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues) or join the conversation in our [Slack community](https://slack.cloudposse.com). --- ## EKS Kubeconfig Authentication: Native kubectl Access Without the AWS CLI Atmos now supports native EKS kubeconfig authentication through the integrations system. When you authenticate with an identity, Atmos automatically generates kubeconfig entries for linked EKS clusters, giving you seamless kubectl access without requiring the AWS CLI. ## What Changed The [`atmos auth`](/cli/commands/auth/usage) system now supports EKS integrations that automatically provision kubeconfig when you log in: - **`aws/eks` integration kind** - Configure EKS clusters as integrations linked to identities - **[`atmos aws eks token`](/cli/commands/aws/eks-token) command** - kubectl exec credential plugin for short-lived token generation - **Enhanced [`atmos aws eks update-kubeconfig`](/cli/commands/aws/eks/update-kubeconfig)** - New `--integration` and `--identity` flags for Go SDK-based kubeconfig generation - **Automatic cleanup** - Kubeconfig entries are removed when you log out ## Why This Matters Previously, accessing EKS clusters required installing the AWS CLI, running `aws eks update-kubeconfig`, and managing credentials separately. Now everything is managed through your `atmos.yaml` configuration: ```yaml auth: integrations: dev/eks: kind: aws/eks via: identity: dev-admin spec: cluster: name: dev-cluster region: us-east-2 alias: dev ``` A single [`atmos auth login dev-admin`](/cli/commands/auth/login) provisions both AWS credentials and kubeconfig entries. kubectl automatically calls `atmos aws eks token` for fresh tokens when needed. ## How to Use It 1. Add an EKS integration to your `atmos.yaml` linking a cluster to an identity 2. Run `atmos auth login ` - kubeconfig is auto-provisioned 3. Use `kubectl --context get pods` - tokens are generated automatically See the [EKS Kubeconfig Authentication Tutorial](/tutorials/eks-kubeconfig-authentication) for a complete setup guide. ## Get Involved Have feedback or questions? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Fixed: Invalid Backend Files from Empty Configuration Atmos now validates that remote backend configurations are not empty before generating backend files. This prevents invalid Terraform configurations that would fail during `terraform init`. ## The Problem When `backend_type` was set to a remote backend like `s3`, but the `backend` section was empty or missing required fields, Atmos would generate an invalid backend configuration: ```yaml # Stack configuration with empty backend components: terraform: my-component: backend_type: s3 backend: {} # Empty! ``` This produced invalid `backend.tf.json`: ```json { "terraform": { "backend": { "s3": {} } } } ``` Running `terraform init` with this configuration would fail because S3 backends require at minimum `bucket` and `key` fields. ## The Fix Atmos now validates that the `backend` section is not empty when using remote backends (`s3`, `gcs`, `azurerm`, `cloud`). If the configuration is empty, Atmos skips backend generation with a helpful warning: ``` Skipping backend generation: 'backend' section is empty but 'backend_type' requires configuration. Set 'components.terraform.auto_generate_backend_file: false' in atmos.yaml to disable. ``` The `local` backend is still allowed to have an empty configuration since Terraform supports it without any required fields. ## Example Correct configuration with required backend fields: ```yaml components: terraform: my-component: backend_type: s3 backend: bucket: "my-terraform-states" key: "terraform.tfstate" region: "us-east-1" dynamodb_table: "terraform-locks" ``` ## Upgrade Upgrade Atmos to get this validation. If you see the warning, add the required backend configuration fields for your backend type. For usage and configuration, see [State Backends](/components/terraform/backends). --- ## Enhanced Color Output Support and Code Quality Improvements This release brings powerful enhancements to color output in CI/CD environments and significant code quality improvements that make Atmos more maintainable and performant. ## Enhanced Color Output Support Color output in CI/CD pipelines and non-TTY environments is now fully supported through the `ATMOS_FORCE_COLOR` environment variable: ```bash # Force colored output in CI/CD export ATMOS_FORCE_COLOR=true atmos terraform plan myapp -s dev # Supports truthy values: 1, true, yes, on, always, 2, 3 # Supports falsy values: 0, false, no, off ``` This enhancement ensures that Atmos help text, logs, and command output render beautifully even when piped or redirected, making debugging in CI/CD environments much easier. ## Code Quality Improvements This release includes significant internal improvements: ### Reduced Cognitive Complexity - Functions with cognitive complexity >15 have been refactored - Better separation of concerns with single-responsibility functions - Improved testability across the codebase ### Enhanced Performance Tracking All public functions now include performance instrumentation: ```go defer perf.Track(atmosConfig, "package.FunctionName")() ``` This allows us to identify bottlenecks and optimize performance in future releases. ### Better Error Handling - Consistent use of static errors from `errors/errors.go` - Proper error wrapping with context - Use of `errors.Join()` for combining multiple errors. ## What's Next We're continuing to improve Atmos with: - Additional template functions for dynamic configurations - Enhanced validation and policy enforcement - Better integration with cloud provider CLIs - More comprehensive testing infrastructure ## Upgrade Notes This release maintains backward compatibility. To take advantage of the new features: 1. Update to the latest Atmos version 2. Consider using `ATMOS_FORCE_COLOR` in CI/CD pipelines for better debugging ## Contributors This release includes contributions from the Atmos team and community. Thank you to everyone who provided feedback, reported issues, and contributed code! For the complete list of changes, see the [GitHub release notes](https://github.com/cloudposse/atmos/releases). --- Have questions or feedback? Join us on [Slack](https://slack.cloudposse.com/) or open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). For usage and configuration, see [Terminal Settings](/cli/configuration/settings/terminal). --- ## !env Function Now Reads from Stack Manifest env Sections The [`!env`](/functions/yaml/env) YAML function now supports reading environment variables from `env` sections defined in your stack manifests and Atmos configuration. This makes it easy to set defaults for environment variables and reference values from your infrastructure configuration. ## What's New The `!env` function has been enhanced to check stack manifest `env` sections before falling back to OS environment variables. This gives you complete control over environment variable resolution in your stack configurations. ## How It Works The `!env` function now looks up environment variables in this order: 1. **Component `env` section** from stack manifests (new!) 2. **OS environment variables** (fallback) 3. **Default value** (if provided in the function call) This hierarchy gives you maximum flexibility: define defaults in your stack configuration, override with OS environment variables when needed, or provide fallback values inline. ## Use Cases ### Setting Environment Variable Defaults Define default values for environment variables in your stack manifests: ```yaml # stacks/catalog/myapp.yaml components: terraform: myapp: env: LOG_LEVEL: info AWS_REGION: us-east-1 ENVIRONMENT: production vars: # References the env section above log_level: !env LOG_LEVEL region: !env AWS_REGION ``` ### Referencing Other Environment Variables Use the `env` section to compose environment variables from other stack values: ```yaml # stacks/ue1-prod.yaml components: terraform: vpc: env: CLUSTER_NAME: !terraform.output eks prod cluster_name VPC_ID: !terraform.output vpc prod vpc_id vars: # Use env vars in your configuration cluster: !env CLUSTER_NAME vpc: !env VPC_ID ``` ### Providing Fallback Values Combine stack defaults with inline fallbacks: ```yaml components: terraform: app: env: LOG_LEVEL: debug # Default for this component vars: # Falls back to 'info' if not in env section or OS env log_level: !env LOG_LEVEL info # Not defined in env section, uses OS env or fallback region: !env AWS_REGION us-west-2 ``` ## Why This Matters This enhancement brings several benefits: 1. **Centralized defaults** - Define environment variable defaults once in your stack configuration 2. **Better testability** - Override values in stack manifests without changing OS environment 3. **Composition** - Reference other Atmos functions in `env` sections to build complex configurations 4. **Consistency** - Same environment variable resolution across all components in a stack 5. **Flexibility** - Override stack defaults with OS environment variables when needed ## Related Documentation - [YAML Functions Documentation](/functions/yaml) - [!env Function](/functions/yaml/env) - [Stack Configuration](/stacks) - [Stack Manifest Templating](/templates) ## Get Started Start using `env` sections in your stack manifests today to centralize your environment variable defaults and improve configuration management. No changes to existing `!env` usage required - this enhancement is fully backward compatible. For questions or feedback, join the discussion in our [GitHub Discussions](https://github.com/orgs/cloudposse/discussions). --- ## Experimental Feature Controls Atmos now provides granular control over experimental features with the new `settings.experimental` configuration option—giving teams the flexibility to explore new capabilities safely while maintaining stability in production environments. ## What's New Experimental features in Atmos are now clearly marked and controllable. The new `settings.experimental` configuration option lets you choose how Atmos handles experimental commands: - **`warn`** (default) — Show a notification when running experimental commands, then continue - **`silence`** — Run experimental commands without any notification - **`disable`** — Block experimental commands entirely - **`error`** — Show notification and exit with an error code ### CLI Visibility Experimental commands are now clearly marked in help output with an `[EXPERIMENTAL]` badge: ``` AVAILABLE COMMANDS devcontainer [command] [EXPERIMENTAL] Manage development containers terraform Execute terraform commands toolchain [command] [EXPERIMENTAL] Manage CLI tool versions ``` When you run `--help` on an experimental command, you'll see a prominent badge at the top: ``` [EXPERIMENTAL] Manage development containers with full lifecycle support. ``` ## Configuration Control experimental features in `atmos.yaml`: ```yaml settings: # Control experimental feature handling # Values: "silence", "disable", "warn" (default), "error" experimental: warn ``` Or via environment variable: ```bash export ATMOS_EXPERIMENTAL=disable ``` ## Current Experimental Features The following features are currently marked as experimental: | Feature | Command | Description | |---------|---------|-------------| | Devcontainers | [`atmos devcontainer`](/cli/commands/devcontainer) | Development container lifecycle management | | Toolchain | [`atmos toolchain`](/cli/commands/toolchain/usage) | Tool version management and installation | | Backend Provisioning | [`atmos terraform backend`](/cli/commands/terraform/terraform-backend) | Terraform state backend management | | Workdir Management | [`atmos terraform workdir`](/cli/commands/terraform/workdir) | Component working directory management | | Affected Components | [`atmos list affected`](/cli/commands/list/affected) | Identify changes for targeted CI/CD | ## Use Cases ### Development Environment For local development where you want to experiment with new features: ```yaml settings: experimental: warn # See warnings but continue working ``` ### CI/CD Pipeline For continuous integration where you want stability: ```yaml settings: experimental: disable # Block experimental commands in CI ``` ### Production Environment For production where experimental features must be explicitly enabled: ```yaml settings: experimental: error # Require explicit opt-in ``` ## Why This Matters As Atmos continues to evolve, we're adding powerful new capabilities like devcontainers, toolchain management, and advanced Terraform features. These features need real-world testing before they're considered stable. The experimental feature flag gives you: - **Clear visibility** — Know immediately when you're using an experimental feature - **Granular control** — Choose how to handle experimental features per environment - **Safe exploration** — Try new features locally while keeping CI stable - **Predictable behavior** — Same configuration works identically across environments ## Documentation For complete documentation on experimental features and configuration options, see: - [Experimental Features Configuration](/cli/configuration/settings/experimental) - [Settings Reference](/cli/configuration/settings) --- ## 3.5× Faster Deep Merge for Stack Processing Atmos stack processing is now up to **3.5× faster** for deep-merge operations — the hot path executed thousands of times per [`atmos describe component`](/cli/commands/describe/component), [`atmos terraform plan`](/cli/commands/terraform/plan), and every other command that reads stack configuration. ## What Changed Every time Atmos resolves a component — merging globals, imports, overrides, base-component settings, environment variables, vars, and backend config — it performs a series of _deep merge_ operations on `map[string]any` trees. The previous implementation called `mergo.Merge` on a **pre-copied** duplicate of every input map, paying two costs per merge step: 1. **Full deep-copy** of the source map (even keys that would never conflict with the destination). 2. **Reflection-based traversal** inside mergo to walk the copied map and assign values. The new implementation replaces this pattern with a single-pass, reflection-free **native Go merge**: - The first input is deep-copied once to create the initial accumulator. - Each subsequent input is merged directly — values are copied into the accumulator _only_ when they are stored as leaves (new keys, scalar overrides, or slice results). Shared intermediate `map[string]any` containers are recursed into without any allocation. This reduces **N full pre-copies** (one per input) down to **1 pre-copy** plus **O(changed leaves)** incremental copies for a typical N-input merge. ## Benchmark Results ``` # Micro-benchmark (5 inputs, 3 top-level keys) Before BenchmarkMerge-4 682 k iter / 5062 ns/op ← original mergo After BenchmarkMerge-4 2514 k iter / 1427 ns/op ← 3.5× faster # Production-scale (10 inputs, 25 top-level sections, nested maps + list-of-map-of-list) After BenchmarkMerge_ProductionScale-4 27K iter / 44000 ns/op / 10952 B/op / 189 allocs/op ``` The 3.5× improvement is from the 5-input micro-benchmark. The production-scale benchmark (10 inheritance layers, 25 top-level sections including nested maps, tags, providers, backend, lists, scalars, and deeply nested `node_groups` with per-group subnet lists — a list-of-map-of-list pattern common in EKS and network stacks) shows ~44 µs per full stack merge on a typical CI/CD server — well under any practical latency budget even for large configurations with many stacks. Run the production benchmark locally: ```bash go test -run=^$ -bench=BenchmarkMerge_ProductionScale -benchmem ./pkg/merge/... ``` The improvement scales with the number of inputs and the depth of the configuration tree — exactly the shapes that matter most in production stacks with multiple layers of inheritance. ## Semantic Compatibility The new implementation preserves the same merge semantics as the mergo-based code for the common cases, including all three list merge strategies (`replace`, `append`, `merge`) and the `WithSliceDeepCopy` / `WithAppendSlice` behaviours. Cross-validation tests (opt-in via `go test -tags compare_mergo ./pkg/merge/...`) verify the native implementation matches mergo for the core cases. Where behavior intentionally differs, the tests document it as a **defined contract** (see below). ### Edge case: `sliceDeepCopy` result length When `sliceDeepCopy` is active and the source list is **longer** than the destination list, the merged result keeps the overlapping merged positions and appends deep-copied source tail elements, so the result length grows to `max(len(dst), len(src))`. This matches mergo's `WithSliceDeepCopy` behavior and is cross-validated against mergo in [`merge_compare_mergo_test.go`](https://github.com/cloudposse/atmos/blob/main/pkg/merge/merge_compare_mergo_test.go) (run with `go test -tags compare_mergo ./pkg/merge/...`). See [`docs/fixes/2026-03-19-deep-merge-native-fixes.md`](https://github.com/cloudposse/atmos/blob/main/docs/fixes/2026-03-19-deep-merge-native-fixes.md) for full edge-case documentation. ### Partial mergo replacement This change replaces the hot-path deep merge in `pkg/merge/merge.go`. The `mergo` library is still used in two lower-traffic call sites: - `pkg/merge/merge_yaml_functions.go` — YAML function merge helpers - `pkg/devcontainer/config_loader.go` — devcontainer config loading Migration of these remaining sites is tracked in [issue #2242](https://github.com/cloudposse/atmos/issues/2242); the dependency will be removed once those two call sites are ported. Until then, a future CVE in mergo could still affect atmos. Follow [#2242](https://github.com/cloudposse/atmos/issues/2242) for progress. ## How to Use It No action required — the improvement is automatic from this release onward. If you notice any difference in merge results, please open an issue. For usage and configuration, see [Inherit Configurations in Atmos Stacks](/howto/inheritance). --- ## Faster Remote Stack Imports: Clone Once, Cache Across Runs Atmos now clones each remote (Git) stack-import source **once per run** instead of re-cloning it for every import that points at the same repository — and an optional `ttl` lets a warm cache (like the GitHub Actions cache) skip the clone entirely on subsequent runs. ## What Changed When stacks import their catalog from a remote repository — common in hub-and-spoke setups: ```yaml import: - "git::https://github.com/my-org/hub.git//stacks/catalog/vpc?ref=main" - "git::https://github.com/my-org/hub.git//stacks/catalog/eks?ref=main" # ...dozens more, all from the same repo ``` …Atmos previously cloned the **entire repository once for every import** and threw each clone away. A spoke with 80 imports of the hub meant ~80 clones per run — and because commands like [`atmos describe affected`](/cli/commands/describe/affected) process stacks twice (current vs. base ref), that doubled again. Now Atmos clones each unique source repository **at most once per invocation** and resolves every subdir import from that single shared clone. This is automatic — no configuration required — and a single run always sees one consistent snapshot. ## Cache Across Runs with `ttl` Within-run dedup already eliminates most of the cost, but every run still re-clones from scratch. For CI, you can now keep the clone **across runs** by setting a `ttl` on the import: ```yaml import: - path: "git::https://github.com/my-org/hub.git//stacks/catalog/vpc?ref=main" ttl: 5m # reuse the cached clone for 5 minutes across runs ``` Or set a default for all imports in `atmos.yaml`: ```yaml title="atmos.yaml" imports: ttl: 5m ``` Atmos caches sources under the XDG cache directory (`~/.cache/atmos/stack-imports/`, honoring `XDG_CACHE_HOME`). Cache that directory between CI runs and 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`, `5m`, `1h`, `7d`, or keywords like `daily`. It is **opt-in**: with no `ttl`, the source is refreshed once per run so mutable refs (e.g. `?ref=main`) always stay current. ## Why This Matters For hub-and-spoke repositories pulling a shared catalog via remote imports, this collapses change-detection time dramatically — what used to be dozens of redundant clones per run becomes one, and with a warm cache and a `ttl`, often zero. Shallow clones (`depth=1`) were already in use; the win here is **not re-cloning the same repository over and over**. ## How to Use It - **Nothing to do for the within-run win** — it's automatic. - Add `ttl` per-import (or `imports.ttl` globally) to enable cross-run cache reuse. - In CI, cache `~/.cache/atmos/stack-imports/` to let the `ttl` skip clones between runs. - Pin to an immutable `?ref=` (or a SHA) and use a longer `ttl` for maximum reuse; for mutable refs like `main`, keep `ttl` short so the catalog stays fresh. See the [imports documentation](/stacks/imports) for details. For usage and configuration, see [Imports](/cli/configuration/imports). --- ## File-Scoped Locals: Simplify Stack Configuration with Temporary Variables We're introducing **file-scoped locals** to Atmos stack configurations. Inspired by Terraform and Terragrunt, locals let you define temporary variables within a single file, reducing repetition and making your configurations more readable and maintainable. ## The Problem: Repetition in Stack Configurations Complex stack configurations often contain repeated values. You might have a naming convention that combines namespace, environment, and stage across multiple components: ```yaml # Before: Repetitive and error-prone components: terraform: vpc: vars: name: acme-prod-us-east-1-vpc tags: Environment: prod Namespace: acme eks: vars: cluster_name: acme-prod-us-east-1-eks tags: Environment: prod Namespace: acme rds: vars: identifier: acme-prod-us-east-1-rds tags: Environment: prod Namespace: acme ``` This approach has several problems: - **Repetition** - Same values copied everywhere - **Inconsistency risk** - Easy to mistype or forget to update all occurrences - **Hard to refactor** - Changing a naming convention requires updates in many places ## The Solution: File-Scoped Locals Locals let you define variables once and reference them throughout the file: ```yaml # After: Clean and DRY locals: namespace: acme environment: prod stage: us-east-1 name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}-{{ .locals.stage }}" tags: Environment: "{{ .locals.environment }}" Namespace: "{{ .locals.namespace }}" components: terraform: vpc: vars: name: "{{ .locals.name_prefix }}-vpc" tags: "{{ .locals.tags }}" eks: vars: cluster_name: "{{ .locals.name_prefix }}-eks" tags: "{{ .locals.tags }}" rds: vars: identifier: "{{ .locals.name_prefix }}-rds" tags: "{{ .locals.tags }}" ``` ## Key Features ### Locals Can Reference Other Locals Locals are resolved in dependency order using topological sorting. You can build complex values from simpler ones: ```yaml locals: namespace: acme environment: prod stage: us-east-1 # References other locals - resolved in correct order name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}" full_name: "{{ .locals.name_prefix }}-{{ .locals.stage }}" ``` ### Circular Dependency Detection Atmos automatically detects circular dependencies and provides clear error messages: ```yaml # This will error with a clear message locals: a: "{{ .locals.b }}" b: "{{ .locals.c }}" c: "{{ .locals.a }}" # Circular! ``` ```shell Error: circular dependency in locals at stacks/prod.yaml Dependency cycle detected: a → b → c → a ``` ### File-Scoped Isolation Unlike `vars`, locals do **not** inherit across file boundaries via `import`. This is intentional: ```yaml # mixins/region.yaml locals: region_prefix: "us-west-2" # Only available in this file vars: region: us-west-2 # Inherited by importing files ``` ```yaml # stacks/prod.yaml import: - mixins/region # The 'region_prefix' local is NOT available here # Only 'vars.region' is inherited locals: my_prefix: "prod" # This file's own locals ``` This keeps locals truly local, preventing unexpected interactions between files. ### Multi-Level Scopes Locals can be defined at three levels, with inner scopes inheriting from outer: 1. **Global** (stack file root) - Available throughout the file 2. **Component-type** (`terraform`, `helmfile`, `packer` sections) - Inherits from global 3. **Component-level** (inside component definitions) - Inherits from global + component-type, and from base components via [`metadata.inherits`](/stacks/components/component-metadata#inherits) ```yaml # Global locals locals: namespace: acme environment: prod terraform: # Terraform-scope locals (inherit from global) locals: backend_bucket: "{{ .locals.namespace }}-{{ .locals.environment }}-tfstate" components: terraform: vpc: vars: # Uses merged locals (global + terraform section) name: "{{ .locals.namespace }}-{{ .locals.environment }}-vpc" bucket: "{{ .locals.backend_bucket }}" ``` ## Inspecting Locals with `atmos describe locals` To see the resolved locals for any component, use the new `describe locals` command: ```bash atmos describe locals vpc -s prod-ue2 ``` ```shell components: terraform: vpc: locals: namespace: acme environment: prod stage: us-east-1 name_prefix: acme-prod full_name: acme-prod-us-east-1 backend_bucket: acme-prod-tfstate ``` ### JSON Output for Automation For scripting and automation, use JSON format: ```bash atmos describe locals vpc -s prod-ue2 --format json ``` ```json { "components": { "terraform": { "vpc": { "locals": { "namespace": "acme", "environment": "prod", "name_prefix": "acme-prod" } } } } } ``` ### Show Locals for a Stack With the `--stack` flag (required), show locals for the specified stack: ```bash atmos describe locals --stack prod-ue2 ``` ```yaml locals: namespace: acme environment: prod terraform: locals: backend_bucket: acme-prod-tfstate ``` The output is in direct stack manifest format - it can be redirected to a file and used as valid YAML. ## Why File-Scoped? You might wonder why locals don't inherit across imports like `vars` do. The design is intentional: 1. **Predictability** - You know exactly what locals are available by looking at the current file 2. **No hidden dependencies** - Locals won't mysteriously change based on import order 3. **Safer refactoring** - Renaming a local in one file won't break other files 4. **Clear separation** - Use `vars` for values that should propagate; use `locals` for file-internal convenience ## Best Practices ### Use locals for DRY configuration within a file ```yaml locals: common_tags: Team: platform CostCenter: infrastructure components: terraform: vpc: vars: tags: "{{ .locals.common_tags }}" eks: vars: tags: "{{ .locals.common_tags }}" ``` ### Build complex values from simple ones ```yaml locals: namespace: acme environment: prod region: us-east-1 # Compose complex values resource_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}-{{ .locals.region }}" s3_bucket: "{{ .locals.resource_prefix }}-artifacts" dynamodb_table: "{{ .locals.resource_prefix }}-state-lock" ``` ### Keep locals close to their usage Define locals at the appropriate scope level - don't put everything at the global level: ```yaml # Global - used everywhere locals: namespace: acme terraform: # Terraform-specific - only used by terraform components locals: state_bucket: "{{ .locals.namespace }}-tfstate" vpc_name: "{{ .locals.namespace }}-vpc" components: terraform: vpc: vars: # Uses merged locals (global + terraform section) name: "{{ .locals.vpc_name }}" bucket: "{{ .locals.state_bucket }}" ``` ## Get Started File-scoped locals are available now. Try them in your stack configurations: ```yaml locals: project: myproject env: dev vars: name: "{{ .locals.project }}-{{ .locals.env }}" ``` ## Related Features - [Stack Templates](/templates) - Go templating in stack manifests - [Configuration Provenance](/cli/commands/describe/component) - Track where values come from - [YAML Functions](/functions/yaml) - Dynamic configuration with [`!terraform.output`](/functions/yaml/terraform.output), [`!env`](/functions/yaml/env), etc. We'd love to hear how you're using locals in your configurations. Share your patterns in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) or open an issue if you encounter any problems. For usage and configuration, see [Configure Locals](/stacks/locals). --- ## Fixed: File-Scoped Locals Now Resolve Templates Correctly The file-scoped locals feature introduced in v1.203.0 now correctly resolves `{{ .locals.* }}` templates in stack configurations. Previously, locals were defined but not integrated into the template processing pipeline, causing templates to remain unresolved. ## The Problem When using file-scoped locals as documented, templates referencing locals were not being resolved: ```yaml # stacks/prod.yaml locals: namespace: acme environment: prod name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}" components: terraform: myapp: vars: name: "{{ .locals.name_prefix }}-myapp" stage: "{{ .locals.environment }}" ``` Running [`atmos describe component`](/cli/commands/describe/component) showed raw template strings instead of resolved values: ```yaml # Before fix - templates not resolved vars: name: "{{ .locals.name_prefix }}-myapp" stage: "{{ .locals.environment }}" ``` ## The Fix Atmos now correctly resolves locals before template processing. The same configuration now produces the expected output: ```yaml # After fix - templates resolved correctly vars: name: "acme-prod-myapp" stage: "prod" ``` ### What Changed 1. **Locals extraction** - Raw YAML is now parsed to extract `locals:` sections before template processing 2. **Template context** - Resolved locals are added to the template context so `{{ .locals.* }}` references work 3. **Section override tracking** - Section-specific locals (in `terraform:`, `helmfile:`, `packer:`) correctly override global locals 4. **Component-level locals** - Components can now define their own `locals:` section that inherits from base components ### New Command: `atmos describe locals` A new command has been added to help inspect and debug locals configurations: ```bash # Show locals for a specific stack (using file path) atmos describe locals --stack deploy/dev # Show locals for a specific stack (using logical stack name from atmos.yaml) atmos describe locals --stack prod-us-east-1 # Show locals available to a specific component in a stack atmos describe locals vpc -s prod # Output as JSON atmos describe locals -s dev --format json # Write to file atmos describe locals -s dev --file locals.yaml ``` **Note:** The `--stack` flag is required. Locals are file-scoped, so a specific stack must be specified. The `--stack` flag accepts either a **stack manifest file path** (e.g., `deploy/dev`) or a **logical stack name** derived from your `atmos.yaml` naming pattern (e.g., `prod-us-east-1`). Both resolve to the same underlying file, and locals are returned from that file only. #### Component-Specific Locals When specifying a component with the `--stack` flag, the command shows the merged locals that would be **available to** that component during template processing. This includes global locals, section-specific locals (for the component's type), and component-level locals (including inherited from base components): ```bash atmos describe locals vpc -s prod ``` ```yaml components: terraform: vpc: locals: namespace: acme environment: prod backend_bucket: acme-prod-tfstate ``` The output uses Atmos schema format, matching the structure of stack manifest files. #### Stack-Level Output Without a component argument, the output is in direct stack manifest format: ```yaml locals: namespace: acme environment: dev name_prefix: acme-dev terraform: locals: backend_bucket: acme-dev-tfstate ``` - **locals** - Global locals defined at root level of the stack file - **terraform/helmfile/packer** - Section-specific locals nested under `{ locals: }` (only shown if defined) The output is in direct stack manifest format - it can be redirected to a file and used as valid YAML (e.g., [`atmos describe locals -s dev --file locals.yaml`](/cli/commands/describe/locals)). ### Section-Specific Locals Locals can be defined at multiple levels, with later scopes overriding earlier ones: ```yaml # Global locals locals: namespace: "global-acme" terraform: # Terraform-scope locals override global locals: namespace: "terraform-acme" backend_bucket: "{{ .locals.namespace }}-tfstate" components: terraform: myapp: vars: # Uses terraform-scope value: "terraform-acme-tfstate" bucket: "{{ .locals.backend_bucket }}" ``` ## Features That Work All documented locals features now function correctly: ### Component-Level Locals with Inheritance ```yaml components: terraform: vpc/base: locals: vpc_type: "standard" cidr_prefix: "10.0" vpc/prod: metadata: inherits: - vpc/base locals: vpc_type: "production" # Overrides base vars: cidr: "{{ .locals.cidr_prefix }}.0.0/16" # Inherited from base ``` ### Locals Referencing Other Locals ```yaml locals: namespace: acme environment: prod # Resolved in dependency order name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}" full_name: "{{ .locals.name_prefix }}-us-east-1" ``` ### Circular Dependency Detection Atmos detects circular dependencies and logs them gracefully: ```yaml # This triggers a circular dependency warning locals: a: "{{ .locals.b }}" b: "{{ .locals.a }}" ``` ### Complex Values Maps and nested structures work as expected: ```yaml locals: common_tags: Environment: "{{ .locals.environment }}" Namespace: "{{ .locals.namespace }}" components: terraform: vpc: vars: tags: "{{ .locals.common_tags }}" ``` ## Supported Scopes Locals can be defined at three levels: ```yaml # Global locals (root level) - available throughout the file locals: namespace: acme environment: prod # Section-level locals (terraform/helmfile/packer) - inherit from global terraform: locals: backend_bucket: "{{ .locals.namespace }}-{{ .locals.environment }}-tfstate" components: terraform: vpc: # Component-level locals - inherit from global and section, plus base components locals: vpc_type: "production" vars: # Uses merged locals (global + terraform section + component) bucket: "{{ .locals.backend_bucket }}" type: "{{ .locals.vpc_type }}" ``` ### Component-Level Locals Inheritance Component-level locals support inheritance from base components via [`metadata.inherits`](/stacks/components/component-metadata#inherits) or `component` attribute, similar to how `vars` work: ```yaml components: terraform: vpc/base: metadata: type: abstract locals: vpc_type: "standard" cidr_prefix: "10.0" vpc/prod: metadata: inherits: - vpc/base locals: # Overrides base component's vpc_type vpc_type: "production" vars: # Uses inherited cidr_prefix from base, overridden vpc_type cidr: "{{ .locals.cidr_prefix }}.0.0/16" name: "{{ .locals.vpc_type }}-vpc" ``` **Full locals resolution order for a component:** ``` Global Locals → Section Locals → Base Component Locals → Component Locals ``` **Note:** File-scoped locals (global and section-level) do NOT inherit across file boundaries. Only component-level locals inherit from base components. ## Upgrade Upgrade Atmos to get this fix. No configuration changes are required. Your existing `locals:` definitions will automatically start working. :::note Reserved Context Key The `locals` key in template context is now reserved for file-scoped locals. If you previously used a `locals` key in import context (via the `context:` parameter), it will be overridden by file-scoped locals when present. This is unlikely to affect existing configurations since the `locals` feature is new. ::: :::warning Template Processing with Locals When a file defines locals, template processing is automatically enabled. If your YAML files contain non-Atmos template syntax (e.g., Helm's `{{ ... }}`), use `skip_templates_processing: true` in the import to preserve literal syntax: ```yaml import: - path: catalog/helm-values skip_templates_processing: true ``` ::: ```bash # View locals for a specific stack atmos describe locals -s prod # Verify locals are resolving correctly in component output atmos describe component myapp -s prod --format yaml ``` ## References - [GitHub Issue #1933](https://github.com/cloudposse/atmos/issues/1933) - [File-Scoped Locals Documentation](/stacks/locals) - [Original Feature Announcement](/changelog/file-scoped-locals) --- ## Atmos binaries now build with Go's FIPS 140-3 crypto module by default Federal agencies, financial institutions, and healthcare organizations are often required to run only cryptography that's been validated against [FIPS](/cli/commands/version/usage) 140, the U.S. government's standard for approved algorithms and key sizes. For a command-line tool built on a general-purpose language runtime, meeting that bar has traditionally meant compiling against a separate validated crypto library, or simply hoping the runtime's own TLS and encryption code happens to stick to approved algorithms. ## The Problem Atmos talks to a lot of TLS endpoints — cloud provider APIs, git servers, artifact registries, the Terraform module registry. Every one of those connections depends on the cryptography built into the Go runtime atmos is compiled with. Until now, nothing about that cryptography was validated or restricted to FIPS-approved algorithms; it just used whatever Go's standard library picked. Operators who needed a FIPS 140-3 claim for their toolchain had no way to get one from an official atmos release. ## The Fix Every officially released atmos binary — along with every binary built from a checkout using `atmos build` — now links Go's own FIPS 140-3 crypto module and defaults to enforcing FIPS 140-3 mode at runtime. Go standard-library TLS connections, key generation, and hashing are restricted to FIPS-approved algorithms and key sizes automatically. No flag, environment variable, or config change is required. This covers the TLS and cryptography atmos itself uses for outbound connections, and it's FIPS 140-3 _mode_ — enforced by Go's runtime — not a CMVP compliance certification for the atmos binary itself. Declarative secrets management ([`atmos secret keygen`](/cli/commands/secret/keygen) and the age-based SOPS backend, along with the sealed values atmos pushes to GitHub Actions secrets) relies on its own encryption, chosen for compatibility with those specific ecosystems, and sits outside Go's FIPS module boundary entirely — worth knowing if your compliance program needs FIPS coverage across secrets handling too. ## How to Use It Nothing to opt into — every current and future atmos release ships this way by default. To confirm it on any binary, ask atmos itself: ```shell atmos version --format=json ``` ```json { "version": "1.226.1", "os": "darwin", "arch": "arm64", "fips": true } ``` If you'd rather check without atmos installed yet — say, auditing a downloaded binary — any standard Go toolchain can tell you the same thing: ```shell go version -m ./atmos | grep -i fips ``` ``` build DefaultGODEBUG=fips140=on build GOFIPS140=latest ``` ## Get Involved Have feedback on this, or a compliance requirement it doesn't yet cover? [Open an issue](https://github.com/cloudposse/atmos/issues) or join the conversation in the [Cloud Posse community Slack](https://cloudposse.com/slack). --- ## Flag-Aware Custom Commands and Dynamic Tables Custom command steps can now read their own command-line flags through `{{ .Flags. }}`, and the [`table`](/workflows/steps/type/table) step templates its `data`, `columns`, and `title` per-step. Together they let you build flag-driven runbooks — rich, dynamic output assembled declaratively in `atmos.yaml`, no shell scripting required. ## What Changed Two things came together: 1. **Flags are now template variables.** When a custom command declares flags, each one is exposed to its steps as `{{ .Flags. }}`. Step titles, shell commands, working directories, and [`table`](/workflows/steps/type/table) data all resolve those values. 2. **`table` steps are fully templated.** A `table` step's `title`, `columns`, and every cell in `data` are rendered as Go templates, so the table content can depend on flags and other [step variables](/workflows/steps/type). ## How to Use It Here's a real example now shipping in `examples/demo-stacks` — a `platform` command group whose subcommands take a `--stack` flag and build their output from it: ```yaml commands: - name: platform description: Platform team shortcuts for stack discovery and day-two commands. commands: - name: status description: Show stacks, components, and common next actions for one stack. flags: - name: stack shorthand: s description: Target stack. required: true steps: - type: stage title: Components in {{ .Flags.stack }} - type: shell command: atmos list components -s {{ .Flags.stack }} - type: table title: Platform commands columns: - task - command data: - task: Show variables command: atmos list vars myapp -s {{ .Flags.stack }} - task: Plan affected command: atmos terraform plan --affected ``` Run it like any other command: ```shell atmos platform status -s plat-ue2-dev ``` Every `{{ .Flags.stack }}` resolves to `plat-ue2-dev`, the stage titles and table rows fill in, and the shell steps run against the right stack. [View the full example](/examples/interactive-workflows) ## Why It Matters - **Build your own shortcuts.** Wrap your team's day-two operations — discovery, planning, secret access — into named commands that adapt to their flags. - **Rich output without scripts.** Tables, staged sections, and dynamic titles are declared in YAML, not hand-rolled in bash with `printf` and `column`. - **Consistent across workflows and custom commands.** The same step library powers both, so what you learn in one applies to the other. For usage and configuration, see [flags](/cli/configuration/commands/flags). ## Get Involved See the [workflow step types](/workflows/steps/type) and the [`table` step](/workflows/steps/type/table) reference to start building. Share what you create with the [Atmos community](https://github.com/cloudposse/atmos). --- ## Flexible Keyring Backends: System, File, and Memory Storage for Credentials Atmos Auth supports flexible keyring backends, giving you control over how authentication credentials are stored. Use your **system keyring** for native OS integration, **file-based keyrings** to share credentials across OS boundaries (like between your Mac and a Docker container), or **memory keyrings** for testing. ## Why Different Keyring Types Matter Different environments have different credential storage needs: **System keyrings are great for personal workstations**, where OS-native security (macOS Keychain, Windows Credential Manager, Linux Secret Service) provides tight integration with your operating system. **File-based keyrings solve cross-boundary problems**. When you're working across OS boundaries—like developing on macOS but running commands in a Docker container, or using a Dev Container in VS Code—your system keyring isn't accessible inside the container. A file-based keyring can be mounted into the container, giving you seamless credential access across both environments. **Memory keyrings are perfect for testing and CI/CD**, where you need fast, isolated credential storage without external dependencies or security concerns. ## Flexible Keyring Backends Atmos now supports three keyring backends, selectable via configuration or environment variable: ### 1. **System Keyring** (Default) OS-native secure credential storage using your operating system's built-in keyring. This is the default and requires no configuration. ```yaml # atmos.yaml (optional - this is the default) auth: keyring: type: system ``` **Best for**: Personal workstations where OS-level security is preferred. **Features**: - ✅ OS-native secure storage - ✅ Integration with system password managers - ❌ Cannot list all stored credentials (API limitation) - ❌ May not be available in CI/headless environments ### 2. **File Keyring** Encrypted file-based storage using [99designs/keyring](https://github.com/99designs/keyring) with interactive password prompting via Charm Bracelet's `huh` library. ```yaml # atmos.yaml auth: keyring: type: file spec: path: ~/.atmos/keyring # Optional: custom path password_env: ATMOS_KEYRING_PASSWORD # Optional: env var for password ``` **Best for**: Shared environments, servers, or when you need portability across different machines. **Features**: - ✅ AES-256 encryption - ✅ Cross-platform (works anywhere) - ✅ Supports listing all stored credentials - ✅ Interactive password prompting (with `huh`) - ✅ Automation-friendly (password via environment variable) - ⚠️ Requires password management **Password Resolution**: 1. Check `ATMOS_KEYRING_PASSWORD` environment variable (or custom env var from `password_env`) 2. Prompt interactively if TTY is available (using Charm Bracelet's secure input) 3. Error if neither is available **Example Usage**: ```shell # Interactive mode - you'll be prompted for password atmos auth login --identity prod-admin # Automation mode - password from environment export ATMOS_KEYRING_PASSWORD="my-secure-password" atmos auth login --identity prod-admin ``` ### 3. **Memory Keyring** (Testing Only) In-memory credential storage with no persistence. Credentials are lost when the process exits. ```yaml # atmos.yaml (or set ATMOS_KEYRING_TYPE=memory) auth: keyring: type: memory ``` **Best for**: Unit tests, integration tests, and CI/CD pipelines where you need fast, isolated credential storage without external dependencies. **Features**: - ✅ No external dependencies - ✅ Thread-safe concurrent access - ✅ Supports listing all stored credentials - ✅ Perfect for testing - ⚠️ Not persistent (ephemeral) - ⚠️ Not encrypted (testing only!) **CI/CD Example**: ```yaml # .github/workflows/test.yml env: ATMOS_KEYRING_TYPE: memory jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Run Auth Tests run: go test ./pkg/auth/... ``` ## Configuration Priority Keyring backend selection follows this priority order: 1. **`ATMOS_KEYRING_TYPE` environment variable** (highest priority - great for testing) 2. **`auth.keyring.type` in atmos.yaml** (explicit configuration) 3. **Default to `system`** (backward compatibility) This means you can override the configured backend for testing: ```shell # Override to memory keyring for this session export ATMOS_KEYRING_TYPE=memory atmos auth login --identity test-identity ``` ## Real-World Use Cases ### Personal Development Use the default system keyring for secure, OS-integrated credential storage: ```yaml # atmos.yaml (no keyring config needed) auth: providers: my-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://mycompany.awsapps.com/start ``` Your credentials are stored in macOS Keychain, Windows Credential Manager, or Linux Secret Service. ### Docker / Dev Container Development Use file keyring to share credentials between your host machine and containers: ```yaml # atmos.yaml auth: keyring: type: file spec: path: ~/.atmos/keyring password_env: ATMOS_KEYRING_PASSWORD ``` Mount the keyring into your container: ```yaml # docker-compose.yml or devcontainer.json volumes: - ~/.atmos/keyring:/root/.atmos/keyring:ro ``` Now credentials authenticated on your Mac are available inside your Docker container. Your system keyring isn't accessible across the OS boundary, but the file-based keyring is. ### CI/CD Pipeline Use memory keyring for fast, isolated testing without external dependencies: ```yaml # .github/workflows/integration.yml env: ATMOS_KEYRING_TYPE: memory jobs: integration-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Run Integration Tests run: | # Tests use memory keyring - fast, isolated, no dependencies go test ./pkg/auth/... ``` ### Local Testing Override the configured backend for local testing: ```shell # Test with memory keyring (no persistence) ATMOS_KEYRING_TYPE=memory atmos auth login --identity test # Test with file keyring ATMOS_KEYRING_TYPE=file ATMOS_KEYRING_PASSWORD=test atmos auth login --identity test ``` ## Security Considerations ### System Keyring - Credentials protected by OS-level security - Access controlled by OS permissions - Password prompts managed by OS ### File Keyring - AES-256 encryption - File permissions: 0600 (user read/write only) - Password never logged or stored - Password required for each process (or from environment variable) ### Memory Keyring - ⚠️ **NOT for production use** - No encryption (plain text in memory) - No persistence (ephemeral) - Perfect for testing where security is not a concern ## What This Gives You - **Cross-boundary credential access**: Use file-based keyrings to share credentials between your host and Docker/Dev containers - **Flexible storage**: Choose the backend that fits your environment—system keyring for personal use, file keyring for portability, memory keyring for testing - **CI/CD friendly**: Run auth tests reliably without system keyring dependencies ## Try It Out The keyring backend system is available now. To use memory keyring for testing: ```shell export ATMOS_KEYRING_TYPE=memory atmos auth login --identity your-identity ``` To use file keyring: ```yaml # atmos.yaml auth: keyring: type: file spec: password_env: ATMOS_KEYRING_PASSWORD ``` Then set the password and authenticate: ```shell export ATMOS_KEYRING_PASSWORD="your-secure-password" atmos auth login --identity your-identity ``` For usage and configuration, see [Keyring](/cli/configuration/auth/keyring). ## Get Involved We'd love to hear your feedback! If you have questions or suggestions: - [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) - [GitHub Issues](https://github.com/cloudposse/atmos/issues) - [Cloud Posse Slack](https://cloudposse.com/slack/) This feature improves testing reliability, enables new deployment patterns, and gives teams more control over credential storage without sacrificing security. --- ## New pkg/function Package for Format-Agnostic Function Registry Introduces `pkg/function/`, a new format-agnostic function registry that consolidates YAML function handlers into a reusable package. ## What Changed This release adds foundational packages for modular function handling: - **`pkg/function/`**: Format-agnostic function registry with handlers for all YAML functions (`!env`, `!exec`, `!terraform.output`, `!store.get`, `!literal`, etc.) - **`pkg/yaml/`**: YAML-specific utilities for position tracking and error handling - **`pkg/aws/identity/`**: Consolidated AWS identity caching (moved from `internal/aws_utils`) ## Why This Matters The function registry separates concerns between format-specific parsing (YAML, HCL, JSON) and format-agnostic function execution. This enables: - **Code Reuse**: Single registry used across all configuration formats - **Extensibility**: New functions can be added without modifying core parsing logic - **Testing**: Interface-driven design with dependency injection for better testability - **Plugin Architecture**: Foundation for future plugin support ## Technical Details Functions are organized by execution phase: - **PreMerge**: `!env`, `!exec`, `!random`, `!template`, `!include`, `!literal` - **PostMerge**: `!terraform.output`, `!terraform.state`, `!store.get`, `!aws.*` The registry provides thread-safe registration, lookup by name or alias, and phase-based filtering. ## Get Involved This is preparatory work for broader YAML processing refactoring. Contributions and feedback are welcome at [github.com/cloudposse/atmos](https://github.com/cloudposse/atmos). --- ## GCP Authentication Support Atmos now supports first-class Google Cloud authentication alongside AWS and Azure, with provider-scoped file isolation and a unified auth experience. ## What Changed - Added GCP providers: `gcp/adc` and `gcp/workload-identity-federation`. - Added GCP identities: `gcp/service-account` and `gcp/project`. - Implemented provider-scoped file isolation at `~/.config/atmos/gcp//...`. - Integrated GCP auth with [`atmos auth login`](/cli/commands/auth/login), [`atmos auth whoami`](/cli/commands/auth/whoami), and Terraform execution. ## Why This Matters - Use the same auth flow across AWS, Azure, and GCP. - Avoid long-lived keys in CI/CD with Workload Identity Federation. - Keep credentials isolated per provider and identity, without touching user `gcloud` config. ## How to Use It For local development with ADC: ```yaml auth: providers: gcp-adc: kind: gcp/adc project_id: my-project identities: terraform: kind: gcp/service-account default: true via: provider: gcp-adc principal: service_account_email: terraform@my-project.iam.gserviceaccount.com ``` For GitHub Actions with Workload Identity Federation — no `token_source` needed, everything is auto-detected: ```yaml auth: providers: gcp-wif: kind: gcp/workload-identity-federation project_number: "123456789012" workload_identity_pool_id: github-pool workload_identity_provider_id: github-provider service_account_email: ci-sa@my-project.iam.gserviceaccount.com ``` In GitHub Actions, Atmos automatically detects `ACTIONS_ID_TOKEN_REQUEST_URL`, constructs the correct WIF audience, and fetches the OIDC token — no manual `token_source`, `url`, or `audience` configuration required. Just ensure your workflow has `id-token: write` permission. Authenticate and verify: ```bash atmos auth login --identity terraform atmos auth whoami ``` For usage and configuration, see [Providers](/cli/configuration/auth/providers). ## Get Involved Feedback and testing reports are welcome. If you use GCP in CI/CD, try the WIF provider and share any edge cases you encounter. --- ## Geodesic: A Production-Ready DevOps Toolbox for Development Containers While Atmos supports any devcontainer configuration, **Geodesic is a proven DevOps toolbox** that's been battle-tested for almost 10 years. If you're looking for a production-ready development container with all the tools you need for infrastructure work, Geodesic is your answer. ## What is Geodesic? [Geodesic](https://github.com/cloudposse/geodesic) is Cloud Posse's implementation of the DevOps toolbox pattern—a comprehensive development container that includes everything you need for modern infrastructure work. **Geodesic is a devcontainer implementation.** It predates the Development Containers specification, but it's the exact same concept: a containerized environment with all your tools pre-installed and pre-configured. ## The DevOps Toolbox Pattern The concept of containerized development environments—what we call "DevOps toolboxes"—originated in the DevOps world long before the Development Containers spec existed. Companies like CoreOS pioneered the toolbox pattern, recognizing that DevOps teams needed consistent, reproducible environments without installing dozens of tools locally. The idea is simple but powerful: **package all your tools into a container, and developers just need Docker and a shell**. Geodesic has been implementing this pattern since 2016, providing infrastructure teams with a battle-tested solution for the "works on my machine" problem. ## What's Included? Geodesic comes pre-loaded with everything you need for infrastructure work: ### Core Infrastructure Tools - **Atmos** (of course!) - **Terraform** with all major providers - **kubectl** and Kubernetes tools (helm, helmfile, k9s, etc.) - **Cloud CLIs**: AWS CLI, Azure CLI, Google Cloud SDK - **Data processing**: jq, yq, gomplate - **Development essentials**: git, make, vim, and more - **Custom scripts and tooling** ### Production-Tested Foundation Geodesic images are: - **Multi-platform**: linux/amd64 and linux/arm64 - **Debian-based**: Familiar package management - **Customizable**: Use as a base image for your own toolbox - **Production-tested**: Nearly a decade of real-world usage - **Open source**: Over 1,000 stars on GitHub ## Using Geodesic with Atmos With [Atmos's native devcontainer support](/changelog/native-devcontainer-support), using Geodesic is incredibly simple: ```yaml # atmos.yaml devcontainer: geodesic: spec: name: "Geodesic DevOps Toolbox" image: "cloudposse/geodesic:latest" workspaceFolder: "/workspace" workspaceMount: "type=bind,source=${localWorkspaceFolder},target=/workspace" containerEnv: ATMOS_BASE_PATH: "/workspace" remoteUser: "root" ``` Then launch it: ```bash atmos devcontainer shell geodesic # You're in a fully-equipped DevOps environment ``` ## Getting Started in 2 Minutes Here's how fast you can go from zero to productive: ```bash # 1. Install Atmos (one binary) brew install atmos # 2. Navigate to your infrastructure repo cd my-infrastructure # 3. Launch Geodesic atmos devcontainer shell geodesic # You're in. Start working. $ atmos terraform plan vpc -s prod $ kubectl get pods $ helm list ``` **That's the ingenious part**: All you need to install is Atmos. Everything else—Terraform, cloud CLIs, Kubernetes tools—gets pulled from the container image automatically. Your host machine stays clean. Your environment stays consistent. Your team uses identical tool versions. ## Quick Start with Examples Check out the live examples in the Atmos repository: ```bash # Clone Atmos repo (or just browse examples on GitHub) git clone https://github.com/cloudposse/atmos.git cd atmos/examples/devcontainer # The example includes a complete configuration cat atmos.yaml # Shows geodesic devcontainer configuration # Launch it atmos devcontainer shell geodesic ``` The `examples/devcontainer` folder contains: - Complete `atmos.yaml` with Geodesic configuration - Example `devcontainer.json` file - Shell aliases for convenience - Ready-to-use setup **Use this as a starting point** for your own configuration. Copy it, customize it, make it yours. ## Shell Aliases for One-Word Access Make it even easier with shell aliases in your `atmos.yaml`: ```yaml # atmos.yaml aliases: shell: "devcontainer shell geodesic" ``` Now you can just type: ```bash atmos shell # Immediately launches Geodesic ``` This mirrors the classic Geodesic pattern where you'd type `./geodesic.sh` to launch your environment. Now it's even simpler: `atmos shell`. ## Customizing Geodesic ### Use as a Base Image Create your own custom toolbox based on Geodesic. First, create a `Dockerfile`: ```dockerfile # .devcontainer/Dockerfile FROM cloudposse/geodesic:latest # Add your organization's tools RUN apt-get update && apt-get install -y \ your-custom-tool \ another-tool # Add custom scripts COPY scripts/ /usr/local/bin/ # Configure environment ENV CUSTOM_VAR=value ``` Then configure Atmos to build from your Dockerfile: ```yaml # atmos.yaml devcontainer: geodesic: spec: name: "Custom Geodesic Toolbox" build: dockerfile: ".devcontainer/Dockerfile" context: "." args: GEODESIC_VERSION: "latest" workspaceFolder: "/workspace" workspaceMount: "type=bind,source=${localWorkspaceFolder},target=/workspace" containerEnv: ATMOS_BASE_PATH: "/workspace" remoteUser: "root" ``` Launch it just like any other devcontainer: ```bash # Build and launch atmos devcontainer shell geodesic # Force rebuild after changing Dockerfile atmos devcontainer shell geodesic --replace ``` Atmos will automatically build the image from your Dockerfile, tag it as `atmos-devcontainer-geodesic`, and create the container. :::tip Example Available Check out the complete example in [`examples/devcontainer-build`](https://github.com/cloudposse/atmos/tree/main/examples/devcontainer-build) which includes a working Dockerfile, devcontainer.json, and atmos.yaml for building custom Geodesic containers. ::: ### Version Pinning for Consistency Pin specific Geodesic versions per project: ```yaml # project-a/atmos.yaml devcontainer: toolbox: spec: image: "cloudposse/geodesic:4.3.0" # Pinned version ``` ```yaml # project-b/atmos.yaml devcontainer: toolbox: spec: image: "cloudposse/geodesic:4.4.0" # Different version ``` Each project gets the right tool versions automatically. ## Integration with Atmos Auth Geodesic works seamlessly with Atmos's identity injection feature: ```bash # Launch Geodesic with AWS identity atmos devcontainer shell geodesic --identity aws-prod # Launch with GitHub identity atmos devcontainer shell geodesic --identity github-main # Works with ANY provider - Azure, GCP, custom providers atmos devcontainer shell geodesic --identity azure-prod ``` Inside the container, cloud provider SDKs automatically use the authenticated identity. No manual credential configuration needed. ## Multiple Instances Need multiple environments? Launch Geodesic with different instance names: ```bash # Development instance atmos devcontainer shell geodesic --instance dev # Production instance atmos devcontainer shell geodesic --instance prod # Each team member can have their own atmos devcontainer shell geodesic --instance alice atmos devcontainer shell geodesic --instance bob ``` Each instance is an independent container with its own state, perfect for running multiple environments or isolating work. ## Why Choose Geodesic? ### Battle-Tested Nearly 10 years in production use across hundreds of infrastructure projects. The patterns and tools have been refined through real-world usage. ### Comprehensive Everything you need for infrastructure work is already installed. No hunting for the right tool versions or dealing with installation issues. ### Consistent Your entire team uses the same tool versions. CI uses the same tool versions. No more "works on my machine." ### Clean Your host machine stays clean. No dozens of CLIs and tools cluttering your system. Just Docker and Atmos. ### Extensible Use Geodesic as-is, or use it as a base image to build your own custom toolbox with organization-specific tools. ### Open Source Over 1,000 stars on GitHub. Active maintenance. Community-driven improvements. ## Get Started Now Use this quick start to get going: ### 1. Install Atmos ```bash brew install atmos # or download from GitHub releases ``` ### 2. Add Geodesic to Your Project ```yaml # atmos.yaml devcontainer: geodesic: spec: image: "cloudposse/geodesic:latest" workspaceFolder: "/workspace" workspaceMount: "type=bind,source=${localWorkspaceFolder},target=/workspace" aliases: shell: "devcontainer shell geodesic" ``` ### 3. Launch Your Environment ```bash atmos shell # Or: atmos devcontainer shell geodesic ``` ## Conclusion Geodesic brings nearly a decade of DevOps toolbox experience into the modern development container era. Combined with Atmos's native devcontainer support, you get a production-ready solution that solves the "works on my machine" problem once and for all. **Install Atmos, run one command, and everything just works.** Check out the [native devcontainer support announcement](/changelog/native-devcontainer-support) to learn more about Atmos's devcontainer capabilities, or dive into the [examples](https://github.com/cloudposse/atmos/tree/main/examples/devcontainer) to get started immediately. ## Resources - [Geodesic GitHub Repository](https://github.com/cloudposse/geodesic) - [Atmos Native Devcontainer Support](/changelog/native-devcontainer-support) - [Devcontainer Command Documentation](/cli/commands/devcontainer) - [Atmos Examples - Devcontainer](https://github.com/cloudposse/atmos/tree/main/examples/devcontainer) - [Development Containers Specification](https://containers.dev/) --- _Have feedback or questions? Join our [Slack community](https://slack.cloudposse.com/) or [open an issue on GitHub](https://github.com/cloudposse/geodesic/issues)._ For usage and configuration, see [Development Containers](/cli/configuration/devcontainer). --- ## Atmos auto-detects GitHub Actions debug logging GitHub Actions has a built-in ["Re-run with debug logging"](https://github.blog/changelog/2022-05-24-github-actions-re-run-jobs-with-debug-logging/) button: when a workflow fails, you click it and the next run launches with runner and step debug logging turned on. Atmos now auto-detects that signal — when you re-run with debug logging, Atmos switches its own log level to `Debug` for the run. No need to remember to also set `ATMOS_LOGS_LEVEL=Debug` in your workflow YAML. ## What Changed When all of the following are true, Atmos sets its log level to `Debug` for the current run and announces the change at startup: - `ci.enabled: true` in your `atmos.yaml`. - Atmos detects it is running in a CI provider that exposes a "debug mode" signal (today: GitHub Actions). - That provider reports debug mode is active for the current run. For GitHub Actions, that means either `ACTIONS_RUNNER_DEBUG=true` or `ACTIONS_STEP_DEBUG=true` is set — which is exactly what the "Re-run with debug logging" button does for you. When the auto-detect fires, you'll see a single Info line in the logs so it's obvious why the output got louder: ``` INFO CI provider debug mode detected — using Debug log level for this run provider=github-actions from=Info ``` ## Why This Matters When something goes wrong in a workflow, debugging Atmos is usually just as important as debugging the workflow around it. The "Re-run with debug logging" button is meant to be one switch that makes everything in the run verbose — but a tool that ignores it forces you back into a per-tool dance: set this env var, add that flag, edit the workflow YAML, re-trigger the job. Atmos now picks up the signal on its own so the button does what you'd expect. The auto-detected level intentionally outranks [`--logs-level`](/cli/global-flags#output-flags) and `ATMOS_LOGS_LEVEL`. The CI-side debug signal is set at the repo or workflow level by the runner itself, so a per-invocation `--logs-level Warning` in your workflow YAML no longer hides debug information when you're actively debugging the run. ## How to Use It You don't have to do anything in Atmos. Make sure `ci.enabled: true` is set in your `atmos.yaml`, and then use the existing GitHub Actions debug controls: - **Per-run**: GitHub's "Re-run with debug logging" button (the primary path). - **Always-on**: set `ACTIONS_STEP_DEBUG=true` as a repository or organization variable to enable step debug logging for every workflow run. See [GitHub's docs on enabling debug logging](https://docs.github.com/en/actions/how-tos/monitor-workflows/enable-debug-logging) for the full list of options. If you don't have `ci.enabled: true`, or you're running outside of GitHub Actions, the auto-detect is a no-op — your configured log level applies as before. ## Get Involved Have feedback or want to suggest improvements? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues) or drop into the [Cloud Posse Slack community](https://slack.cloudposse.com) and let us know. --- ## Gist: Build, Scan, Approve & Share AWS AMIs with Atmos + Packer A common question from the community is: _how do I use Atmos with [Packer](/gists/aws-ami-packer-github-actions) to build AMIs, and automate the whole build → approve → share process?_ We've published a new [**gist**](/gists/aws-ami-packer-github-actions) that shows exactly that — a reference recipe combining several Atmos features into one production-shaped workflow. ## What This Gist Does The gist builds a hardened **Amazon Linux 2023** AMI with Packer orchestrated by Atmos, validates it on a live test instance, optionally scans it, gates promotion behind a **manual approval**, tags the approved image `ScanStatus=approved`, and **shares it across AWS accounts** — all driven from a GitHub Actions pipeline and a set of `atmos ami` custom commands. Like all [gists](/gists), it's shared as-is to demonstrate the pattern. It's not part of the CI-tested [examples](/examples), so adapt it to your environment and your current version of Atmos before relying on it. ## Features Used It combines, in one recipe: - **Packer components in Atmos** — [`atmos packer init/build/output`](/cli/commands/packer/usage). - **Stacks for Packer** — every build input is a stack var, not hardcoded HCL. - **Go templating** — the source AMI name resolves from an environment variable at build time. - **Nested custom commands** — an `atmos ami ` tree (get-ami-id, tag, share, launch/terminate test instances). - **CI/CD with a governance gate** — OIDC auth, ephemeral runners, and a manual approval Environment. - **Tag-based launch governance** — a reference IAM/SCP policy restricting launches to approved AMIs. The optional, proprietary steps (private package repos, commercial scanners) are isolated and off by default, so the recipe works with just a standard AWS account. ## Try It Out ```bash # Copy the gist into a new repo of your own cp -r gists/aws-ami-packer-github-actions/ my-ami-pipeline/ cd my-ami-pipeline/ # Build locally (after editing stacks/al2023.yaml for your environment) atmos packer init al2023 -s al2023 atmos packer build al2023 -s al2023 # Operate the result with custom commands atmos ami get-ami-id al2023 -s al2023 atmos ami launch-instance al2023 -s al2023 --type t3.small atmos ami share al2023 -s al2023 --accounts 123456789012,123456789013 ``` ## Get Involved - Browse the [Gists collection](/gists) - [Join us on Slack](/community/slack) - [Attend Office Hours](/community/office-hours) Open the gist[Read more](/gists/aws-ami-packer-github-actions) --- ## atmos git clone Refuses Unsafe Fork Checkouts by Default [`atmos git clone`](/cli/commands/git/clone) is Atmos's native replacement for `actions/checkout`. Mirroring the [`actions/checkout` v7 hardening](https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/), it now **refuses by default to clone untrusted fork content under the elevated `pull_request_target` and `workflow_run` events** — the classic "pwn request" where fork code would run with your repository's secrets. A grep-able opt-in is available for the rare case you genuinely need it. ## What Changed When you run `atmos git clone` inside a `pull_request_target` or `workflow_run` workflow — which execute with your base repository's secrets, `GITHUB_TOKEN`, and cloud credentials — Atmos now refuses to clone fork content unless you explicitly opt in. The gate triggers only on the genuinely dangerous combination: - an explicit [`--branch`](/cli/commands/git/clone#flags) / ref override that is a pull-request ref (e.g. `refs/pull//merge` or `refs/pull//head`), or - an ad hoc clone URI whose `owner/repo` differs from the base `GITHUB_REPOSITORY`. The safe no-arg checkout (your base repository at its base ref) is **never** gated, and the low-privilege `pull_request`, `push`, and `merge_group` events are unaffected. ## Why This Matters `pull_request_target` and `workflow_run` are exactly the events GitHub hardened in `actions/checkout` v7, because checking out a fork's PR code while holding the base repository's secrets lets a malicious contributor exfiltrate those secrets. Since `atmos git clone` fills the same role as `actions/checkout`, it inherited the same risk — and now it gets the same fail-closed default. ## How to Use It Nothing to do for the common case: base checkouts and `pull_request` workflows keep working. For fork contributions, prefer a `pull_request` workflow (fork secrets are withheld) for any clone-and-plan, and reserve `pull_request_target` / `workflow_run` for trusted, secret-free steps. If you have a deliberate, reviewed reason to bypass the gate, the opt-in is intentionally easy to spot in code review and static analysis: ```yaml # atmos.yaml ci: allow_unsafe_fork_execution: true ``` or per-invocation via the [`--allow-unsafe-fork`](/cli/commands/git/clone#flags) flag or the `ATMOS_ALLOW_UNSAFE_FORK_EXECUTION` environment variable. ## Get Involved See the [`atmos git clone` docs](/cli/commands/git/clone#fork-pr-safety-gate) for the full behavior matrix, and the [CI configuration reference](/cli/configuration/ci) for the opt-in setting. --- ## Git Repository Metadata YAML Functions Atmos now exposes Git **repository metadata** through dedicated YAML functions: `!git.repository`, [`!git.owner`](/functions/yaml/git.owner), [`!git.name`](/functions/yaml/git.name), [`!git.host`](/functions/yaml/git.host), and [`!git.url`](/functions/yaml/git.url). These join the existing [`!git.root`](/functions/yaml/git.root), [`!git.sha`](/functions/yaml/git.sha), [`!git.branch`](/functions/yaml/git.branch), and [`!git.ref`](/functions/yaml/git.ref) functions. ## What Changed You no longer need to shell out through [`!exec`](/functions/yaml/exec) and `sed` to derive the repository slug. The new functions read the `origin` remote and parse it for you, working across GitHub, GitLab, Bitbucket, and Azure DevOps: | Function | Returns | |----------|---------| | `!git.repository` | The `/` slug (e.g. `cloudposse/atmos`), matching GitHub's `GITHUB_REPOSITORY` format | | `!git.owner` | The repository owner / organization (e.g. `cloudposse`) | | `!git.name` | The bare repository name (e.g. `atmos`) | | `!git.host` | The repository host (e.g. `github.com`) | | `!git.url` | The `origin` remote URL | All of them support a fallback value, using the same pattern as the other Git functions: ```yaml vars: git_repo: !git.repository my-org/my-repo git_owner: !git.owner my-org git_host: !git.host github.com ``` ## Why This Matters A common pattern is tagging every resource with the repository that produced the plan. Before, this meant a brittle shell pipeline: ```yaml git_repo: !exec echo ${GITHUB_REPOSITORY:-$(git remote get-url origin | sed -e 's/^.*:// ; s/.git$//')} ``` Now it's a single, portable function: ```yaml terraform: providers: aws: default_tags: tags: atmos_git_repository: !git.repository atmos_git_ref: !git.ref ``` ## How to Use It The functions work in both stack/component YAML processing and Atmos config preprocessing, so repository metadata is available consistently wherever Atmos resolves core YAML functions. ```yaml vars: repository: !git.repository # cloudposse/atmos owner: !git.owner # cloudposse name: !git.name # atmos host: !git.host # github.com url: !git.url # https://github.com/cloudposse/atmos.git ``` A YAML tag owns the entire scalar, so it can't be combined with other text on the same line (for example, prefixing a `workspace_key_prefix`). For that, use the new [`atmos.Resolve`](/functions/template/atmos.Resolve) template function — see the companion post. ## Get Involved See the [`!git.repository`](/functions/yaml/git.repository) documentation for the full reference, and let us know how you're using repository metadata in your stacks. --- ## Run Atmos from Any Subdirectory Atmos now automatically discovers your repository root and runs from there, just like Git. No more `cd`-ing back to the root directory. ## The Git-Like Behavior If you've used Git, you know you can run `git status` from any subdirectory in your repository, and Git automatically finds the repository root. Atmos now works the same way. **Before:** ```bash cd components/terraform/vpc atmos terraform plan vpc -s prod # Error: Could not find atmos.yaml cd ../../.. atmos terraform plan vpc -s prod # Now it works ``` **Now:** ```bash cd components/terraform/vpc atmos terraform plan vpc -s prod # Just works ``` ## How It Works When you run Atmos from a subdirectory: 1. Atmos detects you're in a Git repository 2. It finds the repository root (where `.git` lives) 3. It uses that as the base path for all operations 4. Your `atmos.yaml` at the repository root is found automatically Just like Git, Atmos walks up the directory tree to find the repository root. ## Local Configuration Always Wins If you have an `atmos.yaml` in your current directory, Atmos uses that instead. This ensures local overrides work as expected: ```bash cd experiments/ echo "base_path: ." > atmos.yaml atmos terraform plan # Uses ./atmos.yaml, not repository root ``` Atmos respects these local configuration indicators: - `atmos.yaml` - Main config file - `.atmos.yaml` - Hidden config file - `.atmos/` - Config directory - `.atmos.d/` - Default imports directory - `atmos.d/` - Alternate imports directory If any of these exist in your current directory, they take precedence over git root discovery. ## Disabling the Feature For testing or if you prefer the old behavior, set an environment variable: ```bash export ATMOS_GIT_ROOT_BASEPATH=false atmos terraform plan # Uses current directory as base path ``` This is automatically set for Atmos's internal test suite to prevent test pollution. ## Why This Matters This small change eliminates a common frustration: having to remember where you are in your repository structure. Now you can: - Navigate to component directories to review code - Run Atmos commands without changing directories - Write simpler automation scripts - Work more naturally within your repository Just like Git changed your mental model from "I must be at the root" to "I can work anywhere," Atmos now does the same for infrastructure orchestration. For usage and configuration, see [CLI Configuration](/cli/configuration). --- ## Git YAML Functions for Source Pinning Atmos now includes core Git YAML functions for resolving repository metadata directly in stack and config processing: [`!git.root`](/functions/yaml/git.root), [`!git.sha`](/functions/yaml/git.sha), [`!git.branch`](/functions/yaml/git.branch), and [`!git.ref`](/functions/yaml/git.ref). ## What Changed The new Git function family exposes common repository values without shelling out through [`!exec`](/functions/yaml/exec): | Function | Returns | |----------|---------| | `!git.root` | The current repository root, with the same behavior as [`!repo-root`](/functions/yaml/repo-root) | | `!git.sha` | The full current `HEAD` commit SHA | | `!git.ref` | The same full current `HEAD` commit SHA, intended for immutable source pinning | | `!git.branch` | The current branch name | All four functions support fallback values using the same pattern as `!repo-root`: ```yaml vars: root: !git.root /fallback/path sha: !git.sha unknown ref: !git.ref unknown branch: !git.branch detached ``` ## Source Pinning The primary use case is development source pinning. Dev stacks can point component source versions at the current commit: ```yaml components: terraform: vpc: source: uri: github.com/my-org/my-repo//components/terraform/vpc version: !git.ref ``` Production stacks can stay explicit: ```yaml components: terraform: vpc: source: uri: github.com/my-org/my-repo//components/terraform/vpc version: "1.2.3" ``` This lets pull requests exercise local component changes in development environments while production remains controlled by explicit pins in protected stack or catalog files. ## Tagging Terraform Resources Git functions also combine with Atmos provider generation. For AWS, define `default_tags` in the generated provider override and include both the Atmos component identity and the Git ref that produced the plan: ```yaml terraform: providers: aws: region: us-east-1 default_tags: tags: atmos_stack: !template '{{ .atmos_stack }}' atmos_component: !template '{{ .atmos_component }}' atmos_git_ref: !git.ref atmos_git_branch: !git.branch unknown ``` When you run an [`atmos terraform`](/cli/commands/terraform/usage) command, Atmos writes the provider override for the component: ```json { "provider": { "aws": { "region": "us-east-1", "default_tags": { "tags": { "atmos_stack": "plat-ue1-dev", "atmos_component": "vpc", "atmos_git_ref": "9f3c8b0d2a4e9c7a6f1e0d5c4b3a291817161514", "atmos_git_branch": "feature/source-pinning" } } } } } ``` This tags every resource supported by the AWS provider with the Atmos stack, Atmos component, and exact Git commit used for the run. The commit tag is especially useful when dev environments use `source.version: !git.ref`, because the component source and resource provenance point at the same immutable revision. :::note Dedicated `!atmos.stack` and `!atmos.component` YAML functions would make this pattern a little cleaner, but they are not required for this workflow today. The current template context already exposes `{{ .atmos_stack }}` and `{{ .atmos_component }}` for the Atmos identity, and `!git.ref` covers the immutable Git side. ::: ## Repository Metadata in Config The same functions work in both stack/component YAML processing and Atmos config preprocessing, so repository metadata can be used consistently wherever Atmos already resolves core YAML functions. `!git.root` is also available as an alias for `!repo-root`, and the function registry exposes `git.root` alongside the existing `git-root` alias. ## Detached HEAD Behavior `!git.sha` and `!git.ref` continue to work in detached HEAD checkouts because `HEAD` still resolves to a commit. `!git.branch` returns an error when no branch name exists unless a fallback value is provided. For usage and configuration, see [!git.repository](/functions/yaml/git.repository). --- ## Native GitHub Actions Support for Atmos Toolchain Atmos toolchain now has native GitHub Actions support with the new `github` format for [`atmos toolchain env`](/cli/commands/toolchain/env). ## What Changed The `atmos toolchain env` command now supports a `github` format that outputs paths compatible with GitHub Actions' `$GITHUB_PATH` environment file. When running in GitHub Actions, it automatically detects and writes to `$GITHUB_PATH`. ### New Features - **`--format github`**: Outputs one path per line, compatible with `$GITHUB_PATH` - **`--output` flag**: Append output to any file instead of stdout - **Auto-detection**: When `$GITHUB_PATH` is set, the `github` format automatically writes to it - **Improved error messages**: Helpful hints guide you when tools aren't installed ## How to Use It In your GitHub Actions workflow: ```yaml - name: Install Atmos toolchain run: | atmos toolchain install atmos toolchain env --format github ``` The next step in your workflow will have access to all your toolchain binaries (OpenTofu, Terraform, Helmfile, etc.) in the PATH. ## Why This Matters Previously, integrating Atmos toolchain with GitHub Actions required shell tricks: ```yaml # Before: Manual PATH manipulation - run: echo "$(atmos toolchain path)" >> $GITHUB_PATH ``` Now it's a single, intuitive command that handles everything automatically. For usage and configuration, see [atmos toolchain path](/cli/commands/toolchain/path). ## Get Involved Try out the new GitHub Actions integration and let us know what you think! File issues or feature requests on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## GitHub Actions Output Format Atmos now supports a dedicated `github` output format for [`atmos terraform output`](/cli/commands/terraform/output), making it easier than ever to pass Terraform outputs between GitHub Actions steps. ## What's New The new `--format=github` option for `atmos terraform output` automatically writes outputs to `$GITHUB_OUTPUT` in the format GitHub Actions expects, including proper handling of multiline values using heredoc syntax. ```shell # In a GitHub Actions workflow atmos terraform output vpc -s dev --format=github ``` This writes outputs directly to `$GITHUB_OUTPUT`, making them available to subsequent workflow steps. ## Why This Matters Previously, passing Terraform outputs between GitHub Actions steps required manual formatting or shell scripting to handle: - Multiline values (JSON objects, lists, multi-line strings) - Special characters that need escaping - The correct `$GITHUB_OUTPUT` file format The `github` format handles all of this automatically: - **Single-line values** are written as `key=value` - **Multiline values** use heredoc syntax: `key<- The {{ .atmos_component }} component in the {{ .stack }} stack {{ if eq .status "failure" }}failed{{ else }}deployed{{ end }} ``` The same values are exported as `ATMOS_HOOK_STATUS`, `ATMOS_HOOK_EXIT_CODE`, `ATMOS_HOOK_ERROR`, `ATMOS_COMPONENT`, and `ATMOS_STACK` for steps that read the environment. Under the hood, user hooks now fire on the failure path too — not just on success — so "deployment failed" announcements actually reach you. ## How to Use It 1. Set `kind: step` and a `type:` naming any registered step type. 2. Put the step's parameters under `with:`. 3. Use `events:` to choose when it fires, `on_failure:` for warn/fail/ignore, and `retry:` to wrap the step in Atmos's retry policy. A typo'd `type:` fails the preflight check _before_ Terraform runs, so you find out immediately. All step types are available, including interactive ones — whether an interactive step makes sense on a (usually headless) lifecycle event is the step's responsibility, not the hook's. :::note The `http` step ships separately The Slack example needs the `http` step type. If your build doesn't have it yet, use a registered step type such as `container`, `toast`, `log`, or `markdown` — the bridge works with every registered step type. ::: ## Get Involved See the [Hooks reference](/stacks/hooks#kind-step-run-a-step-type) for the full `kind: step` documentation, and the [step types](/workflows/steps/type) reference for the available steps. --- ## HTTP Step Type: Call HTTP Endpoints from Workflows and Custom Commands Workflows and custom commands now support a native `http` step type that performs an HTTP request — any verb, query-string parameters, headers, and a request body (raw or form/JSON) — with per-attempt timeouts and retries that compose with the existing `retry:` policy. No more shelling out to `curl`. (Prefer `type: webhook`? It's an accepted alias.) ## The Problem Calling an external endpoint from a workflow used to mean a `shell` step running `curl`: ```yaml steps: - type: shell command: | curl -sf -X POST "https://ci.example.com/hook" \ -H "Authorization: Bearer $TOKEN" \ -d '{"status":"deployed"}' ``` That works until it doesn't: `curl` isn't guaranteed to be on the box (especially on Windows), quoting and templating the payload is fiddly, and you get no first-class handling of timeouts or transient failures. Retrying a flaky `5xx` meant hand-rolling a bash loop. ## The Solution The `http` step makes HTTP a first-class citizen: ```yaml workflows: notify: steps: - name: trigger type: http url: "https://ci.example.com/hook/{{ .env.JOB_ID }}" method: POST query: ref: "{{ .env.GIT_SHA }}" headers: Authorization: "Bearer {{ .env.TOKEN }}" Content-Type: application/json body: '{"status":"deployed"}' expect: status: [200, 201, 202, 204] response: - /"status"\s*:\s*"deployed"/ timeout: 30s retry: max_attempts: 5 backoff_strategy: exponential initial_delay: 1s max_delay: 30s - name: report type: info content: "Endpoint returned HTTP {{ .steps.trigger.metadata.status_code }}" ``` Everything templates, including the URL, headers, query params, and body — so you can thread values from earlier steps or the environment straight into the request. Here's the `http` step calling a local endpoint end to end: [View the full example](/examples/http-webhooks) ## Sending Parameters You asked for full parameter support, and it's all here: - **Verb** — `method:` accepts `GET` (default), `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, and `OPTIONS`. - **Query-string parameters** — `query:` is a key-value map appended to the URL. - **POST parameters** — use `body:` for a raw payload, or `form:` for a key-value map. `form` is sent as `application/x-www-form-urlencoded` by default, or as a JSON object when you set a JSON `Content-Type`: ```yaml steps: - name: notify-slack type: http url: "{{ .env.SLACK_WEBHOOK_URL }}" method: POST headers: Content-Type: application/json form: text: "Deployment complete" ``` ## Timeouts and Retries That Compose `timeout` is the per-attempt deadline. `retry` is the same retry block you already use on `shell` and `atmos` steps — and the http step is HTTP-aware about what's worth retrying: - **Retried by default:** transport/network errors, `5xx`, and `429 Too Many Requests`. - **Fail fast:** other `4xx` responses (a `404` won't burn through five attempts). - **Your call:** `retry.conditions` regexes (matched against `" "`) let you retry additional cases, and `expect.status` / `expect.response` define exactly what counts as success. This makes the step a natural fit for polling, too — `GET` a health endpoint and retry until the body matches: ```yaml steps: - name: health type: http url: "{{ .env.HEALTH_URL }}" expect: status: [200] response: - /"status"\s*:\s*"(ok|healthy)"/ retry: max_attempts: 10 backoff_strategy: constant initial_delay: 2s ``` ## Using the Response The response body becomes the step's value, and useful details land in metadata: - `{{ .steps..value }}` — the response body - `{{ .steps..metadata.status_code }}` — the numeric status code - `{{ .steps..metadata.status }}` — the status text (e.g., `200 OK`) - `{{ .steps..metadata.response_headers }}` — response headers Because it's a regular step type, `http` works in [custom commands](/cli/configuration/commands) exactly as it does in [workflows](/workflows). For usage and configuration, see [http](/workflows/steps/type/http). ## Get Involved See the [workflow step types](/workflows) and [custom commands](/cli/configuration/commands) docs for the full reference, and the `examples/http-webhooks` example to try it out. Questions or ideas? Join us in the [Cloud Posse community Slack](https://cloudposse.com/slack). --- ## Unified Import Adapter Registry Atmos now includes a unified import adapter registry that provides a modular, extensible architecture for configuration imports. ## What Changed The import system has been refactored to use an adapter-based registry pattern: - **GoGetterAdapter** handles all remote imports (http://, https://, git::, s3::, gcs::, oci://, etc.) - **LocalAdapter** handles local filesystem paths - **MockAdapter** provides synthetic YAML generation for testing This replaces the previous if/else chain with a clean, extensible registry that routes imports to the appropriate adapter based on URL scheme. ## Why This Matters The new architecture: - **Fixes go-getter routing** - All go-getter schemes now work correctly (git::, s3::, gcs::, oci://, etc.) - **Enables extensibility** - New import schemes can be added by implementing the `ImportAdapter` interface - **Improves testability** - The `mock://` scheme enables testing without external dependencies - **Follows established patterns** - Consistent with the command registry and store registry patterns ## How to Use It Existing imports continue to work unchanged. The new architecture is transparent to users while enabling future extensibility like `terragrunt://` for HCL-to-YAML transformation. For usage and configuration, see [Import Stack Configurations](/stacks/imports). --- ## Improved Error Messages for Terraform HCL Syntax Errors Atmos now provides clear, actionable error messages when your Terraform components contain HCL syntax errors, instead of the misleading "component not found" message. ## The Problem Previously, when a Terraform component had invalid HCL syntax, Atmos would display a confusing error: ``` Error: invalid component Could not find the component `testme` in the stack `prod`. Check that all the context variables are correctly defined in the stack manifests. Are the component and stack names correct? Did you forget an import? ``` This was misleading because the component _was_ defined in the stack manifest and would even appear in the TUI menu. The real issue was that the Terraform files contained a syntax error that prevented Atmos from parsing them. ## The Solution Atmos now correctly identifies HCL parsing failures and provides helpful, actionable feedback: ``` # Error **Error:** failed to load terraform module: Argument or block definition required: An argument or block definition is required here. ## Explanation The Terraform component 'testme' contains invalid HCL code at components/terraform/testme/main.tf:7. ## Hints 💡 Run 'atmos terraform validate' to see more details: atmos terraform validate testme -s prod ``` The new error message includes: - **The actual HCL parsing error** from the Terraform parser - **The exact file and line number** where the error occurred - **A copy-pasteable command** to run [`atmos terraform validate`](/cli/commands/terraform/validate) for more details ## Why This Matters HCL syntax errors are easy to make—a mismatched bracket, a typo in a block name, or a missing quote. When these errors produce misleading messages, developers waste time looking in the wrong places. Now you'll know immediately that the issue is in your Terraform code and exactly where to look. For usage and configuration, see [Error Handling & Monitoring](/cli/configuration/errors). ## Get Involved Found an error message that could be more helpful? [Open an issue](https://github.com/cloudposse/atmos/issues) and let us know! --- ## Apply a Stack and Everything It Depends On Standing up an environment is rarely one `apply`. The stack you actually care about sits on top of prerequisites — a network layer in a shared stack, a database a few components down — and something has to run them first, in the right order. In practice that "something" is usually a bash wrapper: a hand-maintained list of stacks, a loop of [`atmos terraform apply`](/cli/commands/terraform/apply) calls, and a prayer that the ordering comments are still true. Atmos now does this natively: [`--include-dependencies`](/cli/commands/terraform/plan#include-dependencies-and-dependents) and [`--include-dependents`](/cli/commands/terraform/plan#include-dependencies-and-dependents) expand any multi-component selection with its dependency closure and execute it in graph order, and the `list` commands preview exactly what would run. ## The Problem Atmos has always known the dependency graph — `dependencies.components` declares what each component needs, and bulk operations like `atmos terraform apply --all` already execute in topological order. But the _selection_ never followed the edges. Selecting a stack with `-s dev`, or a set of components with [`--tags`](/cli/commands/terraform/plan#plan-components-by-tags-and-labels) or [`--labels`](/cli/commands/terraform/plan#plan-components-by-tags-and-labels), ran only what matched; prerequisites living in another stack (or without the matching tag) were silently out of scope. Deploying "dev and everything dev needs" meant knowing the prerequisite chain yourself and scripting around Atmos — exactly the kind of glue the tool exists to remove. The reverse direction had the same gap: after changing a shared component, running everything that _depends on_ it was only possible through [`--affected`](/cli/commands/terraform/plan#plan-affected-components), not from an arbitrary selection. ## The Fix Every multi-component terraform selection — [`--all`](/cli/commands/terraform/plan#plan-all-components), [`--components`](/cli/commands/terraform/plan#plan-specific-components), [`--query`](/cli/commands/terraform/plan#plan-components-by-query), `-s`, `--tags`, `--labels`, and `--affected` — now accepts two closure flags: - The `--include-dependencies[=N]` flag expands the selection with everything it depends on (its prerequisites), N levels deep. The bare flag means unlimited depth. - The `--include-dependents[=N]` flag expands in the reverse direction: everything that depends on the selection. Selectors choose the _seed_; the closure flags _expand_ it. Expanded components execute even when they don't match the selectors — a prerequisite doesn't need your `env=dev` label to be required by something that has it. Execution stays in dependency order (reverse order for `destroy`), and cross-stack edges are followed: `apply --all -s dev --include-dependencies` will run a prerequisite in `core` before the components in `dev`. The same flags work on [`atmos list components`](/cli/commands/list/components), [`atmos list stacks`](/cli/commands/list/stacks), and [`atmos list instances`](/cli/commands/list/list-instances), so you can see the exact set a bulk run would execute before running it. Scoping stays lazy: Atmos first walks a lightweight structural graph to find the closure, then fully evaluates (templates, YAML functions, authentication) only the stacks the closure actually touches. An unrelated account's unreachable backend still can't break your deploy. ## How to Use It Apply everything labeled for dev, plus all of its prerequisites, in dependency order: ```shell atmos terraform apply --all --labels=env=dev --include-dependencies ``` Bound the expansion to one dependency level: ```shell atmos terraform plan --all --tags=app --include-dependencies=1 ``` Tear down a component and everything that depends on it (dependents are destroyed first): ```shell atmos terraform destroy --components=vpc -s dev --include-dependents ``` Preview the execution set without running anything: ```shell atmos list components --labels=env=dev --include-dependencies atmos list stacks --labels=env=dev --include-dependencies ``` A few things to know: - Using `--include-dependencies` with `destroy` also destroys _shared_ prerequisites of your selection — components other stacks may still rely on. Atmos warns when you combine them. - Because [`metadata.tags`](/stacks/components/component-metadata#tags) and [`metadata.labels`](/stacks/components/component-metadata#labels) now drive scoping decisions _before_ evaluation, they are selectors by design: plain strings, simple templates, and local functions like [`!env`](/functions/yaml/env), `!git.*`, and [`!include`](/functions/yaml/include) are allowed, but values that require authentication or execution ([`!terraform.state`](/functions/yaml/terraform.state), [`!store`](/functions/yaml/store), [`!exec`](/functions/yaml/exec), `atmos.Component`, ...) are rejected with an error. This contract is enforced for every component whenever stacks are enumerated — by `describe`, `list`, and bulk terraform commands — even when no selector flag is used and the component is outside the current stack filter, so an existing impure value fails fast rather than surfacing later as a confusing scoping bug. The error names the offending component and stack manifest. Move those values into `vars` or `settings` instead — or, if you need time to migrate, set `describe.settings.eager_evaluation: true` in `atmos.yaml` to restore the previous full-evaluation behavior (see the [describe configuration](/cli/configuration/describe) reference). - If you already combine `--affected --include-dependents` with `--tags` or `--labels`, note the semantics changed: previously the tag/label filter also removed non-matching dependents from the expanded set; now selectors narrow only the _seed_ and closure members are retained regardless. That is the whole point of closure expansion — but it does mean such runs can now include more components than before. - The depth value must be attached with `=` (for example `--include-dependencies=2`). On the terraform commands a bare `--include-dependencies` followed by a separate value is also accepted, so take care that a following positional argument isn't consumed as the depth — Atmos rejects non-numeric values loudly rather than guessing. ## Get Involved Try the closure flags on your own dependency graph and tell us where the semantics surprise you — especially around `destroy` ordering and depth bounds. Issues and discussions are open at [github.com/cloudposse/atmos](https://github.com/cloudposse/atmos). --- ## Initialize an Atmos project from a proven starting point Initializing a project should establish a useful local starting point, not leave a developer with an empty directory and a list of conventions to reconstruct. Modern development platforms provide an initialization workflow: developers select a known-good example or template, create the project locally, and make the next action obvious. Atmos provides that workflow for infrastructure projects. The [`atmos init`](/cli/commands/init) command initializes an Atmos project from a template chosen for the job at hand: a minimal cloud-agnostic project, an AWS application SDLC project, or a cloud landing zone. Each template creates the configuration, structure, and operational path appropriate to that project rather than asking every team to assemble it from scratch. [View the full init example](/examples/init) ## Initialize the Project You Need The `basic` template gives a new project a small, cloud-agnostic foundation with a real `greeting` Terraform component. It creates a local file, so a developer can validate and deploy the generated project without a cloud account, credentials, or an emulator. The point is to prove that the initialized project works before anyone starts extending it. For teams beginning an application project, the `aws/app` catalog template establishes a complete AWS application SDLC repository with dev, staging, and production stacks, native CI, and an emulator-proven application component. For a platform foundation, `aws/landing-zone` initializes dev, staging, and production environments with a conventional AWS baseline for audit, KMS, SSM, monitoring, and IAM. The catalog also includes GCP and Azure landing-zone starting points. ## A Consistent Initialization Contract An interactive invocation lets a developer choose the project template and answer only the questions that shape it. Required answers, patterns, option lists, and boolean values are enforced consistently whether they come from prompts, defaults, persisted values, or `--set`. The same initialization can therefore be repeated from automation without bypassing the template’s contract. Built-in templates travel with the Atmos binary. Catalog templates resolve to the source associated with that build, so a team initializes from a known template version rather than an accidental snapshot of a repository. ## Build Your Organization's Golden Paths The `init` catalog distributes proven starting points with Atmos. For organization-owned golden paths that your platform team distributes through a catalog or Git repository, see [the scaffolds announcement](/changelog/scaffold-conditional-generation-and-hooks). See the [Init Command Documentation](/cli/commands/init) for the full template list. --- ## Real-Time Instance Status from Atmos CLI to Atmos Pro For teams using [Atmos Pro](https://atmos-pro.com), the Atmos CLI now pushes instance status directly to the Atmos Pro dashboard the moment a plan or apply completes. The dashboard reflects the real state of every component within seconds — no polling, no waiting for webhooks, no stale data. ## Live Infrastructure Status [Atmos Pro](https://atmos-pro.com) gives teams a real-time view of every infrastructure component across all environments. With this release, the status pipeline gets significantly faster: the CLI reports results to Atmos Pro immediately after each terraform operation completes. When you run [`atmos terraform plan`](/cli/commands/terraform/plan) or [`atmos terraform deploy`](/cli/commands/terraform/deploy) with `--upload-status`, the CLI sends the raw execution result to Atmos Pro. The dashboard updates within seconds, showing whether each component is in sync, has drift, or encountered an error — across every stack, every environment, at a glance. ## How It Works The CLI sends the raw command and exit code to Atmos Pro, which interprets the result server-side. This means Atmos Pro can refine how it classifies status without requiring a CLI update. The CLI stays simple — it just reports what happened. ```bash # Plan with status reporting atmos terraform plan mycomponent -s prod-use1 --upload-status # Apply with status reporting atmos terraform deploy mycomponent -s prod-use1 --upload-status ``` In CI workflows, add `--upload-status` to your plan and apply steps. Combined with the new CI exit code mapping, your workflows stay green while Atmos Pro captures the full picture: ```yaml # atmos.yaml ci: enabled: true components: terraform: ci: exit_codes: 0: true # no changes → CI success 1: false # error → CI failure 2: true # changes detected → CI success ``` ## Why This Matters Infrastructure teams managing hundreds of components need to know the state of their deployments at all times. Before this change, status updates depended on webhook processing and background reconciliation — introducing delays and gaps. Now the CLI closes that loop directly: - **Instant feedback** — dashboard updates seconds after plan or apply finishes - **Complete coverage** — both plan and apply report status, including errors - **Zero configuration drift** — Atmos Pro always reflects the latest execution result - **CI-native** — works seamlessly in GitHub Actions, GitLab CI, or any CI system If you're managing infrastructure at scale, [Atmos Pro](https://atmos-pro.com) turns your CLI output into a shared, real-time operational view for the entire team. For usage and configuration, see [atmos list instances](/cli/commands/list/list-instances). --- ## Interactive File Generation for Terraform, Helmfile, and Packer File generation now features interactive component and stack selection, plus cross-provisioner support for helmfile and packer. Run [`atmos terraform generate files`](/cli/commands/terraform/generate/files) without arguments and get an intuitive selector. ## What Changed Building on the [declarative file generation](/changelog/declarative-file-generation) feature, this release adds several improvements: - **Interactive prompts** for component and stack selection when arguments are missing - **Cross-provisioner support** for helmfile and packer (not just terraform) - **Idempotent generation** that only writes files when content changes - **Deterministic output** with sorted map keys for reproducible generation - **JIT component creation** with auto-generation running BEFORE path validation ```bash $ atmos terraform generate files ? Choose a component > vpc eks-cluster rds-aurora ? Choose a stack > ue2-dev ue2-prod ``` ## Why This Matters ### Discoverability When working with unfamiliar infrastructure, you may not know which components have file generation configured. Interactive prompts let you explore what's available without digging through YAML files. ### Cross-Provisioner Consistency Teams using helmfile or packer alongside terraform can now use the same file generation patterns across all provisioners: ```yaml # atmos.yaml components: terraform: auto_generate_files: true helmfile: auto_generate_files: true packer: auto_generate_files: true ``` ### Idempotent Operations File generation now compares existing content before writing. This means: - No unnecessary file modifications - Cleaner git diffs - Faster execution when files haven't changed ## How It Works ### Interactive Selection When you omit the component or stack argument in an interactive terminal, Atmos prompts for selection: ```bash $ atmos terraform generate files ? Choose a component > vpc i Selected component `vpc` ? Choose a stack > ue2-dev i Selected stack `ue2-dev` Generating files for component `vpc` in stack `ue2-dev`... ``` ### JIT Component Creation With `auto_generate_files: true`, files are generated BEFORE path validation during component execution. This enables just-in-time component creation where the generated files can create the component directory itself. ### Summary Output After generation, you see exactly what changed: ```bash ✓ Generated file: locals.tf ✓ Generated file: context.json i Summary: 2 files generated (2 created, 0 updated, 0 unchanged) ``` ## Disabling Interactive Prompts For scripts and automation, disable prompts the same way as other interactive commands: ```bash # Via flag atmos terraform generate files vpc -s ue2-dev --interactive=false # Via environment variable export ATMOS_INTERACTIVE=false ``` For usage and configuration, see [Generate Terraform Files](/stacks/generate). ## Get Involved - Review the [generate files documentation](/cli/commands/terraform/generate/files) for configuration options - See the example in `examples/generate-files/` demonstrating the feature - Share feedback on [GitHub](https://github.com/cloudposse/atmos/issues) --- ## Interactive Prompts for Missing Required Flags Atmos now includes interactive prompts for missing required flags and positional arguments, making commands more discoverable and user-friendly. This feature is being gradually rolled out across commands. ## What Changed Commands with required flags or positional arguments now automatically prompt you to select from available options when values are missing. This works just like shell autocomplete, helping you discover available options without memorizing values or checking documentation. For example, running [`atmos theme show`](/cli/commands/theme/show) without arguments now displays an interactive menu: ```bash atmos theme show # ↓ Shows interactive selector with available themes ``` Previously, you'd see an error message requiring you to check the docs for valid values. ## Why This Matters Interactive prompts significantly improve the developer experience by: 1. **Reducing cognitive load** - No need to remember all valid values for flags 2. **Faster workflow** - Select from a menu instead of typing and potentially mistyping 3. **Better discoverability** - See what options are available without leaving the terminal 4. **CI-friendly** - Automatically disabled in non-interactive environments (CI/CD, piped output) ## How It Works Interactive prompts appear when all these conditions are met: 1. **TTY detected** - You're running in an interactive terminal 2. **Required flag missing** - The command has a required flag without a value 3. **`--interactive` flag is true** - Enabled by default, disable with `--interactive=false` ### Flag Patterns **Interactive selector with empty value:** ```bash # Prompts for identity selection atmos auth whoami --identity # Prompts for theme selection atmos theme show --theme ``` **Interactive selector when flag omitted:** ```bash # Prompts if stack completion is available atmos terraform plan mycomponent ``` **Skip prompt with explicit value:** ```bash # No prompt - uses provided value atmos theme show --theme default atmos auth whoami --identity production ``` ## Disabling Interactive Prompts For scripting or CI/CD environments, disable prompts using: **Via flag:** ```bash atmos theme show --interactive=false ``` **Via environment variable:** ```bash export ATMOS_INTERACTIVE=false atmos theme show ``` **Via configuration:** ```yaml # atmos.yaml settings: interactive: false ``` Interactive prompts are automatically disabled when: - Running in CI (detected via `CI` environment variable) - Output is piped or redirected - Not running in a TTY ## Examples in Action ### Theme Selection ```bash $ atmos theme show ? Select a theme: > default github-dark dracula monokai ``` ### Identity Selection ```bash $ atmos auth whoami --identity ? Select an identity: > production staging development ``` ### Positional Arguments ```bash $ atmos describe component ? Select a component: > vpc eks-cluster rds-aurora ``` :::tip Filtering Options When a selector has many options, you can filter by typing `/` followed by your search text. Note that filtering is case-sensitive. ::: ## Rollout Status This feature is currently being rolled out across Atmos commands. Initial support is available for: - `atmos theme show` - Theme selection. - [`atmos auth`](/cli/commands/auth/usage) commands - Identity selection. **Coming soon:** We're actively working to add interactive prompts to core functionality including [`atmos terraform`](/cli/commands/terraform/usage), [`atmos helmfile`](/cli/commands/helmfile/usage), and [`atmos packer`](/cli/commands/packer/usage). These commands will prompt for component and stack selection when values are missing, making it even easier to work with infrastructure components. ## Get Involved - [Documentation: Developing Atmos Commands](https://atmos.tools/developing-atmos-commands) - Share your feedback in the [Atmos Community Slack](https://cloudposse.com/slack) --- ## Interactive Identity Selection for Auth Login Running [`atmos auth login`](/cli/commands/auth/login) without specifying an identity is now more user-friendly. When no [`--identity`](/cli/commands/auth/login#flags) flag is provided, Atmos presents an interactive selector to choose from your configured identities. ## What Changed The `atmos auth login` command now provides an interactive identity selector when no identity is specified on the command line. This makes it easier to authenticate without remembering exact identity names. ## How It Works When you run `atmos auth login` without the `--identity` flag: **Interactive Mode:** - If exactly one default identity is configured → uses it automatically - If no default identity is configured → shows an interactive selector with all available identities - If multiple default identities are configured → shows an interactive selector with those defaults **CI/CD Mode (non-interactive):** - Returns an error if no default identity is found - Requires using the `--identity` flag or `ATMOS_IDENTITY` environment variable ## Example ```bash # No identity specified - shows interactive selector $ atmos auth login # Use arrow keys to navigate and Enter to select: > dev-admin prod-readonly staging-deploy ``` ## Migration No changes required! This enhancement is fully backward compatible: - Existing commands with `--identity` flag work exactly as before - Default identity configuration continues to work - CI/CD pipelines are unaffected (they should already specify identity explicitly) ## Notes The interactive selector uses arrow keys for navigation and Enter to confirm selection. It's available in terminal environments and provides a better experience when working with multiple identities. For more details, see the [auth login documentation](/cli/commands/auth/login). --- ## Pick configuration profiles interactively with a bare --profile Naming things from memory is one of the more tedious parts of a CLI workflow. You know you want to switch configuration contexts before running a command, but you don't always remember every profile name your team has defined, especially on a project you don't touch daily. Until now, `--profile` required you to type that name exactly, or go check [`atmos profile list`](/cli/commands/profile/profile-list) first and copy it over. ## The Problem `--profile` activates one or more named configuration overlays — dev settings, CI settings, security overrides, whatever a project defines. But the flag always required an explicit value. Typing `--profile` alone, expecting the CLI to prompt you the way `-i`/`--identity` already does when used bare, instead produced a raw usage error: ```shell $ atmos auth login -i core-auto/terraform --profile Incorrect Usage Error: --profile flag needs an argument for command atmos auth login ``` ## The Fix `--profile` with no value now opens an interactive multi-select listing every profile Atmos discovers across your configured profile locations. Nothing is checked by default — toggle the ones you want with space, confirm, and they activate left-to-right in the order shown, exactly as if you'd typed them explicitly. Any profile name you already typed alongside the bare flag starts pre-checked, so `--profile ci --profile` opens with `ci` already selected, ready to confirm or adjust. In a non-interactive context — CI, scripts, no TTY — bare `--profile` returns a clear error instead of hanging or guessing, so automation fails fast rather than silently. ## How to Use It ```shell # Interactively choose which profiles to activate atmos auth login -i core-auto/terraform --profile # Explicit values still work exactly as before atmos --profile developer terraform plan vpc -s prod atmos --profile ci,security terraform plan vpc -s prod ``` ## Get Involved See the [Profiles](/cli/configuration/profiles#interactive-selection) docs for the full behavior, including profile discovery order and merge precedence. Have feedback on this feature? [Open an issue](https://github.com/cloudposse/atmos/issues) or join the conversation in the [Cloud Posse community Slack](https://cloudposse.com/slack). --- ## Interactive Profile Suggestion for Missing Identities When an `--identity` can't be resolved in the currently loaded Atmos config, Atmos now checks whether the identity is defined in another profile — and either prompts you to switch or hints at the exact command to re-run. The same release also adds `profiles.default` so you can pin a default profile in `atmos.yaml`. ## What Changed Two small features that work together. ### `profiles.default` in `atmos.yaml` You can now declare a default profile in your base config: ```yaml profiles: base_path: profiles default: dev ``` When neither the `--profile` flag nor the `ATMOS_PROFILE` env var is set, Atmos loads the `dev` profile automatically. The precedence is now: 1. `--profile` flag 2. `ATMOS_PROFILE` env var 3. `profiles.default` in `atmos.yaml` _(new)_ 4. No profile A default-from-config only applies to the base `atmos.yaml`; a default profile's own `profiles.default` is ignored (no recursion, no cycles). ### Interactive suggestion when an identity is missing Previously, running `atmos --identity root-admin terraform plan` when `root-admin` wasn't defined in the loaded config produced a flat `identity 'root-admin' not found` error — even if `root-admin` lived in a profile you hadn't selected. Now Atmos looks. **Interactive terminal:** ``` $ atmos --identity root-admin terraform plan ? Identity `root-admin` is defined in profile `alpha`. Re-run atmos with this profile? > Yes, use this profile No, cancel ``` Picking "Yes" re-executes Atmos with `--profile alpha` prepended to your original arguments. Picking "No" falls through to the normal not-found error. When multiple profiles define the same identity, you get a select list: ``` ? Identity `shared-id` is defined in multiple profiles. Select one: > alpha bravo charlie ``` **Non-interactive terminal (CI, scripts):** The error is enriched with actionable hints instead of prompting: ``` Error: identity not found Hint: Identity `root-admin` is defined in profile `alpha` Hint: Re-run with `--profile alpha` to use it ``` ## Why This Matters Profiles and identities are two different things — profiles select a config preset, identities select an entry under `auth.identities`. It's easy to forget which profile a particular identity lives in, and the old error message gave no clue. You'd error, grep the repo for the identity name, find the profile, re-run. Now the first error contains the answer. The `profiles.default` setting removes a second papercut: teams that always use the same profile for local development no longer need to set `ATMOS_PROFILE` in their shell RC or remember to pass `--profile` every time. ## Guardrails - **Explicit wins.** If you passed `--profile A` or set `ATMOS_PROFILE=A`, Atmos never suggests swapping to a different profile — even if the identity exists somewhere else. Your choice is respected. - **Default doesn't count as explicit.** If the only reason a profile loaded is `profiles.default`, the suggestion still fires. The suggestion is about helping you find the right profile when you haven't actively chosen one. - **Loop guard.** The re-exec sets `ATMOS_PROFILE_FALLBACK=1` so the second invocation never re-enters the fallback — even if the selected profile also fails to resolve the identity. ### Generic fallback for all auth commands The identity-specific suggestion above needs a name to search on. That misses a more common case: you run [`atmos auth login`](/cli/commands/auth/login) (no `--identity`, no `--profile`) in a repo whose entire auth config lives in profiles. The base `atmos.yaml` has neither `auth.identities` nor `auth.providers`, and the old behavior was a flat `no providers available` — with no hint that the answer lived one flag away. Now every identity-dependent auth command (`auth login`, `auth exec`, `auth shell`, `auth env`, `auth console`, `auth whoami`) checks for profiles with auth config when it hits "no identities / no providers / no default" and offers the same switch: ``` $ atmos auth login ? No identities available. Select a profile: > dev prod staging ``` Pick one and Atmos re-executes `atmos auth login --profile `. In CI the error names every candidate: ``` Error: no identities available Hint: Profile `dev` defines auth configuration Hint: Profile `prod` defines auth configuration Hint: Re-run with `--profile ` to use one of them ``` Same gating as the identity-specific suggestion: an explicit `--profile` or `ATMOS_PROFILE` is always respected, and the `ATMOS_PROFILE_FALLBACK=1` loop guard prevents prompt cycles. ## How to Use It Set a default profile once: ```yaml # atmos.yaml profiles: base_path: profiles default: dev ``` Organize identities into profiles that make sense for your team: ``` profiles/ ├── alpha/ │ └── atmos.yaml # auth.identities.root-admin ├── beta/ │ └── atmos.yaml # auth.identities.dev-user └── prod/ └── atmos.yaml # auth.identities.prod-deployer ``` When someone runs `atmos --identity prod-deployer terraform plan` without selecting a profile, they'll get prompted to switch to `prod` — or, in CI, the error will name the profile to pass. ## Get Involved Found an issue or have a feature request? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Interactive Workflow & Command Steps Now Run in CI You built a slick interactive workflow — `choose` an account, `input` a release tag, then deploy. It is perfect on your laptop. Then CI runs it and everything stops with `interactive terminal required for step`. The prompts that make the workflow friendly locally are exactly what make it unrunnable in a pipeline, so you end up maintaining a second, prompt-free copy just for CI. Interactive steps now fall back to their `default` value when there is no TTY, so the _same_ workflow runs unattended in CI — no duplicate variant required. ## The Problem Interactive step types (`choose`, `input`, `confirm`, `filter`, `file`, `write`) need a terminal to prompt you. In CI there is no TTY, so every one of them failed hard with `interactive terminal required for step`. The usual workaround was to fork your automation: an interactive version for humans and a flag-driven version for pipelines. ## The Fix When an interactive step has a `default` and there is no TTY, Atmos now uses that default instead of erroring. With a TTY it still prompts (using the default as the pre-selected value). Without a default, the existing error stands — so you never silently deploy an unintended value. ```yaml workflows: build-manifests: steps: - name: account type: choose prompt: "Account" options: [dev, prod] default: !env STACK_ACCOUNT dev - name: tag type: input prompt: "Release tag" default: !env RELEASE_TAG latest - type: shell command: atmos kube build "{{ .steps.account.value }}/tao" --tag "{{ .steps.tag.value }}" ``` Locally, this prompts. In CI, it reads `STACK_ACCOUNT` / `RELEASE_TAG` (falling back to `dev` / `latest`), then runs the shell step with the captured values. ## Dynamic Defaults from the Environment Defaults can come from the environment with the [`!env`](/functions/yaml/env) function (`!env VAR fallback`) in a workflow step's `default`, `prompt`, `options`, and `placeholder`. [`!exec`](/functions/yaml/exec) is supported too. Custom commands defined in `atmos.yaml` already resolved `!env`; workflows now do as well. Consuming those captured values in later `shell`/`atmos` steps (`{{ .steps..value }}`) works thanks to a companion fix that brought workflow command templating to parity with custom command steps. ## How to Use It - Give each interactive step a `default` so it can run unattended. - Use `!env VAR fallback` to pull that default from the environment in CI. - Leave `default` off when a human must choose — the step still errors in CI, by design. The behavior is automatic based on TTY detection; there is no new flag to set. It applies to both workflows and custom commands, which share the same step engine. For usage and configuration, see [interactive](/workflows/steps/interactive). ## Get Involved Try converting one of your prompt-driven workflows to run in CI by adding `default` values, and let us know how it goes in [GitHub Discussions](https://github.com/cloudposse/atmos/discussions). --- ## Interactive Component and Stack Selection for Terraform Commands Terraform commands now feature interactive prompts for component and stack selection. Run [`atmos terraform plan`](/cli/commands/terraform/plan) without arguments and get an intuitive selector instead of an error message. ## What Changed All 22 terraform commands that require component and stack arguments now support interactive selection when these values are missing. This brings the [interactive prompts feature](/changelog/interactive-flag-prompts) to the most frequently used Atmos commands. ```bash $ atmos terraform plan ? Choose a component > vpc eks-cluster rds-aurora ? Choose a stack > ue2-dev ue2-prod uw2-staging ``` Previously, running `atmos terraform plan` without arguments resulted in an unhelpful error message requiring you to already know your component and stack names. ## Why This Matters ### For Newcomers When you join a new company or start working with an unfamiliar codebase, the hardest part is often discovering what's available. What components exist? What stacks are configured? Interactive prompts eliminate the need to dig through documentation or ask colleagues for component names. ```bash # Day 1 at a new job? Just run the command $ atmos terraform plan # See all available components, pick one, then see matching stacks ``` ### For Experienced Users Even for veterans, interactive selection reduces friction: - **No more typos** - Select from a list instead of typing `infrastructure/vpc` from memory - **Context-aware filtering** - After selecting a component, the stack list shows only stacks containing that component - **Terminal history visibility** - Selections are displayed after choosing, so your shell history shows what you ran ### For Onboarding Interactive prompts serve as built-in documentation. New team members can explore what infrastructure exists without any prior knowledge. This significantly reduces onboarding time and the burden on senior engineers to answer "what components do we have?" questions. ## How It Works Interactive prompts appear when all conditions are met: 1. **Missing component or stack** - You didn't provide required arguments 2. **Interactive terminal** - Running in a TTY (not piped or in CI) 3. **`--interactive` enabled** - On by default, disable with `--interactive=false` ### Smart Stack Filtering When you select a component first, the stack selector automatically filters to show only stacks that contain that component: ```bash $ atmos terraform plan ? Choose a component > vpc # Select vpc ? Choose a stack # Only shows stacks with vpc configured > ue2-dev ue2-prod ``` ### Selection Feedback After making a selection, Atmos displays what you chose: ```bash $ atmos terraform plan ? Choose a component > vpc i Selected component `vpc` ? Choose a stack > ue2-dev i Selected stack `ue2-dev` # Terraform plan runs... ``` This ensures your terminal history captures exactly what was executed. ### Cancel with ESC Changed your mind? Press ESC or Ctrl+C to cancel at any point: ```bash $ atmos terraform plan ? Choose a component > vpc # Press ESC ! Selection cancelled ``` ## Supported Commands All terraform commands that route through the standard execution path now support interactive prompts: | Command | Description | |---------|-------------| | `plan` | Show execution plan | | `apply` | Apply changes | | `deploy` | Plan and apply | | `destroy` | Destroy infrastructure | | `init` | Initialize working directory | | `validate` | Validate configuration | | `output` | Show outputs | | `refresh` | Update state | | `show` | Inspect state | | `console` | Interactive console | | `state` | State management | | `import` | Import resources | | And 10 more... | | Custom commands like `shell`, `clean`, and `generate varfile/backend/planfile` also support interactive prompts. ## Disabling Interactive Prompts For scripts and automation, disable prompts: ```bash # Via flag atmos terraform plan --interactive=false # Via environment variable export ATMOS_INTERACTIVE=false # Via atmos.yaml settings: interactive: false ``` Interactive prompts are automatically disabled in CI environments (detected via `CI` environment variable) and when output is piped. For usage and configuration, see [atmos terraform apply](/cli/commands/terraform/apply). ## Get Involved - Share your feedback in the [Atmos Community Slack](https://cloudposse.com/slack) - [Report issues](https://github.com/cloudposse/atmos/issues) on GitHub --- ## Introducing Atmos AI: Your Infrastructure-Aware AI Assistant We're excited to introduce **Atmos AI**, an intelligent assistant built directly into Atmos CLI that understands your infrastructure-as-code like no other AI assistant can. Unlike general-purpose AI coding assistants, Atmos AI has deep, native understanding of Atmos stacks, components, inheritance patterns, and infrastructure workflows. It's not just an AI that knows about code—it's an AI that truly understands _your infrastructure_. With support for 7 AI providers (including local/offline Ollama), persistent sessions with full conversation memory, tool execution with granular permissions and persistent permission cache, specialized skills for specific tasks, and seamless IDE integration via MCP—Atmos AI brings the productivity patterns of industry-leading AI systems to infrastructure management. ## The Problem Infrastructure-as-code management is complex. Engineers lose hours searching documentation, debugging YAML configurations, understanding stack inheritance across dozens of files, and onboarding team members. The problem isn't lack of tools—it's the **cognitive overhead** of managing complex infrastructure. ## The Solution: Atmos AI Atmos AI solves this through **infrastructure-aware intelligence**. It's like having an expert Atmos engineer available 24/7, ready to analyze your stacks, validate configurations, answer questions, and help with best practices. ### See It in Action Ask a question about your infrastructure and Atmos AI automatically inspects your stacks, components, and configuration: ```shell $ atmos ai ask "what stacks and components do we have in the infra?" 👽 Thinking... I'll list all the available stacks and components in your infrastructure. Based on the infrastructure configuration, here are the stacks and components: ## Stacks (4 total) 1. **ue1-network** - Network stack in us-east-1 2. **ue1-prod** - Production stack in us-east-1 3. **uw2-network** - Network stack in us-west-2 4. **uw2-prod** - Production stack in us-west-2 ## Components by Stack ### ue1-network • tgw/attachment - Transit Gateway attachment • tgw/hub - Transit Gateway hub • vpc - Virtual Private Cloud ### ue1-prod • tgw/attachment - Transit Gateway attachment • vpc - Virtual Private Cloud ### uw2-network • tgw/attachment - Transit Gateway attachment • tgw/cross-region-hub-connector - Cross-region Transit Gateway connector • vpc - Virtual Private Cloud ### uw2-prod • tgw/attachment - Transit Gateway attachment • vpc - Virtual Private Cloud ## Summary Your infrastructure is organized across two AWS regions (us-east-1 and us-west-2), with dedicated network and production stacks in each region. The network stacks contain Transit Gateway hubs for network connectivity, while the production stacks use Transit Gateway attachments to connect to the network infrastructure. All stacks include VPC components for network isolation. ``` ### What Makes Atmos AI Different? - **Deep Atmos Understanding** — Knows stack structure, inheritance patterns, component relationships, and provides context-aware recommendations. - **Full Conversation Memory** — Remembers entire chat history within sessions. Resume conversations days or weeks later with full context. - **Tool Execution** — Analyzes infrastructure automatically via read-only operations, real-time YAML/Terraform validation, and a granular permission system. - **Multi-Provider Support** — 7 providers including local/offline Ollama. Switch providers mid-conversation with Ctrl+P. - **Persistent Sessions** — SQLite-backed storage with named sessions, auto-compact, and cross-platform support. - **Non-Interactive Execution** — Run AI prompts programmatically for scripting and CI/CD with structured JSON output. ## Key Features ### 1. Infrastructure-Aware Intelligence Atmos AI has **native tools** to inspect your infrastructure: ```bash You: What VPC CIDR does production use? AI: Let me check your configuration... [Executes: atmos describe component vpc -s prod-use1-network] Your production VPC uses CIDR 10.2.0.0/16 with public subnets, private subnets, and NAT Gateways enabled in all AZs. ``` Available tools include `atmos_describe_component`, `atmos_list_stacks`, `atmos_validate_stacks`, `validate_file_lsp`, file operations, and web search. See [Tool System documentation](/cli/configuration/ai/tools) for the full list. ### 2. Real-Time Validation with LSP Atmos AI integrates with Language Server Protocol to provide **IDE-quality validation** directly in the chat — catching typos, deprecated properties, and schema violations in YAML, Terraform, and HCL files. ### 3. Persistent Sessions with Full Memory Unlike basic chatbots that forget context, Atmos AI **remembers everything** within a session. Start an architecture discussion on Monday, resume Tuesday with full context, and reference earlier decisions a week later — all with the same named session. ```bash atmos ai chat --session vpc-migration ``` Sessions are stored in SQLite with visual session picker (Ctrl+L), provider awareness, and auto-compact for extended conversations. See [Sessions documentation](/cli/configuration/ai/sessions). ### 4. Specialized AI Skills Atmos AI provides **21+ specialized skills** you can install from the marketplace: ```bash # Install all official skills with one command atmos ai skill install cloudposse/atmos ``` Skills include **atmos-terraform**, **atmos-stacks**, **atmos-validation**, **atmos-components**, **atmos-config**, and many more — each with tailored prompts and tool access for its domain. **Switch skills with Ctrl+A** during conversations! See [AI Skills documentation](/cli/configuration/ai/skills). ### 5. Multi-Provider Support Choose the right AI for your needs: | Provider | Best For | Privacy | |----------|----------|---------| | **Anthropic (Claude)** | Complex reasoning, analysis | Cloud | | **OpenAI (GPT)** | Code generation, refactoring | Cloud | | **Google (Gemini)** | Large context windows | Cloud | | **xAI (Grok)** | Real-time knowledge | Cloud | | **Ollama (Local)** | **Complete privacy, offline** | **100% Local** | | **AWS Bedrock** | Enterprise, AWS-native | AWS | | **Azure OpenAI** | Enterprise, Azure-native | Azure | [Ollama](/cli/configuration/ai/providers#ollama-installation) runs AI models **entirely on your machine** — zero API costs, complete privacy, offline capable, and compliance ready. Enterprise teams can use AWS Bedrock or Azure OpenAI for data residency, VPC isolation, and audit logging. See [AI Providers documentation](/cli/configuration/ai/providers) for setup instructions. ### 6. Project Instructions (ATMOS.md) Provide **project-specific context** to the AI across all sessions via an `ATMOS.md` file — human-readable Markdown that's version-controlled with your repo. Include your organization's naming conventions, common commands, stack patterns, and CIDR allocations. See [Project Instructions documentation](/cli/configuration/ai/instructions). ### 7. Model Context Protocol (MCP) Integration Use Atmos tools from **any MCP-compatible client** — Claude Desktop, VSCode/Cursor, or custom clients: ```json { "mcpServers": { "atmos": { "command": "atmos", "args": ["mcp", "start"] } } } ``` Learn more: [MCP Server documentation](/ai/mcp-server) ### 8. Permission System A **three-tier security model** protects your infrastructure: - **Allowed Tools** — Execute without prompting (e.g., `atmos_describe_component`, `atmos_list_stacks`, `read_file`) - **Restricted Tools** — Require confirmation (e.g., `edit_file`, `write_stack_file`, `write_component_file`) - **Blocked Tools** — Never execute (e.g., `execute_bash_command`, `execute_atmos_command`) Permission decisions persist across sessions in `.atmos/ai.settings.local.json`, reducing prompt fatigue by 80%+. Every tool execution is logged with timestamp, user, and context. See [Tool System documentation](/cli/configuration/ai/tools) for configuration details. ### 9. Non-Interactive Execution and CI/CD Integration Execute AI prompts programmatically with structured output: ```bash # Simple execution atmos ai exec "List all production stacks" # JSON output for parsing atmos ai exec "Analyze VPC configuration" --format json > analysis.json # CI/CD integration result=$(atmos ai exec "Check for security issues" --format json) if echo "$result" | jq -e '.success == false'; then exit 1 fi ``` **JSON Output Structure:** ```json { "success": true, "response": "Analysis complete...", "tool_calls": [{"tool": "atmos_list_stacks", "success": true}], "tokens": {"prompt": 120, "completion": 80, "cached": 50}, "metadata": {"model": "claude-sonnet-4-6", "provider": "anthropic"} } ``` Supports multiple output formats (JSON, text, markdown), standard exit codes, stdin piping, and session context for multi-turn scripts. Learn more: [atmos ai exec documentation](/cli/commands/ai/exec) ### 10. Token Caching for Cost Savings Atmos AI supports **prompt caching** to dramatically reduce API costs — up to 90% savings by reusing frequently-sent content like system prompts and project instructions. | Provider | Caching Discount | |----------|-----------------| | **Anthropic** | 90% | | **OpenAI / Azure** | 50% | | **Gemini** | Free | | **Grok** | 75% | | **Bedrock** | Up to 90% | Most providers cache automatically. For Anthropic, enable explicit cache markers in `atmos.yaml`: ```yaml ai: providers: anthropic: cache: enabled: true cache_system_prompt: true cache_project_instructions: true ``` Learn more: [Token Caching documentation](/cli/configuration/ai/providers#token-caching) ## Getting Started ### 1. Configure Atmos AI Add to your `atmos.yaml`: **File:** `atmos.yaml` ```yaml ai: enabled: true default_provider: "anthropic" providers: anthropic: model: "claude-sonnet-4-6" api_key: !env "ANTHROPIC_API_KEY" ``` ### 2. Set Up Your Provider ```bash # For Claude (Anthropic) export ANTHROPIC_API_KEY="sk-ant-..." # For Ollama (Local/Offline) - no API key needed ollama pull llama4 ``` See [AI Providers](/cli/configuration/ai/providers) for all provider setup instructions. ### 3. Start Using Atmos AI ```bash # Interactive chat atmos ai chat # Named session atmos ai chat --session infrastructure-review # Quick question atmos ai ask "What components are in production?" # Non-interactive execution atmos ai exec "List all production stacks" --format json # MCP server for Claude Desktop atmos mcp start ``` ## What's Next? We're continuously improving Atmos AI. Here's what's shipped and what's coming: **Recently Completed:** - Non-Interactive Execution ([`atmos ai exec`](/cli/commands/ai/exec)) - Structured JSON Output with standard exit codes - Token Caching (Prompt Caching) — up to 90% cost savings - Conversation Checkpointing — export/import sessions - Automatic Context Discovery with .gitignore support - Skill Marketplace — install community skills from the Agent Skills registry **Coming Soon:** - Enhanced LSP (HCL, JSON Schema) - Advanced Analytics — token usage tracking, cost analysis - Multi-Skill Workflows — skill delegation and collaboration - IDE Plugins — native VSCode/JetBrains integration - Private Skill Registries and advanced security ## Learn More - [Configuration Guide](/cli/configuration/ai) - Complete configuration reference - [AI Providers](/cli/configuration/ai/providers) - All 7 providers with setup instructions - [Tool System](/cli/configuration/ai/tools) - Tool execution and permissions - [AI Skills](/cli/configuration/ai/skills) - Marketplace-installed skills - [Sessions](/cli/configuration/ai/sessions) - Session management and auto-compact - [Project Instructions](/cli/configuration/ai/instructions) - ATMOS.md documentation - [MCP Server](/ai/mcp-server) - Claude Desktop integration - [Troubleshooting](/ai/troubleshooting) - Common issues and solutions - [Atmos Documentation](https://atmos.tools) **Get Involved:** - [GitHub Issues](https://github.com/cloudposse/atmos/issues) - [Community Discussions](https://github.com/cloudposse/atmos/discussions) --- Happy infrastructure engineering! For usage and configuration, see [Atmos AI](/ai). --- ## Introducing Atmos Auth: Native Cloud Authentication for Platform Teams We're introducing [`atmos auth`](/cli/commands/auth/usage) - native cloud authentication built directly into Atmos. After years of solving the same authentication problems repeatedly across different tools and teams, we've built a solution that works whether you adopt the entire Atmos framework or just need better credential management. ## The Problem We're Solving Platform teams face a persistent authentication challenge: **there's no unified, configuration-as-code approach to managing cloud credentials**. Teams typically resort to: - **Standalone tools** like [Leapp](https://www.leapp.cloud/) - the closest alternative we've found, but requires a separate GUI application - **Manual credential management** - copying and pasting temporary credentials, managing profiles across team members - **Wiki-based documentation** - maintaining wiki pages with authentication instructions that quickly become outdated - **Multiple point solutions** - aws-vault for AWS, different tools for Azure, GCP, etc. This creates several pain points: 1. **No shared configuration** - Each team member configures authentication independently, leading to inconsistencies and support burden 2. **Context switching** - Jumping between credential managers, browsers, and CLI tools breaks workflow 3. **Configuration drift** - When access requirements change, teams must update wikis, Slack messages, and individual setups 4. **Framework lock-in** - Authentication solutions are often tied to specific tools or workflows Over the years, we've been heavily inspired by tools like [aws-vault](https://github.com/99designs/aws-vault), [aws2saml](https://github.com/Versent/saml2aws), and other utilities we cut our teeth on. These tools solved specific problems well, but we kept reimplementing authentication for each new project, cloud provider, or workflow. ## Why We Built This Authentication is **integral to every platform team's ability to deliver infrastructure**. When your team spends time debugging credential issues, updating wiki pages, or helping teammates configure access, that's time not spent delivering value. We were tired of solving the same problem over and over. So we built native authentication into Atmos, following these principles: - **Configuration as code** - Authentication config lives in `atmos.yaml` alongside your infrastructure - **Shared by default** - Commit once, everyone on the team uses the same configuration - **Cloud-agnostic** - Works with AWS IAM Identity Center, SAML providers, and extensible to other providers - **Standalone or integrated** - Use it even if you don't adopt the whole Atmos framework ## What Atmos Auth Provides ### Native AWS IAM Identity Center Support ```yaml auth: providers: company-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://company.awsapps.com/start/ identities: prod-admin: kind: aws/permission-set via: provider: company-sso principal: name: "AdministratorAccess" account: name: "production" ``` ### Simple Authentication Flow ```bash # Authenticate once atmos auth login # Verify who you are atmos auth whoami # Use with Terraform, Helmfile, or any tool atmos terraform plan vpc -s prod ``` ### Component-Level Authentication Different components can use different identities: ```yaml components: terraform: vpc: settings: auth: identity: network-admin database: settings: auth: identity: data-admin ``` ### Credential Security - **Browser-based SSO flow** - Leverages your existing IAM Identity Center authentication - **Temporary credentials** - Short-lived credentials that expire automatically - **OS keyring integration** - Optionally stores refresh tokens in macOS Keychain, Linux Secret Service, or Windows Credential Manager - **No static credentials** - Never stores long-term access keys ## Getting Started ### 1. Configure Authentication in `atmos.yaml` ```yaml auth: providers: my-company-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://mycompany.awsapps.com/start/ identities: my-identity: default: true kind: aws/permission-set via: provider: my-company-sso principal: name: "PowerUserAccess" account: name: "development" ``` ### 2. Authenticate ```bash atmos auth login ``` This opens your browser for IAM Identity Center authentication, then stores temporary credentials locally. ### 3. Use with Your Infrastructure ```bash # Terraform atmos terraform plan -s # Helmfile atmos helmfile deploy -s # Export credentials for other tools eval $(atmos auth env) ``` ## Use It Standalone You don't need to adopt Atmos's stack management, workflows, or component architecture to use `atmos auth`. Install Atmos, configure `atmos.yaml` with just the `auth` section, and use it for credential management: ```bash # Just for authentication atmos auth login eval $(atmos auth env) # Now use any AWS tool aws s3 ls aws ecs list-clusters kubectl get pods ``` ## What's Next We're continuing to expand authentication capabilities: - Additional provider types (Azure AD, GCP, generic SAML) - Enhanced session management - Credential caching optimizations - IDE integrations ## Documentation and Support - [Authentication User Guide](/cli/commands/auth/usage) - [Command Reference](/cli/commands/auth/login) - [Migrating from Leapp](/tutorials/migrating-from-leapp) - [Configuring Geodesic](/tutorials/configuring-geodesic) ## Get Involved Authentication is critical infrastructure for platform teams. If you have feedback, feature requests, or want to contribute: - Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues) - Share your use cases in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) - Contribute provider implementations or enhancements --- **Ready to try it?** Install Atmos v1.194.1 or later and configure authentication in your `atmos.yaml`. You can use it standalone for credential management or as part of your complete Atmos infrastructure workflow. --- ## Introducing atmos auth list: Visualize Your Authentication Configuration We're excited to announce a powerful new command for managing authentication in Atmos: [`atmos auth list`](/cli/commands/auth/list). This command provides comprehensive visibility into your authentication configuration, making it easier than ever to understand and manage complex authentication chains across multiple cloud providers and identities. See it in action: [View the full example](/examples/demo-auth) ## Why atmos auth list? As cloud infrastructure grows more complex, so does authentication management. Modern teams often work with: - **Multiple cloud providers** (AWS, Azure, GCP, Okta) - **Complex role assumption chains** (SSO → base role → admin role → specific account) - **Multiple identities per environment** (dev, staging, production) - **Team-specific access patterns** (developer, operator, security auditor) Without proper tooling, it becomes difficult to answer simple questions like: - "What authentication providers do we have configured?" - "Which identities can I use to access production?" - "How does this identity authenticate? Through which provider?" - "What's the complete authentication chain for this admin role?" `atmos auth list` solves these challenges by providing clear, actionable visibility into your entire authentication configuration. ## Key Features ### 🎨 Multiple Output Formats **Table Format (Default)** Perfect for quick overviews with formatted tables showing key attributes: ```shell atmos auth list ``` **Tree Format** Visualize hierarchical relationships and authentication chains: ```shell atmos auth list --format tree ``` **JSON/YAML Export** Integrate with scripts and automation tools: ```shell atmos auth list --format json | jq '.identities' atmos auth list --format yaml > auth-config.yml ``` **Graph Visualization** Generate diagrams for documentation: ```shell atmos auth list --format graphviz > auth-chain.dot atmos auth list --format mermaid > auth-chain.mmd atmos auth list --format markdown > docs/auth-config.md ``` ### 🔍 Smart Filtering Filter by providers or identities to focus on what matters: ```shell # Show only AWS SSO providers atmos auth list --providers=aws-sso # View specific identities atmos auth list --identities=admin,developer # Show all providers (no identities) atmos auth list --providers ``` ### 🔗 Authentication Chain Visualization Understand complex authentication flows at a glance. Chains show the complete path from provider to target identity: ```text aws-sso → base-role → admin-role → prod-account ``` This makes it immediately clear: - Which provider authenticates you initially - What roles you assume along the way - The final identity you end up with ### 🎯 Real-World Examples #### Quick Overview ```shell $ atmos auth list PROVIDERS NAME KIND REGION START URL DEFAULT aws-sso aws-sso us-east-1 https://example.awsapps.com/start ✓ okta okta https://example.okta.com IDENTITIES NAME KIND VIA PROVIDER VIA IDENTITY DEFAULT ALIAS admin aws/assume-role aws-sso ✓ prod-admin developer aws/assume-role aws-sso dev ops aws/assume-role aws-sso admin ops-admin ``` #### Detailed Tree View ```shell $ atmos auth list --format tree Authentication Configuration ├─ aws-sso (aws-sso) [DEFAULT] │ ├─ Region: us-east-1 │ ├─ Start URL: https://example.awsapps.com/start │ └─ Identities │ ├─ admin (aws/assume-role) [DEFAULT] [ALIAS: prod-admin] │ │ ├─ Principal │ │ │ └─ arn: arn:aws:iam::123456789012:role/AdminRole │ │ └─ ops (aws/assume-role) [ALIAS: ops-admin] │ │ └─ Principal │ │ └─ arn: arn:aws:iam::987654321098:role/OpsRole │ └─ developer (aws/assume-role) [ALIAS: dev] │ └─ Principal │ └─ arn: arn:aws:iam::123456789012:role/DeveloperRole └─ okta (okta) └─ URL: https://example.okta.com ``` The tree format shows the hierarchical relationship between providers and identities. Identities that authenticate through a provider appear as children under that provider's "Identities" section. Identity chains (where one identity assumes another) are shown as nested children - notice how `ops` appears as a child of `admin` since it authenticates via the `admin` identity. #### Automation Integration ```shell # Export to JSON for CI/CD validation atmos auth list --format json | jq -r '.providers | keys[]' # Generate documentation atmos auth list --format yaml > docs/auth-config.yml # Check if specific provider exists atmos auth list --providers=aws-sso --format json | jq -e '.providers["aws-sso"]' ``` ## Understanding Authentication Chains One of the most powerful features is authentication chain visualization. Chains show how identities authenticate through providers or other identities: - **Simple chain**: `aws-sso → admin` Direct authentication through AWS SSO - **Multi-step chain**: `aws-sso → base-role → admin-role` Authenticate via SSO, assume base role, then assume admin role - **Complex chain**: `okta → aws-dev → prod-account → admin` Authenticate through Okta, assume AWS dev role, switch to prod account, become admin These chains can be arbitrarily long, supporting even the most complex enterprise authentication scenarios. ## Integration with Existing Commands `atmos auth list` complements the existing authentication commands: - **[`atmos auth whoami`](/cli/commands/auth/whoami)** - See your current authentication status - **[`atmos auth login`](/cli/commands/auth/login)** - Authenticate with a provider - **`atmos auth list`** - **NEW!** View all available providers and identities - **[`atmos auth validate`](/cli/commands/auth/validate)** - Validate authentication configuration - **[`atmos auth env`](/cli/commands/auth/env)** - Export credentials as environment variables Together, these commands provide a complete authentication workflow from discovery to usage. ## Get Started `atmos auth list` is available in Atmos `v1.195.0` and later. To get started: 1. **Upgrade Atmos** to the latest version 2. **List your configuration**: Run `atmos auth list` 3. **Explore the formats**: Try `--format tree`, `json`, and `yaml` 4. **Filter as needed**: Use `--providers` and `--identities` to focus For full documentation, see the [atmos auth list command reference](/cli/commands/auth/list). ## What's Next? The `atmos auth list` command is part of our broader authentication management initiative. Coming soon: - **[`atmos auth logout`](/cli/commands/auth/logout)** - Cleanly terminate authentication sessions and clear cached credentials - **[`atmos auth shell`](/cli/commands/auth/shell)** - Launch an authenticated shell session with credentials automatically configured - **Interactive identity selection** - Enhanced `atmos auth login` with improved identity selection and TTY dialogs - **AWS SSO improvements** - Better user experience with spinners and interactive prompts for AWS SSO authentication Together, these enhancements will provide an even more seamless authentication workflow from discovery to usage. We'd love to hear your feedback! Let us know what you think on [GitHub](https://github.com/cloudposse/atmos) or join our [community Slack](https://slack.cloudposse.com/). --- _Happy authenticating! 🔐_ --- ## Introducing atmos auth shell: Isolated Shell Sessions for Secure Multi-Identity Workflows We're excited to introduce [`atmos auth shell`](/cli/commands/auth/shell), a new command that makes working with multiple cloud identities more secure. This command launches isolated shell sessions scoped to specific cloud identities. Think of it like `aws-vault exec`, but for all your cloud identities managed by Atmos—AWS, Azure, GCP, GitHub, SAML, and more. When you exit the shell, you return to your parent shell where those credentials were never present. It's a simple pattern that helps prevent credential leakage and reduces the risk of running commands against the wrong environment. ## The Problem The traditional approach to managing cloud credentials—storing multiple profiles in `~/.aws/credentials` or exporting variables in your main shell—is fundamentally unsafe. When all credentials are accessible simultaneously in a single shell session, it's dangerously easy to accidentally run commands against the wrong environment: - You think you're in `dev`, but you're actually authenticated to `prod` - You modify a staging database when you meant to update a development one - You deploy infrastructure changes to the wrong AWS account **Credential leakage is a real problem.** When you export credentials to your shell environment, they persist until you explicitly unset them or close the terminal. This creates dangerous scenarios: ```shell # Traditional workflow - credentials persist indefinitely eval $(atmos auth env --identity prod-admin --format bash) aws s3 ls # ... hours later, you forget which identity is active ... kubectl delete deployment critical-service # 😱 Which cluster am I in?! ``` Without clear session boundaries, you have no built-in protection against: - Running commands with the wrong credentials - Leaving privileged credentials active in your shell for hours - Mixing operations across multiple environments in a single terminal ## The Solution: `atmos auth shell` The new `atmos auth shell` command solves this through isolation. Each shell session is scoped to exactly one identity with one set of credentials. When you run: ```shell atmos auth shell --identity prod-admin ``` You get a dedicated shell where **only** those production credentials are available. When you exit, your parent shell's environment remains completely unchanged—no credential pollution, no confusion about which environment you're operating in. This isolation enables a useful workflow: **run multiple authenticated shells simultaneously, each scoped to a different identity**: ```shell # Terminal 1: Production operations atmos auth shell --identity prod-admin # Terminal 2: Staging testing atmos auth shell --identity staging-dev # Terminal 3: Development work atmos auth shell --identity dev-sandbox ``` Each terminal operates in complete isolation. You can't accidentally run a production command in your development shell, and vice versa. Your credentials are **session-scoped**—when you close the shell, it's destroyed and you return to your parent shell where those credentials don't exist. No lingering environment variables, no shared credential files with overly broad access. The command supports multiple identities, custom shells ([`--shell`](/cli/commands/auth/shell#flags)), custom arguments (`--`), shell nesting tracking (`ATMOS_SHLVL`), and works cross-platform. :::tip Customize Your Prompt Want to see which identity is active in your prompt? You can customize your shell prompt by adding `ATMOS_IDENTITY` or `ATMOS_SHLVL` to your `PS1` (bash) or equivalent prompt variable in your shell configuration file (e.g., `.bashrc`, `.zshrc`). Example for bash: ```bash export PS1='[\u@\h \W${ATMOS_IDENTITY:+ ($ATMOS_IDENTITY)}]\$ ' ``` ::: ## Real-World Use Cases ### Safe Production Operations ```shell atmos auth shell --identity prod-admin # Now in authenticated shell, scoped ONLY to production aws s3 sync ./assets s3://prod-bucket/ aws ecs update-service --cluster prod --service api --force-new-deployment aws rds describe-db-instances --region us-east-1 exit # Back to parent shell—credentials never existed here ``` When you exit, you're back in your parent shell where those production credentials don't exist. You can't accidentally run a production command after switching contexts. ### Debugging Cloud Resources ```shell atmos auth shell --identity dev-debug # Isolated shell with ONLY dev credentials aws logs tail /aws/lambda/my-function --follow kubectl get pods -n development aws dynamodb scan --table-name dev-users exit ``` ### Long-Running Operations with Multiple Steps ```shell atmos auth shell --identity migration-role # Run multiple migration steps without re-authenticating ./migrate-database.sh ./update-dns-records.sh aws s3 sync ./backups s3://migration-bucket/ ./verify-migration.sh exit ``` ### Multiple Concurrent Environments Open multiple terminals, each with different credentials: ```shell # Terminal 1: Monitoring production atmos auth shell --identity prod-readonly watch aws cloudwatch get-metric-statistics ... # Terminal 2: Testing in staging atmos auth shell --identity staging-admin terraform apply -target=module.test_feature # Terminal 3: Development work atmos auth shell --identity dev-sandbox aws s3 cp ./test-data s3://dev-uploads/ ``` Each terminal has **exactly one set of credentials**. No confusion, no accidents. ## How It Works Under the hood, `atmos auth shell`: 1. **Authenticates** with your configured identity provider 2. **Retrieves** temporary credentials from your cloud provider 3. **Writes** credentials to Atmos-managed configuration files 4. **Sets** environment variables pointing to these config files (e.g., `AWS_SHARED_CREDENTIALS_FILE`, `AWS_CONFIG_FILE`, `AWS_PROFILE`) 5. **Sets** `ATMOS_IDENTITY` to track which identity is active 6. **Launches** your preferred shell with all environment variables configured 7. **Propagates** the shell's exit code back to Atmos when you exit ## Comparison to AWS Vault If you're familiar with `aws-vault exec`, you'll recognize this pattern. Both tools prioritize **temporary credentials** and **session isolation**: | Feature | aws-vault exec | atmos auth shell | |---------|---------------|------------------| | **Temporary credentials** | ✅ Yes | ✅ Yes | | **Isolated subshells** | ✅ Yes | ✅ Yes | | **Session boundaries** | ✅ Return to parent shell on exit | ✅ Return to parent shell on exit | | **Cloud providers** | AWS only | AWS, Azure, GCP, GitHub, SAML, and more | | **Integration** | Standalone tool | Part of Atmos infrastructure workflow | | **Identity management** | IAM profiles | Atmos identities (unified across clouds) | `atmos auth shell` brings the same security-first philosophy to **all your cloud identities**, not just AWS. If you manage infrastructure across multiple cloud providers, you get consistent, isolated authentication for everything—AWS SSO, Azure AD, GCP service accounts, GitHub Apps, and more. ## Environment Variables When you're in an `atmos auth shell` session, you'll have access to: - **ATMOS\_IDENTITY** The name of the active identity - **ATMOS\_SHLVL** Shell nesting level (increments for nested shells) - **AWS\_SHARED\_CREDENTIALS\_FILE** Path to Atmos-managed credentials file (AWS identities only) - **AWS\_CONFIG\_FILE** Path to Atmos-managed config file (AWS identities only) - **AWS\_PROFILE** Profile name corresponding to your identity (AWS identities only) These environment variables work seamlessly with any tool that uses the AWS SDK, such as Terraform, the AWS CLI, kubectl with AWS authentication, and more. Credentials are never exposed directly in environment variables—only secure file paths are set. ## When to Use `atmos auth shell` vs `atmos auth env` Both commands serve important but different purposes: **Use `atmos auth shell` when:** - You need **session isolation** for security - Working with production or sensitive environments - Running multiple concurrent sessions with different identities - You want credentials to **automatically expire** when you exit - You need clear boundaries between different cloud contexts **Use [`atmos auth env`](/cli/commands/auth/env) when:** - Integrating with CI/CD pipelines that need to export variables - Writing scripts that source environment variables - You need credentials in a specific format (bash, fish, etc.) - Automating workflows where a subshell isn't appropriate Think of it this way: `atmos auth shell` is for **interactive work** where safety and isolation matter. `atmos auth env` is for **automation and integration** where you need to export credentials to your existing environment. ## Getting Started The `atmos auth shell` command is available now in the latest version of Atmos. To start using it: 1. **Configure your identity** in `atmos.yaml`: ```yaml auth: providers: - type: aws name: aws-sso config: sso_start_url: https://your-org.awsapps.com/start sso_region: us-east-1 identities: - name: dev-user provider: aws-sso config: account_id: "123456789012" role_name: DeveloperRole - name: prod-admin provider: aws-sso config: account_id: "987654321098" role_name: AdminRole ``` 2. **Launch your authenticated shell**: ```shell atmos auth shell --identity dev-user ``` 3. **Start working** with your cloud resources! ## Learn More - [PR #1640](https://github.com/cloudposse/atmos/pull/1640) ## What's Next? We're continuously improving Atmos authentication capabilities. Future enhancements may include: - Session duration customization - Automatic credential refresh - Multi-cloud authentication in a single shell - Integration with external secret managers Have feedback or suggestions? [Open an issue](https://github.com/cloudposse/atmos/issues) or join the discussion in our community! --- Happy authenticating! 🚀 --- ## Introducing atmos init and atmos scaffold: Get Started in Seconds We're excited to announce two new commands that dramatically simplify getting started with Atmos: [`atmos init`](/cli/commands/init) and [`atmos scaffold`](/cli/commands/scaffold/usage). These commands eliminate the manual setup process and help you bootstrap new Atmos projects or generate infrastructure code in seconds. ## The Problem: Setup Friction Setting up a new Atmos project has traditionally required several manual steps: 1. Creating the project directory structure (`components/`, `stacks/`, `schemas/`) 2. Writing a complete `atmos.yaml` configuration file 3. Understanding the recommended patterns and conventions 4. Setting up initial stack files and component configurations For new users, this process could take hours and required deep knowledge of Atmos conventions. Even experienced users found themselves repeatedly creating similar boilerplate for new projects or infrastructure components. ## The Solution: Instant Project Creation ### `atmos init`: Bootstrap New Projects The `atmos init` command creates a complete Atmos project from built-in templates with a single command: ```bash # Interactive mode - guided setup with prompts $ atmos init ? Select a template: ❯ simple - Basic Atmos project structure atmos - Complete Atmos project with full configuration ? Enter project name: my-infrastructure ? Enter Terraform version: 1.5.0 ? Enter default AWS region: us-west-2 ? Enter target directory: ./my-infrastructure Initializing my-infrastructure in ./my-infrastructure ✓ atmos.yaml ✓ README.md ✓ stacks/.gitkeep ✓ components/terraform/.gitkeep Initialized 4 files. ``` For automation and CI/CD, use non-interactive mode: ```bash $ atmos init atmos ./my-project \ --set project_name=my-infra \ --set terraform_version=1.5.0 \ --set aws_region=us-east-1 \ --no-interactive ``` ### `atmos scaffold`: Generate Infrastructure Code The `atmos scaffold` commands help you generate infrastructure code from templates: ```bash # Generate from a local scaffold template $ atmos scaffold generate vpc-component ./components/terraform/vpc \ --set vpc_name=main \ --set cidr_block=10.0.0.0/16 Generating vpc-component in ./components/terraform/vpc ✓ main.tf ✓ variables.tf ✓ outputs.tf ✓ versions.tf Initialized 4 files. ``` List available scaffold templates: ```bash $ atmos scaffold list Available scaffold templates: Name Source Version Description ──────────────── ──────────────────────── ───────── ────────────────────────────── vpc-component ./scaffolds/vpc 1.0.0 AWS VPC component template eks-cluster ./scaffolds/eks 2.1.0 EKS cluster template rds-instance github.com/acme/rds.git 1.5.0 RDS database template ``` ## Key Features ### Built-in Templates The `atmos init` command includes two carefully crafted templates: **simple**: Perfect for getting started quickly - Minimal `atmos.yaml` configuration - Basic directory structure - Essential `.gitkeep` files **atmos**: Complete project setup - Full `atmos.yaml` with all sections - Directory structure for components, stacks, schemas, and workflows - Configured for multiple environments - Backend configuration examples ### Powerful Templating Both commands use Go templates with Gomplate functions, supporting: - **Conditional file generation**: `{{if .Config.enable_monitoring}}file.yaml{{end}}` - **Dynamic paths**: `{{.Config.namespace}}/config.yaml` - **Content templating**: `project: {{.Config.project_name}}` - **Rich functions**: `upper`, `lower`, `title`, `default`, and 200+ Gomplate functions ### Interactive and Automated Workflows **Interactive Mode** (default): - Guided prompts for template selection - User-friendly questions for configuration values - Preview of what will be created - Safe defaults for all values **Non-Interactive Mode** (automation): - Pass all values via `--set` flags - Perfect for CI/CD pipelines - Reproducible project creation - Scriptable workflows ### Extensible Scaffold System Configure custom scaffold templates in `atmos.yaml`: ```yaml scaffold: base_path: "./scaffolds" templates: vpc-component: description: "AWS VPC component template" source: "./scaffolds/vpc" version: "1.0.0" eks-cluster: description: "EKS cluster template" source: "github.com/acme/atmos-scaffolds/eks.git" version: "2.1.0" ref: "tags/v2.1.0" ``` ## Use Cases ### 1. Onboarding New Team Members New developers can have a working Atmos project in under 2 minutes: ```bash $ atmos init simple ./my-first-project $ cd my-first-project $ # Start adding components and stacks ``` ### 2. Starting New Infrastructure Projects Bootstrap production-ready projects with complete configurations: ```bash $ atmos init atmos ./prod-infrastructure \ --set project_name=acme-production \ --set aws_region=us-east-1 \ --set terraform_version=1.6.0 ``` ### 3. Generating Repetitive Components Create similar infrastructure components without copy-paste: ```bash # Generate VPC for dev environment $ atmos scaffold generate vpc-component ./components/terraform/vpc-dev \ --set vpc_name=dev \ --set cidr_block=10.0.0.0/16 # Generate VPC for prod environment $ atmos scaffold generate vpc-component ./components/terraform/vpc-prod \ --set vpc_name=prod \ --set cidr_block=10.1.0.0/16 ``` ### 4. Organization-Wide Standardization Create organization-specific scaffold templates that encode your team's best practices: ```bash # Team members use your custom scaffolds $ atmos scaffold generate acme-microservice ./services/api \ --set service_name=user-api \ --set team=platform ``` ## Creating Custom Scaffold Templates Scaffold templates are simple directories with a `scaffold.yaml` file: ```yaml # scaffolds/vpc/scaffold.yaml apiVersion: atmos/v1 kind: AtmosScaffoldConfig metadata: name: vpc-component description: AWS VPC component template author: Cloud Posse version: 1.0.0 spec: fields: - name: vpc_name label: VPC name type: input default: main - name: cidr_block label: CIDR block type: input default: 10.0.0.0/16 ``` Template files support Go templates: ```hcl # scaffolds/vpc/main.tf module "vpc" { source = "cloudposse/vpc/aws" version = "2.1.0" name = "{{.Config.vpc_name}}" cidr_block = "{{.Config.cidr_block}}" tags = { Name = "{{.Config.vpc_name}}" } } ``` ## Technical Highlights ### For Atmos Contributors These commands represent significant architectural improvements: 1. **Command Registry Pattern**: Both commands use the new command registry pattern, making them independently testable and maintainable 2. **Shared Core Packages**: - `pkg/init/ui` - Interactive UI components and prompts - `pkg/scaffold/templating` - Template processing engine - `pkg/init/config` - Scaffold configuration parsing 3. **Embedded Templates**: Built-in templates are embedded in the Atmos binary, ensuring version compatibility and eliminating external dependencies 4. **Comprehensive Testing**: Over 80% test coverage with unit and integration tests for all template processing logic See the [PRD documents](https://github.com/cloudposse/atmos/blob/main/docs/prd/atmos-init.md) for complete technical details. ## What's Next These commands lay the foundation for future enhancements: - **Template Marketplace**: Central registry of community scaffold templates - **Git Integration**: Clone templates directly from GitHub/GitLab repositories - **Template Validation**: Schema validation for scaffold.yaml files - **Template Composition**: Combine multiple templates into complex projects ## Get Started Today The `atmos init` and `atmos scaffold` commands are available in Atmos v1.97.0. Try them out: ```bash # Install or upgrade Atmos brew upgrade atmos # Create your first project atmos init # Explore scaffold templates atmos scaffold list ``` We'd love to hear your feedback! Join the discussion in our [GitHub Discussions](https://github.com/cloudposse/atmos/discussions) or share your custom scaffold templates with the community. ## Documentation - [atmos init command reference](/cli/commands/init) - [atmos scaffold command reference](/cli/commands/scaffold/usage) - [Creating Custom Scaffold Templates](/cli/commands/scaffold/generate) - [PRD: Init Command](https://github.com/cloudposse/atmos/blob/main/docs/prd/atmos-init.md) - [PRD: Scaffold Command](https://github.com/cloudposse/atmos/blob/main/docs/prd/atmos-scaffold.md) 🚀 Generated with [Atmos](https://atmos.tools) --- ## Introducing Atmos LSP: IDE-Native Infrastructure Configuration We're excited to introduce **Atmos LSP**, bringing IDE-quality features directly to your infrastructure configuration workflow—no context switching, no manual validation, no documentation hunting. Atmos LSP provides comprehensive Language Server Protocol integration that transforms how you write and validate Atmos configurations. Get instant feedback on errors, autocomplete for Atmos keywords, hover documentation without leaving your editor, and seamless integration with external language servers for YAML and Terraform validation. With support for 13+ editors (VS Code, Neovim, Zed, Cursor, Emacs, and more), multiple transport protocols, and deep AI integration—writing infrastructure configuration now feels like writing code in a modern IDE. ## The Problem Infrastructure configuration is error-prone and time-consuming. Engineers constantly: - **Context switch** between editor and terminal to validate configurations - **Run commands manually** to check syntax errors ([`atmos validate stacks`](/cli/commands/validate/stacks)) - **Search documentation** for correct YAML structure and Atmos keywords - **Discover errors late** during terraform plan or apply - **Fix typos manually** without autocomplete assistance - **Navigate large configs** without structural overview Even simple typos in YAML can cause deployment failures that take hours to debug. The feedback loop is too long, and the cost of mistakes is too high. ## The Solution: Atmos LSP Atmos LSP brings **IDE-native infrastructure configuration** through the Language Server Protocol (LSP)—the same standard that powers modern code editors. **What You Get:** - **Real-time validation** as you type—catch errors before committing - **Autocomplete** for Atmos keywords, components, and variables - **Hover documentation** explains Atmos concepts without leaving your editor - **Multi-editor support**—works in VS Code, Neovim, Zed, Cursor, Emacs, and 8+ others - **External LSP integration**—unified YAML and Terraform validation - **AI integration**—enables AI assistants to validate configurations accurately ### Dual-Component Design Atmos LSP consists of two complementary components: **1. Atmos LSP Server** - Native Atmos-specific features - Autocomplete for Atmos keywords (`import`, `components`, `vars`, etc.) - Hover documentation for all Atmos concepts - Real-time Atmos-specific validation - Multi-transport support (stdio, TCP, WebSocket) **2. Atmos LSP Client** - External language server integration - Manages multiple LSP servers (yaml-language-server, terraform-ls) - Routes files to appropriate server by type - Aggregates diagnostics from all servers - Provides unified interface for AI tools ## Key Features ### 1. Real-Time Validation Get instant feedback as you type—no more waiting for `atmos validate stacks`: ```yaml # stacks/prod/vpc.yaml import: - stacks/base # ❌ Error: imports must be arrays components: terraform: vpc: vars: cidr_block: 10.0.0.0/16 # ✅ Valid vpc_ciddr: 10.1.0.0/16 # ⚠️ Warning: Unknown property # (did you mean 'cidr_block'?) availability_zones: # ⚠️ Warning: Property deprecated - us-east-1a # Use 'azs' instead ``` **Validation Triggers:** - Real-time as you type - On document save - On document open - Manual validation request **Validation Types:** - **YAML Syntax** - Malformed YAML detection - **Atmos Structure** - Stack schema validation - **Import Arrays** - Ensures imports are valid lists - **Component Sections** - Validates terraform/helmfile structure - **Type Safety** - Checks value types match expectations ### 2. Intelligent Autocomplete Stop memorizing Atmos keywords—let autocomplete guide you: ```yaml # Type 'com' and press Ctrl+Space com| ↓ # Autocomplete suggests: components: terraform: helmfile: ``` **Autocomplete Categories:** **Top-Level Keywords:** - `import` - Import other stack files - `components` - Define infrastructure components - `vars` - Stack variables - `settings` - Stack settings - `metadata` - Stack metadata **Component Types:** - `terraform` - Terraform components - `helmfile` - Helmfile components **Common Variables:** - `namespace`, `tenant`, `environment`, `stage`, `region` - `enabled` - Enable/disable pattern - `tags` - Resource tags **Settings:** - `spacelift` - Spacelift integration - `atlantis` - Atlantis integration - `validation` - Validation rules ### 3. Hover Documentation Get context-aware documentation without leaving your editor: **Hover over `import`:** ````markdown **import** Import other Atmos stack configuration files. Import paths are relative to the stacks directory. **Example:** ```yaml import: - catalog/vpc - mixins/kubernetes ```` **Note:** Imports are processed sequentially, and later imports can override values from earlier imports. ```` **Documented Keywords:** - Stack structure: `import`, `components`, `vars`, `settings`, `metadata` - Component types: `terraform`, `helmfile` - Stack variables: `namespace`, `tenant`, `environment`, `stage`, `region` - Common patterns: `enabled` flag, resource `tags` ### 4. Multi-Editor Support (13+ IDEs) Works with your favorite editor—same experience everywhere: | Editor | Status | Setup | |--------|--------|-------| | **VS Code** | Yes | LSP client extension | | **Neovim** | Yes | nvim-lspconfig | | **Cursor** | Yes | LSP client (VS Code fork) | | **Zed** | Yes | Built-in LSP config | | **Emacs** | Yes | lsp-mode | | **Vim** | Yes | vim-lsp plugin | | **Sublime Text** | Yes | LSP package | | **Helix** | Yes | languages.toml | | **IntelliJ IDEA** | Yes | LSP4IJ plugin | | **Kate** | Yes | LSP client plugin | | **Lapce** | Yes | Built-in LSP | | **Atom** | Yes | atom-languageclient | | **Eclipse** | Yes | LSP4E | **Universal Setup Pattern:** ```json { "atmos-lsp": { "command": "atmos", "args": ["lsp", "start"], "filetypes": ["yaml"] } } ```` ### 5. Multi-Transport Support Choose the right transport for your environment: **stdio (Default)** - Desktop IDE integration ```bash atmos lsp start ``` **TCP** - Remote development and testing ```bash atmos lsp start --transport tcp --address localhost:7777 ``` **WebSocket** - Web-based editors ```bash atmos lsp start --transport websocket --address localhost:7777 ``` ### 6. External LSP Server Integration Atmos LSP Client manages external language servers for unified validation: **Supported Servers:** | Server | Purpose | File Types | |--------|---------|------------| | **yaml-language-server** | YAML validation with JSON Schema | `.yaml`, `.yml` | | **terraform-ls** | Terraform HCL validation | `.tf`, `.tfvars`, `.hcl` | | **json-languageserver** | JSON validation | `.json` | | **Any LSP Server** | Extensible via config | Custom | **Configuration:** ```yaml # atmos.yaml lsp: enabled: true servers: yaml-ls: command: "yaml-language-server" args: ["--stdio"] filetypes: ["yaml", "yml"] initialization_options: yaml: validation: true hover: true completion: true terraform-ls: command: "terraform-ls" args: ["serve"] filetypes: ["tf", "tfvars", "hcl"] initialization_options: experimentalFeatures: validateOnSave: true ``` **Benefits:** - Single unified validation experience - Automatic file routing by extension - Diagnostic aggregation from multiple servers - Consistent error reporting ### 7. AI Integration Enable AI assistants to validate configurations with the `validate_file_lsp` tool: ```bash You: Validate stacks/prod/vpc.yaml AI: [Uses validate_file_lsp tool] Found 3 issues in stacks/prod/vpc.yaml: ERRORS (2): 1. Line 15, Col 5: Unknown property 'vpc_ciddr' Did you mean 'vpc_cidr'? 2. Line 23, Col 3: Invalid CIDR block format WARNINGS (1): 1. Line 30, Col 7: Property 'availability_zones' is deprecated Use 'azs' instead Would you like me to help fix these issues? ``` **AI Integration Features:** - Precise line and column numbers for errors - Clear error/warning separation - Suggested fixes when available - Summary statistics - Supports all AI providers (Claude, GPT, Gemini, Ollama, etc.) Learn more: [Atmos AI Documentation](/ai) ## Real-World Use Cases ### 1. Catch Errors Early **Before Atmos LSP:** ```bash # Edit stacks/prod/vpc.yaml in editor vim stacks/prod/vpc.yaml # Save and exit # Validate manually in terminal atmos validate stacks # ❌ Error: line 15: unknown property 'vpc_ciddr' # Go back to editor, fix, repeat... vim stacks/prod/vpc.yaml ``` **With Atmos LSP:** ```yaml # Edit stacks/prod/vpc.yaml # Error appears instantly as you type: vpc_ciddr: 10.0.0.0/16 # ⚠️ Unknown property (did you mean 'vpc_cidr'?) # Red squiggly line appears immediately # Fix it right away: vpc_cidr: 10.0.0.0/16 # ✅ Valid - green checkmark ``` **Result:** Catch typos in seconds, not minutes. ### 2. Learn Atmos Structure **Before:** ```bash # What top-level keywords are available? # Open documentation... # Search for "stack structure"... # Copy example... ``` **With Atmos LSP:** ```yaml # Start typing at root level # Press Ctrl+Space # Autocomplete shows: - import - components - vars - settings - metadata # Hover over any keyword for documentation ``` **Result:** Discover Atmos features without leaving your editor. ### 3. Unified YAML and Terraform Validation **Before:** ```bash # Validate YAML syntax yamllint stacks/prod/vpc.yaml # Validate Terraform cd components/terraform/vpc terraform validate # Check Atmos structure atmos validate stacks ``` **With Atmos LSP:** ```yaml # All validation happens automatically in editor: # - YAML syntax (yaml-language-server) # - Atmos structure (atmos lsp server) # - Terraform HCL (terraform-ls) # All errors shown inline with precise locations ``` **Result:** One unified validation experience. ### 4. AI-Powered Configuration Review **Before:** ```bash # Copy file contents # Paste into AI chat # Ask for validation # AI provides generic advice ``` **With Atmos LSP + AI:** ```bash You: Validate stacks/prod/vpc.yaml AI: [Uses validate_file_lsp tool for precise validation] Found 2 issues: 1. Line 15, Col 5: 'vpc_ciddr' should be 'vpc_cidr' 2. Line 30, Col 7: 'availability_zones' is deprecated I can fix both issues. Would you like me to: 1. Update vpc_ciddr → vpc_cidr 2. Migrate availability_zones → azs array Apply fixes? (yes/no) ``` **Result:** AI gets precise error locations and can suggest specific fixes. ## Getting Started ### 1. Start LSP Server (IDE Integration) **VS Code Setup:** Create `.vscode/settings.json`: ```json { "atmos-lsp": { "command": "atmos", "args": ["lsp", "start"], "filetypes": ["yaml"], "settings": { "atmos": { "configPath": "${workspaceFolder}/atmos.yaml" } } } } ``` Install a generic LSP client extension (like "LSP" by sublimelsp). **Neovim Setup:** Add to your config: ```lua require('lspconfig').atmos.setup{ cmd = { 'atmos', 'lsp', 'start' }, filetypes = { 'yaml' }, root_dir = function(fname) return require('lspconfig.util').root_pattern('atmos.yaml', '.git')(fname) end, } ``` **See Full Setup Guides:** - [VS Code setup](/lsp/lsp-server#editor-configuration) - [Neovim setup](/lsp/lsp-server#editor-configuration) - [13+ other editors](/lsp/lsp-server#editor-configuration) ### 2. Configure External LSP Servers (Optional) Add to `atmos.yaml` for YAML and Terraform validation: ```yaml lsp: enabled: true servers: # YAML validation yaml-ls: command: "yaml-language-server" args: ["--stdio"] filetypes: ["yaml", "yml"] root_patterns: ["atmos.yaml", ".git"] initialization_options: yaml: validation: true hover: true completion: true # Terraform validation terraform-ls: command: "terraform-ls" args: ["serve"] filetypes: ["tf", "tfvars", "hcl"] root_patterns: [".terraform", ".git"] initialization_options: experimentalFeatures: validateOnSave: true ``` **Install External Servers:** ```bash # Install yaml-language-server npm install -g yaml-language-server # Install terraform-ls # macOS brew install terraform-ls # Linux curl -LO https://releases.hashicorp.com/terraform-ls/latest/terraform-ls_latest_linux_amd64.zip unzip terraform-ls_latest_linux_amd64.zip sudo mv terraform-ls /usr/local/bin/ ``` ### 3. Start Editing Open any Atmos stack file: ```yaml # stacks/prod/vpc.yaml # Start typing - autocomplete appears com| ↓ (Ctrl+Space) components: # Hover for documentation import: # ← Hover here for docs - catalog/vpc # Real-time validation vpc_ciddr: 10.0.0.0/16 # ⚠️ Instant error vpc_cidr: 10.0.0.0/16 # ✅ Fixed ``` ## Advanced Features ### Diagnostic Formatting Three output formats for different use cases: **1. Full Format** - Human-readable ``` ERRORS (2): 1. Line 15, Col 5: Unknown property 'vpc_ciddr' (did you mean 'vpc_cidr'?) Source: yaml-language-server 2. Line 23, Col 3: Invalid CIDR block format Source: yaml-language-server WARNINGS (1): 1. Line 30, Col 7: Property 'availability_zones' is deprecated, use 'azs' Source: yaml-language-server ``` **2. Compact Format** - One line per issue ``` vpc.yaml:15:5: error: Unknown property 'vpc_ciddr' (yaml-ls) vpc.yaml:23:3: error: Invalid CIDR block format (yaml-ls) vpc.yaml:30:7: warning: Property 'availability_zones' is deprecated (yaml-ls) ``` **3. AI-Optimized Format** - Structured for AI ``` Found 3 issue(s) in /stacks/prod/vpc.yaml: ERRORS (2): 1. Line 15, Col 5: Unknown property 'vpc_ciddr' (did you mean 'vpc_cidr'?) 2. Line 23, Col 3: Invalid CIDR block format WARNINGS (1): 1. Line 30, Col 7: Property 'availability_zones' is deprecated, use 'azs' ``` ### Multi-Server Diagnostic Aggregation Diagnostics from all LSP servers combined: ```yaml # stacks/prod/eks.yaml import: - catalog/vpc # ✅ Atmos LSP: Valid import components: terraform: eks: vars: cluster_name: "prod" # ✅ yaml-ls: Valid YAML syntax cluster_versio: "1.27" # ⚠️ yaml-ls: Unknown property # ⚠️ Atmos LSP: Typo detection ``` **Aggregation Features:** - Collect from all servers - Deduplicate overlapping diagnostics - Sort by severity and location - Filter by severity level - Per-file diagnostic access ### Document Lifecycle LSP tracks your editing workflow: **1. Document Open** → Initial validation + publish diagnostics **2. Document Change** → Real-time re-validation (debounced) **3. Document Save** → Final validation **4. Document Close** → Clear diagnostics, free resources **Thread-Safe:** - Document collection protected with RWMutex - Concurrent access supported - No race conditions ## Performance **Validation Speed:** - Initial validation: \<100ms for typical stack files - Real-time updates: \<50ms (debounced) - Large files (>1000 lines): \<200ms **Resource Usage:** - Memory: ~10-50MB per server - CPU: Minimal (validation only on change) - Network: None (all local) **Optimizations:** - Debounced validation (avoid validation on every keystroke) - Async diagnostic publishing (non-blocking) - Thread-safe concurrent access ## What's Coming Next ### Short-term (Next 3 Months) **Go-to-Definition:** ```yaml import: - catalog/vpc # Ctrl+Click → Opens stacks/catalog/vpc.yaml components: terraform: vpc: component: vpc-module # Ctrl+Click → Opens components/terraform/vpc-module/ ``` **Document Symbols (Outline View):** ``` Outline: ├── imports (3) │ ├── catalog/vpc │ ├── catalog/eks │ └── mixins/common ├── components │ ├── terraform │ │ ├── vpc │ │ ├── eks │ │ └── rds └── vars ├── namespace ├── environment └── region ``` ### Medium-term (3-6 Months) **Find References:** ```yaml vars: vpc_id: vpc-123 # Find all references → Shows all files using vpc_id ``` **Rename Symbol:** ```yaml vars: old_name: value # Rename to new_name → Updates all usages across files ``` **Code Actions (Quick Fixes):** ```yaml vpc_ciddr: 10.0.0.0/16 # Quick fix: Change to 'vpc_cidr' ``` ### Long-term (6+ Months) **Cross-File Validation:** - Validate component references exist - Check variable consistency across stacks - Detect circular imports - Dependency analysis **Enhanced Schema Support:** - JSON Schema integration - Custom validation rules - Pluggable validators - User-defined schemas **Performance Improvements:** - Parsed document caching - Incremental text sync - Background processing - Metrics and profiling ## Security & Privacy **LSP Server:** - Read-only access to opened documents - No file system access beyond provided documents - No network access - Validates user input safely **LSP Client:** - Spawns only configured, trusted servers - Local stdio communication only - No network communication - No credential handling **Privacy:** - All processing 100% local - No telemetry or analytics - No document content sent to cloud - External servers may have own policies (check yaml-ls, terraform-ls docs) ## Troubleshooting ### LSP Server Not Starting **Check:** ```bash # Verify atmos is in PATH which atmos # Test LSP server manually atmos lsp start # Should wait for stdin (Ctrl+C to exit) # Check Atmos version (LSP requires v1.50.0+) atmos version ``` ### No Autocomplete or Validation **VS Code:** 1. Check Output panel → "Atmos LSP" for errors 2. Verify LSP client extension is installed 3. Reload window (Cmd/Ctrl+Shift+P → "Reload Window") **Neovim:** ```lua -- Check LSP status :LspInfo -- Check logs :lua vim.cmd('e ' .. vim.lsp.get_log_path()) ``` ### External LSP Server Not Working **Check server is installed:** ```bash # YAML server yaml-language-server --version # Terraform server terraform-ls version ``` **Check configuration:** ```yaml # atmos.yaml - verify paths are correct lsp: servers: yaml-ls: command: "yaml-language-server" # Must be in PATH args: ["--stdio"] ``` **Enable debug logging:** ```bash # Set environment variable before starting editor export ATMOS_LOGS_LEVEL=Debug ``` ## Learn More **Documentation:** - [LSP Server Guide](/lsp/lsp-server) - Complete server setup for 13+ editors - [LSP Client Guide](/lsp/lsp-client) - External server integration - [AI Integration](/cli/configuration/ai/tools#available-tools) - AI validation tool - [Configuration Reference](/cli/configuration) - Full atmos.yaml options **LSP Resources:** - [Language Server Protocol Specification](https://microsoft.github.io/language-server-protocol/) - [yaml-language-server](https://github.com/redhat-developer/yaml-language-server) - [terraform-ls](https://github.com/hashicorp/terraform-ls) **Related Features:** - [Atmos AI](/ai) - AI-powered infrastructure assistance - [Atmos Validation](/cli/commands/validate/usage) - Validation commands - [Atmos Stacks](/stacks) - Stack structure and inheritance **Get Involved:** - [GitHub Issues](https://github.com/cloudposse/atmos/issues) - [Community Discussions](https://github.com/cloudposse/atmos/discussions) --- Happy configuring! For usage and configuration, see [IDE Integration](/lsp). --- ## Introducing atmos auth logout: Secure Credential Cleanup We're excited to announce a new authentication command: **[`atmos auth logout`](/cli/commands/auth/logout)**. This command provides secure, comprehensive cleanup of locally cached credentials, making it easy to switch between identities, end work sessions, and maintain proper security hygiene. ## Why This Matters Most cloud practitioners never log out of their cloud provider identities. Not because they don't want to, but because the tooling doesn't make it easy. When you authenticate with cloud providers, credentials get scattered across your filesystem: - **AWS**: `~/.aws/credentials`, `~/.aws/config`, session tokens - **Azure**: `~/.azure/` directory with multiple authentication artifacts - **Google Cloud**: `~/.config/gcloud/` with various credential files Most cloud provider tools don't provide a simple, comprehensive logout command. You're left to: - Manually hunt down and delete credential files across different locations - Navigate through provider-specific web consoles to revoke tokens - Hope that session expiration handles cleanup for you This leads to **credential sprawl**: old, forgotten credentials littering your system, many still valid and exploitable. The `atmos auth logout` command makes credential cleanup explicit, comprehensive, and easy. ## What's New ### Basic Usage Logout from a specific identity: ```shell atmos auth logout dev-admin ``` This removes credentials for `dev-admin` and all identities in its authentication chain: ```shell Logging out from identity: dev-admin Building authentication chain... ✓ Chain: aws-sso → dev-org-admin → dev-admin Removing credentials... ✓ Keyring: aws-sso ✓ Keyring: dev-org-admin ✓ Keyring: dev-admin ✓ Files: ~/.aws/atmos/aws-sso/ Successfully logged out from 3 identities ⚠️ Note: This only removes local credentials. Your browser session may still be active. Visit your identity provider to end your browser session. ``` ### Interactive Mode Run `atmos auth logout` without arguments for an interactive experience: ```shell atmos auth logout ``` ```shell ? Choose what to logout from: ❯ Identity: dev-admin Identity: prod-admin Identity: dev-readonly Provider: aws-sso (removes all identities) All identities (complete logout) ``` The interactive mode uses **Charmbracelet Huh** with Atmos theming for a polished experience. ### Provider Logout Remove all credentials for a specific provider: ```shell atmos auth logout --provider aws-sso ``` This removes the provider credentials and all identities that authenticate through it: ```shell Logging out from provider: aws-sso Removing all credentials for provider... ✓ Keyring: aws-sso ✓ Keyring: dev-org-admin (via aws-sso) ✓ Keyring: dev-admin (via aws-sso) ✓ Keyring: prod-admin (via aws-sso) ✓ Files: ~/.aws/atmos/aws-sso/ Successfully logged out from 4 identities ``` ### Dry Run Mode Preview what would be removed without actually deleting anything: ```shell atmos auth logout dev-admin --dry-run ``` ```shell Dry run mode: No credentials will be removed Would remove from identity: dev-admin • Keyring: aws-sso • Keyring: dev-org-admin • Keyring: dev-admin • Files: ~/.aws/atmos/aws-sso/credentials • Files: ~/.aws/atmos/aws-sso/config 3 identities would be logged out ``` ## How It Works ### Authentication Chain Resolution Atmos intelligently resolves the complete authentication chain for your identity and removes credentials at each step: ```shell aws-sso → dev-org-admin → dev-admin ↓ ↓ ↓ Removed Removed Removed ``` This ensures no orphaned credentials are left behind. ### Comprehensive Cleanup The logout command removes credentials from **all storage locations**: - ✅ **System keyring entries** - Credentials stored securely by your OS - ✅ **AWS credential files** - `~/.aws/atmos//credentials` - ✅ **AWS config files** - `~/.aws/atmos//config` - ✅ **Empty directories** - Cleans up provider directories after removal ### Best-Effort Error Handling The logout command continues even if individual steps fail, ensuring maximum cleanup: ```shell Logging out from identity: dev-admin Removing credentials... ✓ Keyring: aws-sso ✗ Keyring: dev-admin (not found - already logged out) ✓ Files: ~/.aws/atmos/aws-sso/ Logged out with warnings (2/3 successful) Errors encountered: • dev-admin: credential not found in keyring ``` This best-effort approach means you always get as much cleanup as possible. ## Security Best Practices ### Browser Sessions :::warning Important `atmos auth logout` only removes **local credentials**. Your browser session with the identity provider (AWS SSO, Okta, etc.) remains active. ::: To completely end your session: 1. Run `atmos auth logout` to remove local credentials 2. Visit your identity provider's website (AWS SSO, Okta, etc.) 3. Sign out from the browser session 4. Close all browser windows. The command displays this warning after every logout to ensure you don't forget. ### When to Logout **Logout at the end of your work session:** ```shell atmos auth logout --provider aws-sso ``` **Logout when switching contexts:** ```shell atmos auth logout dev-admin atmos auth login prod-admin ``` **Logout when troubleshooting authentication:** ```shell atmos auth logout dev-admin --dry-run # Preview atmos auth logout dev-admin # Execute atmos auth login dev-admin # Fresh login ``` ### Audit Trail All logout operations are logged for security auditing: ```shell 2025-10-17T10:15:30Z DEBUG Starting logout identity=dev-admin 2025-10-17T10:15:30Z DEBUG Authentication chain built chain=[aws-sso dev-org-admin dev-admin] 2025-10-17T10:15:30Z DEBUG Removing keyring entry alias=aws-sso 2025-10-17T10:15:30Z INFO Logout completed identity=dev-admin removed=3 ``` Enable debug logging with `ATMOS_LOGS_LEVEL=Debug` to see detailed audit information. ## Use Cases ### 1. Daily Workflow Start and end your day with clean credential state: ```shell # Morning: Login for the day atmos auth login # Evening: Logout for security atmos auth logout ``` ### 2. Multi-Identity Switching Switch between development and production environments: ```shell # Switch from dev to prod atmos auth logout dev-admin atmos auth login prod-admin # Later: Switch back atmos auth logout prod-admin atmos auth login dev-admin ``` ### 3. Troubleshooting Clear credential cache when debugging authentication issues: ```shell # Check current status atmos auth whoami # Clear and re-authenticate atmos auth logout dev-admin atmos auth login dev-admin # Verify atmos auth whoami ``` ### 4. Compliance Demonstrate credential cleanup for security audits: ```shell # Interactive review of what to remove atmos auth logout # Select "All identities" to clear everything # Audit logs show complete cleanup ``` ## Configuration The logout command works with your existing `atmos.yaml` authentication configuration: ```yaml auth: providers: aws-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://mycompany.awsapps.com/start identities: dev-admin: kind: aws/permission-set via: provider: aws-sso principal: name: AdminAccess account: name: "dev-account" ``` No additional configuration required - logout uses the same identity and provider definitions as login. ## Technical Details ### Cross-Platform Support The logout command uses native Go libraries for maximum compatibility: - **File operations**: `os.RemoveAll()` for cross-platform directory removal - **Keyring access**: `go-keyring` library supporting macOS, Linux, and Windows - **Path handling**: `filepath` package for platform-specific path separators. ### Error Handling Following Atmos error handling patterns: - Uses static error sentinels from `errors/errors.go` - Wraps errors with `errors.Join` for proper error chains - Continues cleanup on non-fatal errors - Reports all errors at completion. ### Interface Extensions The logout feature extends the auth interfaces: ```go // Provider interface type Provider interface { // ... existing methods Logout(ctx context.Context) error } // Identity interface type Identity interface { // ... existing methods Logout(ctx context.Context) error } // AuthManager interface type AuthManager interface { // ... existing methods Logout(ctx context.Context, identityName string) error LogoutProvider(ctx context.Context, providerName string) error LogoutAll(ctx context.Context) error } ``` ### Telemetry Like all Atmos commands, logout automatically captures anonymous usage telemetry: - Command path: `auth logout` - Error state: Boolean only (no sensitive data) - No user data, credentials, or identity names captured ## What's Next This initial release supports: - ✅ AWS provider logout (SSO, SAML, user credentials) - ✅ Identity chain resolution - ✅ Interactive mode - ✅ Dry run mode Future enhancements: - 🔄 Azure Entra ID provider logout - 🔄 GCP OIDC provider logout - 🔄 GitHub Actions OIDC logout - 🔄 Selective logout (keep provider, remove identity only) - 🔄 Automatic cleanup of expired credentials ## Get Started The `atmos auth logout` command is available in Atmos v1.x.x and later. **Try it now:** ```shell # Interactive mode atmos auth logout # Or logout from specific identity atmos auth logout # See all options atmos auth logout --help ``` **Learn more:** - 📖 [CLI Documentation](/cli/commands/auth/logout) - Complete command reference - 📋 [PRD: Auth Logout](https://github.com/cloudposse/atmos/blob/main/docs/prd/auth-logout.md) - Technical design document - 🔐 [Authentication Overview](/cli/commands/auth/usage) - Complete authentication overview ## Feedback Welcome We'd love to hear how you're using `atmos auth logout`: - 💬 **Discuss** - Share your thoughts in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) - 🐛 **Report Issues** - Found a bug? [Open an issue](https://github.com/cloudposse/atmos/issues) - 🚀 **Contribute** - Submit PRs for improvements Secure credential management is critical for infrastructure automation. We're committed to making authentication with Atmos both powerful and secure. --- **Ready to try it?** Run `atmos auth logout` to get started! --- ## Introducing --chdir: Simplify Your Multi-Repo Workflows We're excited to announce a new global flag that makes working with Atmos across multiple repositories and directories significantly easier: [`--chdir`](/cli/global-flags#directory-change-examples) (or `-C` for short). ## The Problem If you've ever worked with Atmos in a multi-repository setup or during development, you've probably faced these scenarios: - **Development workflow**: You're building a new Atmos binary and want to test it against your infrastructure repo without installing it globally - **CI/CD complexity**: Your build runs in one directory, but your infrastructure code lives elsewhere - **Multi-repo operations**: You need to quickly check configurations across different infrastructure repositories - **Script complexity**: Your automation scripts have to change directories manually, making them harder to read and maintain Previously, you had to either: - Manually `cd` to each directory before running Atmos - Write wrapper scripts to handle directory changes - Modify your `PATH` or use absolute paths to atmos binaries - Copy binaries to specific locations ## The Solution The new `--chdir` flag (and its short form `-C`) changes Atmos's working directory **before** any other operations, including configuration loading. This mirrors the familiar behavior of tools like `git -C` and `make -C`. ### Basic Usage ```bash # Long form atmos --chdir=/path/to/infrastructure describe stacks # Short form (preferred for brevity) atmos -C /infra terraform plan vpc -s prod # Relative paths work too atmos --chdir=../other-repo list components ``` ### Development Workflow Testing a development build against your infrastructure is now trivial: ```bash # Build your changes make build # Point the dev binary at your infrastructure repo ./build/atmos -C ~/projects/my-infrastructure describe stacks # No need to change directories or modify PATH ./build/atmos -C ~/projects/my-infrastructure terraform plan vpc -s dev ``` ### CI/CD Pipelines Your CI/CD workflows become cleaner and more explicit: ```bash # Before: Manual directory management cd /infrastructure atmos terraform plan vpc -s prod cd - # After: Explicit and clear atmos -C /infrastructure terraform plan vpc -s prod ``` ### Environment Variable Support For scripts and CI/CD environments, you can use the `ATMOS_CHDIR` environment variable: ```bash export ATMOS_CHDIR=/infrastructure # All atmos commands now run in that directory atmos terraform plan vpc -s prod atmos describe stacks atmos list components ``` The CLI flag takes precedence over the environment variable, allowing you to override the default when needed: ```bash export ATMOS_CHDIR=/default-infra # Use the default atmos describe stacks # Override for specific command atmos -C /other-infra describe stacks ``` ## How It Works with --base-path It's important to understand the difference between `--chdir` and [`--base-path`](/cli/global-flags#core-global-flags): - **`--chdir`**: Changes the **working directory** (like running `cd` first) - **`--base-path`**: Overrides the **Atmos project root** (where `atmos.yaml` lives) ### Processing Order 1. `--chdir` executes first, changing the working directory 2. `--base-path` is then resolved relative to the new working directory 3. All other configuration loading and operations proceed ### Example ```bash # Change to /infra directory, then look for atmos.yaml in ./config subdirectory atmos -C /infra --base-path=./config terraform plan vpc -s dev # This is equivalent to: # cd /infra # atmos --base-path=./config terraform plan vpc -s dev ``` ### When to Use Each - Use `--chdir` when you want Atmos to run as if you had changed directories first - Use `--base-path` when your Atmos project root is in a non-standard location - Combine them when your working directory and Atmos project root are in different places ```bash # Just change working directory (atmos.yaml is in the root) atmos -C /path/to/infra terraform plan vpc -s prod # Just override project root (stay in current directory) atmos --base-path=/custom/location terraform plan vpc -s prod # Both: change directory AND override project root atmos -C /infra --base-path=./custom terraform plan vpc -s prod ``` ## Real-World Examples ### Multi-Repo Infrastructure Management ```bash # Check production infrastructure atmos -C ~/projects/prod-infra describe affected # Compare with staging atmos -C ~/projects/staging-infra describe affected # All without leaving your current directory ``` ### Development and Testing ```bash # Test local changes against test environment ./build/atmos -C ~/infra terraform plan vpc -s test # Once satisfied, use installed version for production atmos -C ~/infra terraform apply vpc -s prod ``` ### Automated Scripts ```bash #!/bin/bash # Script can stay in your tools repo while operating on infrastructure INFRA_DIR="/path/to/infrastructure" echo "Validating stacks..." atmos -C "$INFRA_DIR" validate stacks echo "Checking for drift..." atmos -C "$INFRA_DIR" terraform plan vpc -s prod --detailed-exitcode echo "Generating documentation..." atmos -C "$INFRA_DIR" docs generate ``` ## Error Handling Atmos provides clear error messages when things go wrong: - **Directory doesn't exist**: Clear message with the path that was attempted - **Path is a file, not a directory**: Explicit error explaining the issue - **Permission denied**: OS-level error with context - **Invalid path**: Path resolution errors are caught and reported ## Platform Compatibility The `--chdir` flag works consistently across: - **Linux** - All distributions - **macOS** - All versions - **Windows** - Native support Both absolute and relative paths work as expected on all platforms, with proper path separator handling. ## Technical Details For those interested in the implementation: - **Execution order**: `--chdir` → config loading → `--base-path` resolution → command execution - **Path resolution**: Relative paths are resolved from the current working directory - **Symlinks**: Symlinks to directories work correctly - **Environment variable**: `ATMOS_CHDIR` follows the same precedence rules as other Atmos env vars - **Flag precedence**: CLI flag > environment variable > current directory ## Getting Started The `--chdir` flag is available starting in Atmos vX.X.X. To start using it: ```bash # Upgrade to the latest version brew upgrade atmos # macOS # or download from GitHub releases # Start using the flag immediately atmos --chdir=/your/infra describe stacks # Or set it as an environment variable for your session export ATMOS_CHDIR=/your/infra ``` ## Conclusion The `--chdir` flag is a small addition that makes a big difference in daily workflows. It removes friction from multi-repo operations, development workflows, and automation scripts. We designed it to feel natural if you've used similar flags in other tools (`git -C`, `make -C`, `tar -C`), while integrating seamlessly with Atmos's existing flag system. Try it out and let us know what you think! We'd love to hear how you're using it in your workflows. ## Resources - [Global Flags Documentation](/cli/global-flags) - [Atmos CLI Reference](/cli/commands) - [GitHub Repository](https://github.com/cloudposse/atmos) --- _Have feedback or questions? Join our [Slack community](https://slack.cloudposse.com/) or [open an issue on GitHub](https://github.com/cloudposse/atmos/issues)._ --- ## Introducing the Command Registry Pattern: Toward Pluggable Commands We're excited to announce the first step in a major architectural evolution for Atmos: the **Command Registry Pattern**. This foundational change will eventually enable **pluggable commands**, allowing the community to extend Atmos with custom command packages without modifying the core codebase. ## Why This Matters Today, all Atmos commands live in a single monolithic `cmd/` directory. While this works well for built-in commands, it creates friction for: - **Plugin developers** who want to add new commands without forking Atmos - **Command maintainers** who need clear boundaries between command implementations - **Organizations** that want to distribute custom command packages internally The Command Registry Pattern solves these challenges by treating commands as **self-contained packages** that register themselves with Atmos at startup. ## What's Changing ### Before: Monolithic Command Structure ``` cmd/ ├── terraform.go # All commands in one directory ├── describe.go ├── list.go └── about.go ``` ### After: Package-Per-Command Architecture ``` cmd/ ├── terraform/ # Each command is a package │ └── terraform.go ├── describe/ │ └── describe.go ├── about/ # First migrated command │ └── about.go └── internal/ # Registry infrastructure ├── command.go # CommandProvider interface └── registry.go # Thread-safe registry ``` ## How It Works Commands implement a simple interface and register themselves during package initialization: ```go // cmd/about/about.go package about import ( "github.com/spf13/cobra" "github.com/cloudposse/atmos/cmd/internal" ) func init() { // Self-registration via init() internal.Register(&AboutCommandProvider{}) } type AboutCommandProvider struct{} func (a *AboutCommandProvider) GetCommand() *cobra.Command { return aboutCmd } func (a *AboutCommandProvider) GetName() string { return "about" } func (a *AboutCommandProvider) GetGroup() string { return "Other Commands" } ``` The registry pattern provides: - ✅ **Self-registering commands** - No manual wiring required - ✅ **Type-safe interfaces** - Compile-time guarantees - ✅ **Thread-safe operation** - Concurrent registration support - ✅ **Custom command compatibility** - Works seamlessly with existing `atmos.yaml` custom commands ## Impact on Users This is a **100% backward-compatible change** with zero impact on Atmos users. All existing functionality remains identical: - Custom commands in `atmos.yaml` work exactly as before - Command behavior is unchanged - No configuration updates required - All existing workflows continue to work For context: The registry pattern actually enhances custom command capabilities by allowing them to extend built-in commands with subcommands, but this is an existing feature that continues to work—nothing new from a user perspective. ## The Road Ahead This PR lays the **foundation** for pluggable commands. Here's what's coming next: ### Phase 2: Migrate Core Commands Subsequent PRs will refactor existing commands into the new package structure: - `atmos terraform` → `cmd/terraform/` - `atmos describe` → `cmd/describe/` - `atmos list` → `cmd/list/` - `atmos validate` → `cmd/validate/` Each command family will move into its own **package** (Go's term for a self-contained code module). ### Phase 3: External Plugin Support Once all commands use the registry pattern, we'll enable: - **Plugin discovery** - Load commands from external Go modules - **Plugin packaging** - Distribute commands as standalone binaries - **Plugin marketplace** - Share and discover community commands ## For Atmos Contributors This change is **internal to Atmos development** and has no impact on Atmos users. End users won't notice any difference in behavior—this is purely an architectural improvement for maintainability and future extensibility. **If you're an Atmos contributor** interested in migrating commands or building plugins: - **[PRD: Command Registry Pattern](https://github.com/cloudposse/atmos/blob/main/docs/prd/command-registry-pattern.md)** - Complete architecture documentation - **[Developer Guide: Developing Atmos Commands](https://github.com/cloudposse/atmos/blob/main/docs/developing-atmos-commands.md)** - Step-by-step implementation guide The `about` command has been migrated as a proof-of-concept in this PR, demonstrating the pattern works in production. ## Get Involved We're building this in the open and welcome contributions from the community: - 💬 **Discuss** - Share your thoughts in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) - 🐛 **Report Issues** - Found a bug? [Open an issue](https://github.com/cloudposse/atmos/issues) - 🚀 **Contribute** - Help migrate commands in future PRs This is the foundation for a more modular, extensible Atmos architecture. --- **Want to learn more?** Check out the full [Command Registry Pattern PRD](https://github.com/cloudposse/atmos/blob/main/docs/prd/command-registry-pattern.md) for technical details. --- ## Introducing Emulators: Run Real Cloud Infrastructure Locally A big challenge for infrastructure developers is how hard it is to iterate locally. Every `terraform apply` needs a real cloud account, costs real money, and touches a live environment. And in many enterprises developers don't have permission to the cloud at all, so they can't iterate locally even when they want to. Atmos emulators help with this: long-running, containerized stand-ins for AWS, GCP, Azure, Kubernetes, Vault, and an OCI registry that you provision as ordinary Atmos components — so you can run the **full Atmos workflow (auth, secrets, vendoring, toolchain, and `terraform apply`) on your laptop, with no cloud account and no credentials.** ## The Problem Iterating on a Terraform component is always coupled to something real: - **A real account.** You can't `apply` without one, so even a small experiment starts with a prerequisites checklist. - **Real money and real risk.** Standing resources up and tearing them down to test a change costs money, and a mistake touches a live account. - **Onboarding friction.** A new contributor can't run the tutorial — or reproduce a bug — without first being granted access to your infrastructure. - **The enterprise permission wall.** In many organizations developers simply aren't allowed near the cloud environment. Local iteration isn't slow for them; it's impossible. Application developers reach for local databases, mock servers, and emulators to iterate without touching production — but those conveniences never really existed the same way for infrastructure. That was true until emulators became first class in Atmos. ## The Solution An emulator is a new Atmos component kind. You declare it in a stack and drive it with the new `atmos emulator` command: ```bash atmos emulator up aws -s local # start a local AWS emulator atmos terraform apply s3-bucket -s local # apply real Terraform against it — no AWS account atmos emulator down aws -s local # stop it (state is kept by default) ``` Atmos wires the emulator into the rest of the system automatically: - **No credentials.** Atmos binds a local identity to the emulator and points both in-process cloud calls and the Terraform provider at the emulator endpoint — so the same stacks that target real AWS run unmodified against the emulator. - **The same stacks everywhere.** Because the emulator is bound through Atmos identity and provider generation, your component code doesn't change between local and real cloud — laptop and CI run the identical configuration. - **Persistent by default.** State survives `down`/`up`; [`atmos emulator reset`](/cli/commands/emulator/reset) wipes it, and `--ephemeral` opts out per run. You can apply, destroy, and re-apply against a local cloud in seconds, without touching an account or spending money — and without waiting on anyone to grant you access. ## Supported Emulators Built-in drivers cover the major clouds and the backing services your stacks lean on. The `driver` field on the component selects the image and target: | Service | Driver | Image | Notes | |---|---|---|---| | AWS | `floci/aws` | `floci/floci` | **Default** — free, MIT-licensed | | AWS | `ministack/aws` | `ministack/ministack` | Alternative AWS emulator | | AWS | `localstack/aws` | `localstack/localstack:3` | Opt-in / legacy | | GCP | `floci/gcp` | `floci/floci-gcp` | Storage, Pub/Sub, Firestore, Bigtable, Datastore | | Azure | `floci/az` | `floci/floci-az` | Blob / Storage | | Kubernetes | `k3s` | `rancher/k3s` | A real single-node cluster | | Vault / secrets | `openbao` | `openbao/openbao` | **Default** — MPL open-source fork | | Vault / secrets | `vault` | `hashicorp/vault` | Opt-in | | OCI / Terraform registry | `registry` | `registry:2` | Vendoring + the registry cache | The defaults are deliberately the free, open-source options (`floci/aws`, `openbao`) — reach for `localstack/aws` or `vault` only when you specifically need them. See the [emulator component reference](/stacks/components/emulator#supported-drivers--targets) for the full matrix, ports, and per-driver details. ## What Emulators Are — and Aren't Emulators are built for the **happy path** — the common control-plane API surface your stacks actually exercise. That's where they shine, and for most local development and onboarding it's all you need. But it's worth being clear about what they are not: - **They're not bug-for-bug clones of the real clouds.** Edge cases, eventual-consistency quirks, and the long tail of services and IAM nuances won't all be reproduced. - **They're not a substitute for a real pre-prod environment.** Validate against a real account before production — emulators raise your confidence locally; they don't replace staging. - **Fidelity varies by service.** The more exotic the resource, the more likely you'll hit an unimplemented corner. The point isn't 100% fidelity — it's being able to iterate quickly, for free, without cloud access, on the work you do every day. ## Try It: the Advanced Quick Start The [advanced quick start](/quick-start/advanced) now deploys a **real event-driven AWS backend** — a KMS key, an encrypted S3 bucket, a DynamoDB table, an SNS topic, an SQS queue, and an SSM Parameter Store config — **entirely on your laptop, start to finish, with no AWS account and no credentials.** ## Persistence and Reset Emulators persist their state by default. Atmos bind-mounts a per-instance host directory under the XDG cache (`$XDG_CACHE_HOME/atmos/emulator/`) onto each emulator's data directory, so resources, images, clusters, and secrets all survive a restart: ```bash atmos emulator up registry -s local # start the registry # ... push images ... atmos emulator down registry -s local # stop it — state is kept atmos emulator up registry -s local # back up — your images are still there ``` Use `reset` for a clean slate, or `--ephemeral` for a throwaway instance that never persists: ```bash atmos emulator reset registry -s local --force atmos emulator up registry -s local --ephemeral ``` ## How to Use It - [`atmos emulator up -s `](/cli/commands/emulator/up) to start an emulator, then run your normal [`atmos terraform …`](/cli/commands/terraform/usage) commands against it. - [`atmos emulator ps`](/cli/commands/emulator/ps), `logs`, and `exec` to inspect a running emulator (it outlives the `atmos` process and is rediscovered by label — no local state files). - `atmos emulator reset -s ` to wipe persisted state. - `--ephemeral` (or `ephemeral: true` on the component) for a throwaway instance. See the [emulator component reference](/stacks/components/emulator) and the [`atmos emulator`](/cli/commands/emulator/usage) command docs for details. ## What's New - **`atmos emulator list`** renders every emulator across your stacks in a clean table — a status dot, the image, and the container ID — so you can see what's running at a glance. Scope it to a single stack with `--stack`, or omit it to list everything. See the [`atmos emulator list`](/cli/commands/emulator/list) reference. - **Ready-to-run examples.** Two new examples let you try emulators without assembling a stack yourself: [`examples/emulator-aws`](https://github.com/cloudposse/atmos/tree/main/examples/emulator-aws) brings up a local AWS environment, and [`examples/emulator-k8s`](https://github.com/cloudposse/atmos/tree/main/examples/emulator-k8s) brings up a local Kubernetes (k3s) cluster and deploys into it. See the AWS emulator lifecycle in action: [View the full example](/examples/emulator-aws) And the Kubernetes emulator lifecycle: [View the full example](/examples/emulator-k8s) For usage and configuration, see [Using Emulators](/components/emulator). --- ## Introducing Gists: Community Recipes for Atmos Atmos now has a dedicated space for community-contributed recipes called **[Gists](/gists)** — creative patterns showing how to combine Atmos features in ways that go beyond standard documentation. ## What Are Gists? Gists are shared as-is by community contributors. Unlike maintained [examples](/examples), gists may need adaptation for your version of Atmos. They serve as inspiration and starting points for building your own workflows. | | Examples | Gists | |---|---|---| | **Maintained** | Yes, tested with each release | No, shared as-is | | **Scope** | Single feature demonstration | Combining multiple features | | **Style** | Minimal config files | Rich README + config files | ## First Gist: FinOps with AWS MCP Servers Our inaugural gist is a masterclass in combining [Custom Commands](/cli/configuration/commands), [Auth](/stacks/auth), and [Toolchain](/cli/configuration/toolchain) to give AI assistants direct access to AWS cost data. Ask Claude questions like "What did we spend on EC2 last month?" and get real answers from your actual AWS account — all authenticated automatically through Atmos. ```bash # Install all AWS MCP server packages atmos mcp aws install all # Start a cost server with automatic AWS auth atmos mcp aws start cost-explorer # Test that auth is working atmos mcp aws test all ``` The gist configures 21 AWS MCP servers including Billing, Cost Explorer, Pricing, CloudWatch, IAM, and more — all using a single authentication pattern powered by [`atmos auth exec`](/cli/commands/auth/exec). [Browse the gist](/gists/mcp-with-aws) ## Contributing a Gist Have a creative Atmos recipe? We welcome contributions. 1. Create a directory in `gists/` with your config files and a comprehensive README 2. Submit a pull request 3. Your gist will automatically appear on the [Gists page](/gists) ## Get Involved - Browse the [Gists collection](/gists) - [Join us on Slack](/community/slack) - [Attend Office Hours](/community/office-hours) --- ## Reshape a version before writing it into a JSON file Git tags on GitHub almost always carry a `v` prefix — `v1.228.0` — because that's the convention both `git tag` and GitHub releases nudge toward. Package manifests, on the other hand, almost never want that prefix: `package.json`, plugin manifests, and marketplace listings all expect bare semver. Whenever a version comes from GitHub and needs to land in one of those files, something has to strip the `v` first. ## The Problem The [Version Tracker](/cli/configuration/version/files)'s `json` file manager writes a locked dependency's resolved value directly into a JSON field, byte-for-byte. For a dependency sourced from `github-releases` or `github-tags`, that resolved value is the raw git tag — so a target field that needed bare semver got `v1.228.0` verbatim, with no way to reshape it before the write. ## The Fix `options.set` entries in the `json` manager now accept an optional `format`: a small Go template rendered against the resolved version, whose output replaces the verbatim value instead. Trimming a `v` prefix is the common case, but any Sprig string function — `trimSuffix`, `replace`, `regexReplaceAll` — is available too. ## How to Use It ```yaml version: files: - manager: json paths: - package.json options: set: - path: version from: cli format: '{{ trimPrefix "v" .Version }}' ``` Leaving `format` off keeps today's default: the resolved value is written verbatim, exactly as before. ## Get Involved See the [Version Files](/cli/configuration/version/files#updating-json-files) docs for the full `set` syntax. Have feedback on this feature? [Open an issue](https://github.com/cloudposse/atmos/issues) or join the conversation in the [Cloud Posse community Slack](https://cloudposse.com/slack). --- ## Injecting Terraform Values into Kustomize Without Hand-Editing Overlays Kustomize expects the files it consumes to have exact, reserved names. A remote base or component can only be included if the location it points to contains a file with one of a handful of recognized names (`kustomization.yaml` is the common one) — that's not configurable on Kustomize's side. So when a value only Terraform knows — a security group ID, a Route53 zone ID, an ARN — needs to land inside a Kustomize-managed GitOps repo, teams are usually stuck hand-editing the overlay after every apply, or routing the value through a separate tool just to produce one correctly-named file. ## The Problem Delivering rendered Kubernetes manifests to a Git deployment repository (the source Argo CD or Flux reconciles) always wrote them as a directory — one generated file per manifest, no way to land a single file under an exact, caller-chosen name. That's a fine default for a directory of standalone resources, but it can't produce `kustomization.yaml`, so it couldn't support this pattern at all. Separately, Kustomize's own `Kustomization` and `Component` config objects don't have a `metadata.name` in the real Kustomize schema — they're local input to the `kustomize` build tool, not Kubernetes API resources — but Atmos's manifest validator required one anyway, rejecting perfectly valid Kustomize files. ## The Fix A git provision target's `path` can now be an exact single-file destination, not just a directory. Set `split: false` to merge every rendered manifest into one file written at that path; leave it unset and Atmos infers the right mode from whether the path looks like a manifest filename (`.yaml`, `.yml`, or `.json`). Every existing configuration keeps its current directory behavior unchanged. Atmos also now recognizes Kustomize's own `Kustomization` and `Component` kinds and no longer requires a `metadata.name` on them — matching Kustomize's own validation, not an opinion Atmos invented. For anything else, a new `validate: false` component setting opts out of Atmos's structural checks entirely. ## How to Use It ```yaml components: kubernetes: cert-manager-patch: provision: targets: deployment-repo: kind: git repository: deployments path: "kustomize/overlays/{{ .vars.environment }}/kustomization.yaml" commit: message: "Render manifests for {{ .vars.environment }}" manifests: - apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component patches: - target: kind: ClusterIssuer name: letsencrypt-dns patch: | - op: add path: /spec/acme/solvers value: - dns01: route53: region: "{{ .vars.aws_region }}" hostedZoneID: "{{ atmos.Resolve \"!terraform.state route53 public_zone_id\" }}" ``` ```shell atmos kubernetes deploy cert-manager-patch -s plat-ue2-dev --target=deployment-repo ``` No `split` is set here — the path ends in `kustomization.yaml`, so Atmos writes it as a single file automatically. No `metadata.name` is needed on the `Component` object either. The real Kustomize overlay then includes the generated file as a remote component, so the Terraform-derived value flows through on every deploy without anyone touching the overlay by hand. See [Generating a Kustomize component for GitOps](/stacks/components/kubernetes#generating-a-kustomize-component-for-gitops) for the full walkthrough. For usage and configuration, see [atmos kubernetes render](/cli/commands/kubernetes/render). ## Get Involved Try delivering a Kustomize component or patch through a git provision target in your own GitOps repo. Tell us what's missing — pull-request publishing for the git target, support for other Kustomize-only object kinds, or something else — by opening an issue at [github.com/cloudposse/atmos](https://github.com/cloudposse/atmos). --- ## Read One Component Label with an Optional Default A component can have several labels, while a variable may need only one value. The [`!labels` function](/functions/yaml/labels) now accepts a key to read a single component label, with an optional fallback when that label is absent. ## The Problem Individual labels are already accessible through Go templates, such as `{{ .metadata.labels.owner }}`. The [`!labels` function](/functions/yaml/labels) previously returned only the full map and accepted no key or fallback argument. ## The Fix Use [`!labels key [default]`](/functions/yaml/labels#single-label-lookup) to retrieve a string from the component’s resolved [metadata labels](/stacks/components/component-metadata#labels). An absent key uses the explicit default or produces an error if none was supplied. Existing empty-string values are preserved. Bare `!labels` continues to return the full map. ## How to Use It ```yaml components: terraform: vpc: metadata: labels: cost-center: platform vars: cost_center: !labels cost-center owner: !labels owner "Platform Team" ``` Here `cost_center` resolves to `platform`. If the resolved metadata has no `owner` label after stack defaults and component inheritance, `owner` resolves to the explicit fallback, `Platform Team`. An inherited `owner` label supplies its value instead. Keys are literal, including dots, slashes, and hyphens. Quote fallback values containing spaces; use `""` when the fallback should be an empty string. Values reflect [stack defaults and component inheritance](/stacks/components/component-metadata#global-stack-wide-metadata). See the [label lookup reference](/functions/yaml/labels) for full-map access and Go template equivalents. ## Get Involved Share your label lookup use cases in [Atmos discussions](https://github.com/orgs/cloudposse/discussions). --- ## Introducing atmos list affected: A Better Way to See What Changed Quickly identify which components and stacks are affected by your changes with the new [`atmos list affected`](/cli/commands/list/affected) command. ## What Changed We've added a new `atmos list affected` command that provides a human-readable table view of affected components and stacks between Git commits. This complements the existing [`atmos describe affected`](/cli/commands/describe/affected) command, which outputs detailed JSON/YAML for automation. ```shell atmos list affected ``` The command displays results in a clean table format with visual status indicators: ``` ┌────────┬─────────────────────┬──────────────────┬──────────┬──────────┐ │ Status │ Component │ Stack │ Type │ Affected │ ├────────┼─────────────────────┼──────────────────┼──────────┼──────────┤ │ ● │ vpc │ plat-ue2-dev │ terraform│ component│ │ ● │ vpc │ plat-ue2-prod │ terraform│ component│ │ ● │ eks │ plat-ue2-dev │ terraform│ stack.vars│ │ ◐ │ rds │ plat-ue2-staging │ terraform│ file │ └────────┴─────────────────────┴──────────────────┴──────────┴──────────┘ ``` ## Why This Matters Before this change, understanding what components were affected by your changes required parsing JSON output or using tools like `jq`. Now you can get a quick overview directly in your terminal. **Visual status indicators** make it easy to spot: - `●` Enabled components ready for deployment - `◐` Locked components that shouldn't be modified - `○` Disabled components **Multiple output formats** support different workflows: - `table` (default) - Human-readable overview - `json` / `yaml` - Machine-readable for automation - `csv` / `tsv` - Spreadsheet and scripting integration ## How to Use It ### Basic Usage Compare your current branch against main: ```shell atmos list affected ``` ### Compare Against Specific References ```shell # Compare against a specific branch atmos list affected --ref refs/heads/develop # Compare against a specific commit atmos list affected --sha abc123def456 ``` ### Filter and Sort Results ```shell # Filter by stack atmos list affected --stack prod-us-east-1 # Sort by component name atmos list affected --sort component:asc # Include dependent components atmos list affected --include-dependents ``` ### Custom Column Selection ```shell # Show only specific columns atmos list affected --columns component,stack,affected # Named columns with templates atmos list affected --columns "Component={{ .component }},Stack={{ .stack }}" ``` ### CI/CD Integration ```shell # JSON output for automation atmos list affected --format json # CSV for processing atmos list affected --format csv > affected.csv # Use pre-cloned repository atmos list affected --repo-path /tmp/target-repo --format json ``` ## When to Use Each Command | Use Case | Command | |----------|---------| | Quick overview of changes | `atmos list affected` | | CI/CD pipelines (full data) | `atmos describe affected --format json` | | Spreadsheet analysis | `atmos list affected --format csv` | | Dependency visualization | `atmos list affected --include-dependents` | ## Learn More - [atmos list affected documentation](/cli/commands/list/affected) - [atmos describe affected documentation](/cli/commands/describe/affected) - [Native CI for GitHub Actions](/ci) ## Get Involved We'd love your feedback! Try out `atmos list affected` and let us know how it works for your workflow. Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues) or join us in [Slack](https://slack.cloudposse.com). --- ## List Command Query Syntax and Multi-Tool Installation New query syntax for [`atmos list components`](/cli/commands/list/components) and support for installing multiple tools at once with [`atmos toolchain install`](/cli/commands/toolchain/install). ## What Changed ### Simplified Query Syntax for List Commands The `atmos list components` command now supports a simplified query syntax that makes filtering components more intuitive: ```bash # Filter by stack pattern atmos list components plat-ue2-dev # Filter by component name atmos list components vpc # Combine filters atmos list components vpc plat-ue2-dev ``` This replaces the need for verbose `--stack` flags in many common cases. ### New `atmos list aliases` Subcommand View all command aliases—both built-in and user-configured: ```bash atmos list aliases ``` This command shows: - **Built-in aliases**: Native command shortcuts (e.g., `tf` → `terraform`, `hf` → `helmfile`) - **Configured aliases**: User-defined shortcuts from your `atmos.yaml` The output includes a "Type" column so you can easily distinguish between the two, helping you discover all available shortcuts in your project. ### Multi-Tool Installation Install multiple tools in a single command: ```bash atmos toolchain install opentofu tflint kubectl helm ``` Previously, each tool required a separate `atmos toolchain install` invocation. Now you can batch installations with a progress summary. ## Why This Matters - **Faster Workflows**: Query syntax reduces typing for common list operations - **Better Discoverability**: The aliases command helps teams discover configured shortcuts - **Streamlined Setup**: Multi-tool installation speeds up environment bootstrapping ## How to Use It Update to the latest version of Atmos: ```bash atmos version install latest ``` Then try the new features: ```bash # Explore the simplified query syntax atmos list components --help # View your configured aliases atmos list aliases # Install your toolchain in one command atmos toolchain install opentofu terraform-docs tflint ``` For usage and configuration, see [List Command Configuration](/cli/configuration/list). --- ## New List Configuration and Stack Filtering The [`atmos list components`](/cli/commands/list/components) command now correctly shows unique component definitions, supports stack filtering, and uses a new dedicated configuration namespace. ## What Changed The `atmos list components` command behavior has been restored to its original intent: - **`list components`** - Shows unique component definitions (deduplicated across all stacks) - **`list instances`** - Shows all component instances (one entry per component+stack pair) Previously, both commands were returning the same data (component instances), which was confusing. ## New: Filter Components by Stack You can now filter which stacks to consider when listing unique components using the `--stack` flag with glob patterns: ```bash # List components that exist in any dev stack atmos list components --stack "*-dev" # List components in a specific environment atmos list components --stack "plat-ue2-*" # List components across all production stacks atmos list components --stack "*-prod" ``` This is useful when you want to see what components are defined for a particular environment or stage without seeing the full list across all stacks. ## New Configuration Namespace We've introduced a cleaner configuration structure for list commands with separate settings for `list components` and `list instances`: ```yaml # atmos.yaml list: # Configuration for "atmos list components" # Shows unique component definitions (deduplicated) components: format: table columns: - name: Component value: "{{ .component }}" - name: Type value: "{{ .type }}" - name: Stacks value: "{{ .stack_count }}" # Configuration for "atmos list instances" # Shows component instances (one per component+stack pair) instances: format: table columns: - name: Stack value: "{{ .stack }}" - name: Component value: "{{ .component }}" - name: Type value: "{{ .type }}" - name: Tenant value: "{{ .vars.tenant }}" - name: Environment value: "{{ .vars.environment }}" ``` ### Available Fields **For `list components` (unique component fields):** - `{{ .component }}` - Component name - `{{ .type }}` - Component type (terraform, helmfile, packer) - `{{ .stack_count }}` - Number of stacks using this component - `{{ .component_folder }}` - Path to component folder **For `list instances` (per-instance fields):** - All unique component fields above, plus: - `{{ .stack }}` - Stack name - `{{ .vars.* }}` - Any variable from the component (tenant, environment, stage, region, etc.) - `{{ .status }}` - Component status indicator ## Backward Compatibility Existing configurations using `components.list.columns` continue to work for `list instances`. The precedence is: **For `list instances`:** 1. `--columns` CLI flag 2. `list.instances.columns` (new) 3. `components.list.columns` (deprecated, backward compat) 4. Default columns **For `list components`:** 1. `--columns` CLI flag 2. `list.components.columns` (new) 3. Default columns (Component, Type, Stacks) Note: The old `components.list.columns` does **not** fall back for `list components` because those columns were designed for per-instance data (with stack-specific fields). ## Example Output **`atmos list components`** now shows unique components: ``` Component Type Stacks vpc terraform 6 vpc-flow-logs-bucket terraform 6 ``` **`atmos list components --stack "*-dev"`** filters to dev stacks: ``` Component Type Stacks vpc terraform 2 vpc-flow-logs-bucket terraform 2 ``` **[`atmos list instances`](/cli/commands/list/list-instances)** continues to show all instances: ``` Stack Component Type ... plat-ue2-dev vpc terraform ... plat-ue2-dev vpc-flow-logs-bucket terraform ... plat-ue2-prod vpc terraform ... ... ``` ## Migration Update your `atmos.yaml` from: ```yaml components: list: columns: [...] ``` To: ```yaml list: instances: columns: [...] ``` And optionally add a `list.components` section to customize the unique components output. --- ## Visualize Component Dependencies as a Tree The new `atmos list dependencies` command renders the dependency relationships between your components as a tree — showing, for every component, both what it **depends on** and what **depends on it**. It reads `dependencies.components` (preferred) and the legacy `settings.depends_on`, so the output stays consistent with [`atmos describe dependents`](/cli/commands/describe/dependents). ## The Problem As infrastructure grows, the relationships between components matter as much as the components themselves. A `vpc` is a prerequisite for the `eks` cluster, which is a prerequisite for the `alb-controller`, and so on. Until now, answering "what would break if I change this?" or "what has to exist before I deploy that?" meant reading `atmos describe dependents` for one component at a time and stitching the picture together in your head. ## The Solution `atmos list dependencies` gives you the whole graph at a glance, as a tree: ```shell atmos list dependencies ``` Tree output keeps stack context next to the component hierarchy, with component type as secondary metadata on the right: ```text Dependencies Stack Component Type plat-ue2-dev app-config terraform ├──depends on ↓ │ ├──▶ dynamodb-table │ └──▶ kms-key └──required by ↑ └──(none) ``` Triangle markers show edge direction: `▶` means the component depends on that child, and `◀` means that child depends on the selected component. By default it walks every component and renders **both directions** — prerequisites and dependents. Scope it to a stack, focus on a single component, pick a direction, or emit structured data instead: ```shell # Limit to one stack atmos list dependencies --stack plat-ue2-dev # Focus on a single component in a stack atmos list dependencies vpc --stack plat-ue2-dev # Only what the component depends on (its prerequisites) atmos list dependencies vpc --stack plat-ue2-dev --direction forward # Only what depends on the component (its dependents) atmos list dependencies vpc --stack plat-ue2-dev --direction reverse # Machine-readable output for scripts and CI atmos list dependencies --format json ``` ## Why It Matters - **See blast radius before you change anything.** `--direction reverse` answers "what depends on this?" so you know what a change could affect. - **Understand deployment order.** `--direction forward` shows prerequisites, so you know what must exist first. - **Consistent with the rest of Atmos.** Dependencies come from the same `dependencies.components` / `settings.depends_on` sources used by `atmos describe dependents`, so the tree never contradicts your CI change-detection. ## Get Involved See the [`atmos list dependencies`](/cli/commands/list/dependencies) reference for all flags and output formats. As always, feedback and ideas are welcome in the [Atmos community](https://github.com/cloudposse/atmos). --- ## list and describe Commands Degrade Gracefully by Default Reading [`!terraform.state`](/functions/yaml/terraform.state) and [`!terraform.output`](/functions/yaml/terraform.output) can fail to resolve for a dozen different reasons — the backend hasn't been applied yet, your credentials aren't configured, you don't have access to the bucket, or the state you're reading just isn't the latest. Any one of those, on any one component, used to abort the _entire_ `list`/`describe` command — every other stack you did want to see disappeared behind one unrelated failure. The `list` and `describe` commands now degrade gracefully by default: an unresolved value is shown as `(computed)` instead of aborting the command, with a one-line summary telling you how many values were affected. ## The Problem Resolving `!terraform.state`/`!terraform.output` requires successfully reading real backend state, and there are many ways that read can fail: the backend hasn't been provisioned or applied yet, the caller isn't authenticated, the caller lacks access to the bucket or table, or the state simply doesn't have the output being asked for. Whatever the cause, that single failure used to take down the whole command, not just the one value that couldn't be resolved. The only workaround was `--process-functions=false`, which throws out every computed value, not just the unresolvable ones. ## The Fix These commands — `list stacks`, `list components`, `list settings`, `describe stacks`, `describe affected`, `list affected`, and `describe dependents` — all now default to `--error-mode=warn`: a recoverable resolution failure is substituted with `(computed)` and processing continues. Every other error class — malformed YAML, misconfiguration, anything not classified recoverable — still fails the command exactly as before. ```shell $ atmos list stacks Stack Bucket dev my-dev-bucket staging (computed) prod my-prod-bucket ⚠ 1 value could not be determined and is shown as (computed). Run with --logs-level=Debug for details, or --error-mode=strict to fail immediately instead. ``` The summary prints once at the end of the command, after the data — not once per degraded value. Full detail (which stack, component, function, and the underlying error) is always available via `--logs-level=Debug`, whether or not the summary is shown. ## Three Modes The `--error-mode` flag accepts three values: - `warn` (default) — degrade, substitute `(computed)`, print the summary above. - `silent` — degrade the same way, but skip the summary. Debug logs still show the detail. - `strict` — the old behavior: the first unresolvable value fails the command immediately. ```shell atmos list stacks --error-mode=strict atmos describe affected --error-mode=silent ``` That `(computed)` value isn't a raw `nil` slipping through — it's a typed value that renders consistently as `(computed)` in table, JSON, and YAML output alike, so scripts parsing `list ... --format=json` see a predictable string instead of `null` in one format and an empty string in another. ## Setting a Project-Wide Default Passing `--error-mode` on every invocation gets old fast. Set a default in `atmos.yaml`, still overridable per-command by the flag or an env var. `list` and `describe` are independent command families with independent defaults — you might want `describe` strict in CI while `list` stays lenient for interactive use, or vice versa — so each gets its own setting: ```yaml list: error_mode: warn # strict | warn | silent — applies to list stacks/components/settings/affected describe: error_mode: warn # strict | warn | silent — applies to describe stacks/affected/dependents ``` Precedence for `list` commands: `--error-mode` flag > `ATMOS_LIST_ERROR_MODE` env var > `list.error_mode` in `atmos.yaml` > `warn`. Precedence for `describe` commands is the same shape, with `ATMOS_DESCRIBE_ERROR_MODE` and `describe.error_mode` in place of the `list` equivalents. ## Breaking Change If you relied on the previous hard-fail-on-first-error behavior — for example, a CI check that intentionally wanted `list stacks` to fail when a backend wasn't ready — set `--error-mode=strict` (or `list.error_mode: strict` / `describe.error_mode: strict` in `atmos.yaml`, matching the command family you rely on) to restore it explicitly. For usage and configuration, see [List Command Configuration](/cli/configuration/list). ## Get Involved Try [`atmos list stacks`](/cli/commands/list/stacks) against a stack you haven't fully provisioned yet, and let us know what you think in [GitHub Discussions](https://github.com/cloudposse/atmos/discussions). --- ## Matrix Output for List Instances [`atmos list instances`](/cli/commands/list/list-instances) now supports `--format=matrix`, producing GitHub Actions-compatible JSON for driving parallel CI/CD jobs — the same format already available in [`atmos describe affected`](/cli/commands/describe/affected). ## What Changed A new `matrix` output format generates a `{"include":[...]}` JSON structure that plugs directly into GitHub Actions `strategy.matrix`: ```bash atmos list instances --format=matrix ``` ```json {"include":[{"stack":"ue1-dev","component":"vpc","component_path":"components/terraform/vpc","component_type":"terraform"},{"stack":"ue1-dev","component":"eks","component_path":"components/terraform/eks","component_type":"terraform"}]} ``` Each entry contains four fields: `stack`, `component`, `component_path`, and `component_type`. The [`--output-file`](/cli/commands/list/list-instances#flags) flag writes results in `key=value` format for `$GITHUB_OUTPUT`: ```bash atmos list instances --format=matrix --output-file=$GITHUB_OUTPUT ``` This writes: ``` matrix={"include":[...]} count=42 ``` ## Why This Matters `atmos describe affected` generates a matrix of _changed_ components for targeted CI. But some workflows need a matrix of _all_ instances — for example, scheduled drift detection, compliance scans, or full-fleet operations. Previously, you had to script your own extraction from `atmos list instances --format=json`. Now it's a single flag. ## How to Use It ### GitHub Actions Example ```yaml jobs: enumerate: runs-on: ubuntu-latest outputs: matrix: ${{ steps.instances.outputs.matrix }} steps: - uses: actions/checkout@v6 - name: List all instances id: instances run: atmos list instances --format=matrix --output-file=$GITHUB_OUTPUT deploy: needs: enumerate runs-on: ubuntu-latest strategy: matrix: ${{ fromJson(needs.enumerate.outputs.matrix) }} steps: - run: echo "Deploying ${{ matrix.component }} to ${{ matrix.stack }}" ``` ## Get Involved Found an issue or have a feature request? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## `atmos list instances`: --stack, --filter, and --query now work Three documented flags on [`atmos list instances`](/cli/commands/list/list-instances) were silently ignored: [`--stack`](/cli/commands/list/list-instances#flags), [`--filter`](/cli/commands/list/list-instances#flags), and [`--query`](/cli/commands/list/list-instances#flags). They now do what the docs say. ## What Changed | Flag | Before | After | | ---------- | -------------------------------------------- | -------------------------------------------------------------------------------------- | | `--stack` | Returned every component in every stack. | Filters with `path.Match` glob semantics (e.g. `tenant1-*`). | | `--filter` | TODO stub — no filtering applied. | Evaluates a YQ predicate per row and keeps rows where the expression is truthy. | | `--query` | Set on the options struct but never read. | Projects each row via YQ. Scalars land in a `value` column; maps become row keys. | The fix also closes a latent ENV-precedence gap: `ATMOS_LIST_FORMAT` and `ATMOS_UPLOAD` are now honored. Previously the implementation re-read [`--format`](/cli/commands/list/list-instances#flags) and [`--upload`](/cli/commands/list/list-instances#flags) directly from cobra, bypassing viper. ## How to Use It ```shell # Filter by stack glob — the original bug report atmos list instances --stack 'tenant1-*' # YQ predicate on instance rows atmos list instances --filter '.component == "vpc"' atmos list instances --filter '.vars.region == "us-east-2"' # YQ projection — pull specific values across all instances atmos list instances --query '.vars.region' \ --columns 'Stack={{ .stack }},Region={{ .value }}' # Map projection for multi-field extraction atmos list instances \ --query '{"region": .vars.region, "tenant": .vars.tenant}' \ --columns 'Stack={{ .stack }},Tenant={{ .tenant }},Region={{ .region }}' ``` `--filter` and `--query` are rejected with `--format=tree` and `--format=matrix` — those output modes are not row-shaped, so per-row transforms have no meaningful target. Use them with `table`, `json`, `yaml`, `csv`, or `tsv`. ## Why This Matters When a documented flag silently does nothing, it's worse than not having the flag at all — users build pipelines and dashboards assuming the filter is applied. The `--upload` workflow in particular was sending every instance across every stack instead of the targeted subset; this change fixes that mismatch between docs and behavior. `list metadata --stack` got the same fix in passing — it shared the same underlying `processInstances` plumbing. ## Get Involved If you find another `list` flag that doesn't do what the docs claim, please [open an issue](https://github.com/cloudposse/atmos/issues). --- ## Full Template and YAML-Function Control for `atmos list` Every [`atmos list`](/cli/commands/list/usage) subcommand that processes stack manifests now accepts `--process-templates` and `--process-functions` (with matching `ATMOS_PROCESS_TEMPLATES` / `ATMOS_PROCESS_FUNCTIONS` env vars), matching the flag surface of [`atmos describe affected`](/cli/commands/describe/affected) and [`atmos describe stacks`](/cli/commands/describe/stacks). Defaults are `true` across the board. ## What Changed Five commands gained the two flags: - `atmos list instances` - `atmos list components` - `atmos list metadata` - `atmos list sources` - `atmos list stacks` [`atmos list affected`](/cli/commands/list/affected), [`atmos list settings`](/cli/commands/list/settings), and [`atmos list values`](/cli/commands/list/list-values) (plus its `list vars` alias) already had them; the naming, defaults, and env-var bindings now line up with the rest of the `list` family and with `describe affected` / `describe stacks` / `describe component`. ## Why This Matters The two flags control two different things, and the distinction finally matches what's actually happening: - **`--process-templates`** toggles Go template processing — including the `atmos.Component(...)` template function. - **`--process-functions`** toggles YAML functions — [`!terraform.state`](/functions/yaml/terraform.state), [`!terraform.output`](/functions/yaml/terraform.output), [`!store`](/functions/yaml/store), `!aws.*`, and friends. Before this release, the flag descriptions and docs conflated the two. If you wanted to run `atmos list instances --upload` in CI against a repo where component sections call `atmos.Component(...)` inside Go templates, you had no user-facing dial — the combination was pinned to `processTemplates=true, processYamlFunctions=false`, and the upload path would fail with `No valid credential sources found` when Terraform tried to read a remote backend. Now you can mix and match: ```bash # CI upload with full processing (default) atmos list instances --upload # Skip terraform-dependent functions in environments without `tofu` atmos list instances --process-functions=false # Tabular listing without template expansion (cheaper, no atmos.Component calls) atmos list components --process-templates=false ``` Both flags accept `true` / `false` explicitly and can be set via `ATMOS_PROCESS_TEMPLATES` / `ATMOS_PROCESS_FUNCTIONS` in CI. ## How to Use It Defaults (`true`) cover the common case — no flags needed for CI uploads: ```bash atmos list instances --upload ``` Use the flags (or env vars) to opt out when you need to: ```bash # Skip YAML functions — no `tofu` / `terraform` on $PATH locally atmos list instances --process-functions=false # Skip templates too, for the lightest stack pass atmos list components --process-templates=false --process-functions=false # Same knobs via env, handy in CI workflows ATMOS_PROCESS_FUNCTIONS=false atmos list instances ``` If you're running locally without `tofu` / `terraform` on `$PATH` and hit errors from `!terraform.state` or `!terraform.output`, flip `--process-functions=false` — templates that don't touch terraform outputs still render correctly. Docs for each command list the two flags in full: see [`atmos list instances`](/cli/commands/list/list-instances), [`atmos list components`](/cli/commands/list/components), [`atmos list metadata`](/cli/commands/list/list-metadata), [`atmos list sources`](/cli/commands/list/sources), and [`atmos list stacks`](/cli/commands/list/stacks). For usage and configuration, see [List Command Configuration](/cli/configuration/list). ## Get Involved Found an issue or have a feature request? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Selective YAML Function Bypass with --skip on `atmos list` Every [`atmos list`](/cli/commands/list/usage) subcommand that processes stack manifests now accepts `--skip ` and the matching `ATMOS_SKIP` env var, mirroring the surface already exposed by [`atmos describe affected`](/cli/commands/describe/affected), [`atmos describe component`](/cli/commands/describe/component), and [`atmos describe stacks`](/cli/commands/describe/stacks). Use it to bypass a single YAML function while leaving the rest of YAML function processing — including [`!template`](/functions/yaml/template) — fully enabled. ## What Changed Five commands gained the flag: - `atmos list instances` - `atmos list components` - `atmos list metadata` - `atmos list sources` - `atmos list stacks` [`atmos list affected`](/cli/commands/list/affected) already had `--skip`; with this release, the flag surface lines up across the family. The existing `ATMOS_AFFECTED_SKIP` env var continues to work on `list affected` for backward compatibility. ## Why This Matters `--process-functions=false` is a blunt instrument. It turns off **all** YAML function evaluation — including `!template`, which many users rely on to compute settings like `settings.pro.enabled` to a real boolean. The concrete motivating failure: `atmos list instances --upload` in CI, against a repo where a few component sections include [`!terraform.state`](/functions/yaml/terraform.state) calls. The upload itself doesn't need those backends to resolve, but it does need `settings.pro.enabled` (computed via `!template`) to be a real boolean so Atmos Pro accepts the payload. Before this release, that was a stuck combination: - `--process-functions=true` (default) → the upload tries to talk to a remote backend and fails. - `--process-functions=false` → `!template` stops evaluating, so `settings.pro.enabled` arrives as a literal string and Atmos Pro rejects it with `expected boolean but received invalid value (string)`. With `--skip`, you can name the single function you want to bypass and leave everything else on: ```bash atmos list instances --upload --skip terraform.state ``` ## How to Use It Skip a single function: ```bash atmos list instances --upload --skip terraform.state ``` Skip multiple: ```bash atmos list stacks --skip terraform.state --skip terraform.output ``` Or via env var (handy in CI workflows): ```bash ATMOS_SKIP=terraform.state atmos list instances --upload ``` The flag accepts the same function names as `atmos describe stacks --skip`. The value is the YAML function name without the leading `!` (for example `terraform.state`, `terraform.output`, `store`). Docs for each command list the flag in full: [`atmos list instances`](/cli/commands/list/list-instances), [`atmos list components`](/cli/commands/list/components), [`atmos list metadata`](/cli/commands/list/list-metadata), [`atmos list sources`](/cli/commands/list/sources), and [`atmos list stacks`](/cli/commands/list/stacks). For usage and configuration, see [List Command Configuration](/cli/configuration/list). ## Get Involved Found an issue or have a feature request? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## New !literal YAML Function for Template Passthrough Atmos now supports the [`!literal`](/functions/yaml/literal) YAML function, which preserves values exactly as written without any template processing. This solves a common pain point when passing template syntax to downstream tools like Terraform, Helm, or ArgoCD. ## The Problem When working with multi-tool pipelines, you often need to pass template-like syntax through Atmos to downstream tools. For example, you might want to pass Helm template expressions or Terraform `templatefile()` variables. Previously, Atmos would attempt to evaluate these expressions, causing errors or unexpected behavior. ```yaml # This would fail - Atmos tries to evaluate {{external.email}} db_users: - "{{external.email}}" # This would also fail - Atmos tries to evaluate ${hostname} user_data: "echo ${hostname}" ``` The workarounds were awkward and error-prone: ```yaml # Double braces - fragile and hard to read db_users: - "{{'{{external.email}}'}}" # Template escaping - verbose db_users: - "{{ `{{external.email}}` }}" ``` ## The Solution The new `!literal` function provides a clean, self-documenting way to bypass template processing: ```yaml # Clear intent, preserves value exactly db_users: - !literal "{{external.email}}" - !literal "{{external.admin}}" # Works with inline arrays too users: [!literal "{{user1}}", !literal "{{user2}}"] ``` ## Use Cases ### Terraform Variables Pass `${var.name}` syntax to Terraform templates: ```yaml vars: user_data_template: !literal "#!/bin/bash\necho ${hostname}" config_file: !literal "${var.environment}-config.json" ``` ### Helm Values Pass Helm template expressions: ```yaml vars: ingress_annotations: !literal "{{ .Values.ingress.class }}" service_name: !literal "{{ .Release.Name }}-api" ``` ### ArgoCD ApplicationSets Pass ArgoCD generator expressions: ```yaml vars: config_url: !literal "{{external.config_url}}" cluster_name: !literal "{{name}}" ``` ### Multiline Scripts Works with YAML multiline syntax: ```yaml vars: startup_script: !literal | #!/bin/bash echo "Hello ${USER}" export CONFIG={{config_path}} ``` ## Get Started The `!literal` function is available now. Check out the [documentation](/functions/yaml/literal) for more examples and details. --- ## Run Terraform Tests Locally Against Cloud Emulators Terraform's native testing framework (`*.tftest.hcl`) is great — until you hit a `run` block with `command = apply`. Those blocks create **real** infrastructure, so running them means a cloud account, credentials, and spend. Atmos now lets you point `terraform test` at a **local emulator**, so the same apply-backed tests run for free and hermetically on your laptop or in CI. ## The Problem A meaningful Terraform test applies resources and asserts on the result: ```hcl run "provisions_resources_against_emulator" { command = apply assert { condition = output.bucket_id == "atmos-demo-test" error_message = "The S3 bucket was not created" } } ``` Because `command = apply` provisions for real, this almost never runs locally. It needs cloud credentials, it costs money, and it leaves residue you have to clean up. So the most valuable tests — the ones that actually create infrastructure — rarely run until CI, against a real account. ## The Solution Atmos [emulators](/stacks/components/emulator) provide a local, containerized stand-in for AWS (and GCP, Azure, and more). Bind a component to an `aws/emulator` identity and Atmos wires the AWS provider — endpoint, dummy credentials, path-style S3, skip-flags — into **every** Terraform run, including `terraform test`. Your component code doesn't change between local and real cloud: ```shell atmos terraform test app -s fixtures ``` That's it. The component hook starts the emulator, applies the fixture VPC, the `apply` run blocks create the app resources against that VPC, and everything is torn down — no AWS account, no credentials, no `providers.tf`. Here's that stack discovery and test run end to end: [View the full example](/examples/terraform-tests) ## Lifecycle Hooks Bring Fixtures Up and Down You don't even start the emulator or provision fixtures yourself. The component declares ordered lifecycle hooks using `kind: steps`, bound to the new `before.terraform.test` and `after.terraform.test` events: ```yaml components: terraform: app: hooks: test-fixtures-up: kind: steps on_failure: fail events: [before.terraform.test] with: - type: emulator component: aws action: up - type: atmos command: terraform apply vpc -s fixtures -auto-approve test-fixtures-down: kind: steps events: [after.terraform.test] when: always with: - type: atmos command: terraform destroy vpc -s fixtures -auto-approve - type: emulator component: aws action: down ``` [`atmos terraform test`](/cli/commands/terraform/test) fires these hooks around the run: the emulator comes up, the fixture VPC is applied, the app test uses that VPC, and `when: always` guarantees teardown even when a test fails. The app component owns its test fixture lifecycle without scripts or a custom command wrapper. When a test needs fixture outputs, define them under the component's `test.vars`. Atmos resolves those values after the setup hook, so `test.vars` can use [`!terraform.state`](/functions/yaml/terraform.state) to read the fixture VPC ID and pass it into `.tftest.hcl` as a declared Terraform test variable. ## In CI: Test Summaries `terraform test` now plugs into Atmos's native-CI reporting, the same path as `plan` and `apply`. Turn it on in `atmos.yaml` — `ci.enabled` is the master switch: ```yaml ci: enabled: true summary: enabled: true ``` In a GitHub Actions job, a passing or failing **step summary** is then written to the job summary — per-run pass / fail / skip results, with the failing assertions inlined — so you see what broke without digging through logs. ## Why This Matters - **Run the valuable tests locally.** Apply-backed assertions — the ones that actually create infrastructure — run on a laptop in seconds, for free. - **Hermetic and repeatable.** No shared account, no drift, no cleanup. Every run starts from a clean sandbox. - **Identical config everywhere.** Because the emulator is bound through Atmos identity and provider generation, the component is byte-for-byte the same locally and in CI. ## Get Involved Try the [`terraform-tests` example](https://github.com/cloudposse/atmos/tree/main/examples/terraform-tests), read the [emulator component reference](/stacks/components/emulator), and see the original [emulators announcement](/changelog/introducing-emulators) for the bigger picture. A container runtime (Docker or Podman) is the only prerequisite. --- ## Locals Context Access Locals can access `{{ .settings }}`, `{{ .vars }}`, and `{{ .env }}` from the same file during template resolution. ## The Enhancement Previously, locals could only reference other locals via `{{ .locals.* }}`. Now they can also access: - `{{ .settings.* }}` - Settings defined in the same file - `{{ .vars.* }}` - Vars defined in the same file - `{{ .env.* }}` - Environment variables ### Example ```yaml settings: version: v1 vars: stage: dev locals: namespace: acme label: "{{ .locals.namespace }}-{{ .vars.stage }}-{{ .settings.version }}" components: terraform: myapp: vars: name: "{{ .locals.label }}" ``` See it resolve with structured values: [View the full example](/examples/locals) ## File-Scoped Context The context available to locals comes from **the same file only**. Locals cannot access settings or vars from imported files. If you need values from imported files, use `vars` or `settings` (which inherit across imports). ## Upgrade Upgrade Atmos to get this enhancement. Existing locals configurations will continue to work. ## References - [Locals Documentation](/stacks/locals) - [DRY Configuration with Locals](/design-patterns/configuration-composition/locals) --- ## YAML Functions in Locals Locals now support YAML functions like [`!env`](/functions/yaml/env), [`!exec`](/functions/yaml/exec), [`!store`](/functions/yaml/store), [`!terraform.state`](/functions/yaml/terraform.state), and [`!terraform.output`](/functions/yaml/terraform.output). ## The Feature Locals can now use all Atmos YAML functions to fetch dynamic values: - `!env` - Environment variables - `!exec` - Command execution - `!store` - Store lookups - `!terraform.state` - Terraform state queries - `!terraform.output` - Terraform outputs from other components ### Example with Environment Variables ```yaml locals: api_endpoint: !env API_ENDPOINT api_url: "https://{{ .locals.api_endpoint }}/api/v1" components: terraform: myapp: vars: api_url: "{{ .locals.api_url }}" ``` ### Example with Terraform State ```yaml locals: # Fetch from another component's terraform state vpc_id: !terraform.state vpc .vpc_id subnet_ids: !terraform.state vpc .private_subnet_ids components: terraform: eks: vars: vpc_id: "{{ .locals.vpc_id }}" subnet_ids: "{{ .locals.subnet_ids }}" ``` ### Example with Store ```yaml locals: db_password: !store secrets/database .password connection_string: "postgresql://app:{{ .locals.db_password }}@db.example.com/mydb" components: terraform: backend: vars: database_url: "{{ .locals.connection_string }}" ``` ## How It Works 1. YAML functions in locals are processed during stack configuration loading 2. The resolved values are available to other locals and component vars 3. Locals can combine YAML function results with Go templates ## Use Cases - **Environment-specific configuration**: Use `!env` to inject environment-specific values - **Cross-component references**: Use `!terraform.state` to reference outputs from other components - **Secret management**: Use `!store` to fetch secrets from your configured store - **Dynamic values**: Use `!exec` to run commands and capture output ## Upgrade Upgrade Atmos to get this feature. Existing locals configurations continue to work unchanged. ## References - [Locals Documentation](/stacks/locals) - [YAML Functions Documentation](/functions/yaml) --- ## Fixing Gatekeeper SIGKILLs on Downloaded Toolchain Verifiers On macOS, Atmos would sometimes install a verifier CLI (like `cosign`, used to check signatures on downloaded toolchain binaries) through `verifier_install: auto`, checksum-verify it successfully — and then have the OS kill it the instant Atmos tried to run it. Gatekeeper and AMFI re-validate a downloaded binary's code-signing trust on every execution, not just the first Finder launch, so a checksum match alone wasn't enough to let a freshly downloaded, ad-hoc-signed release asset actually run. ## The Fix After Atmos installs and checksum-verifies a verifier binary, it now strips the macOS quarantine extended attributes (`com.apple.quarantine`, `com.apple.provenance`) and ad-hoc re-signs the binary — the same trick Homebrew uses for downloaded formula binaries. This only ever runs on binaries Atmos itself just downloaded into its own bootstrap path; a verifier already found on `PATH` is user-managed and already OS-trusted, so it's left untouched. On Linux and Windows, this is a no-op — only macOS re-validates trust on every exec. ## How to Use It This is on by default (`verifier_trust: auto`) — most users won't need to touch it. To opt out entirely: ```yaml toolchain: verification: verifier_trust: disabled ``` See the [toolchain verification docs](/cli/configuration/toolchain/verification) for the full settings reference. ## Get Involved Questions or feedback? Open an issue on [GitHub](https://github.com/cloudposse/atmos) or join the conversation in our community Slack. --- ## Breaking Change: macOS Now Uses ~/.config for XDG Paths Atmos now follows CLI tool conventions on macOS, using `~/.config`, `~/.cache`, and `~/.local/share` instead of `~/Library/Application Support`. This ensures seamless integration with Geodesic and consistency with other DevOps tools. ## What Changed Starting with this release, Atmos on **macOS** uses different default paths for XDG Base Directory Specification: **Before:** - Config: `~/Library/Application Support/atmos/` - Cache: `~/Library/Caches/atmos/` - Data: `~/Library/Application Support/atmos/` **After:** - Config: `~/.config/atmos/` - Cache: `~/.cache/atmos/` - Data: `~/.local/share/atmos/` **Note:** This only affects macOS. Linux and Windows paths remain unchanged. ## Why This Change? ### The Problem When we implemented XDG Base Directory Specification support, we used the `github.com/adrg/xdg` library which defaults to `~/Library/Application Support` on macOS. This follows macOS conventions for **GUI applications**. However, this created problems: 1. **Geodesic Incompatibility**: Geodesic mounts `~/.config` by default, not `~/Library/Application Support` 2. **Ecosystem Inconsistency**: Other CLI tools (gh, git, packer, stripe, op, kubectl, docker, terraform) all use `~/.config` on macOS 3. **Platform Fragmentation**: Different paths on Linux vs macOS made cross-platform workflows confusing ### CLI Tools vs GUI Applications Research into the CLI tool ecosystem revealed a clear pattern: **CLI Tools** (command-line only): - Use `~/.config`, `~/.cache`, `~/.local/share` on **all platforms** including macOS - Examples: GitHub CLI (`gh`), HashiCorp Packer, Stripe CLI, 1Password CLI - Benefits: Consistent paths across Linux/macOS, works with containerized environments **GUI Applications** (native Mac apps): - Use `~/Library/Application Support`, `~/Library/Caches` - Provides better macOS system integration - Standard for applications in `/Applications` Since **Atmos is a CLI tool**, it should follow CLI conventions, not GUI conventions. ## Impact on Users ### Most Users Not Affected If you're upgrading from versions **prior to v1.195.0**, you're not affected because: - Old versions used `~/.aws/atmos/` (legacy path) - The `~/Library/Application Support` path was never released in a stable version ### macOS Users Running Unreleased Versions If you were using Atmos auth on macOS from the main branch between v1.195.0 and this release: ### Option 1: Use new path (recommended) ```bash # Re-login to store credentials in new location atmos auth login ``` ### Option 2: Keep existing location ```bash # Add to ~/.zshrc or ~/.bash_profile export ATMOS_XDG_CONFIG_HOME="$HOME/Library/Application Support" ``` **Note**: This keeps credentials in the old location but affects **all** Atmos XDG paths (config, cache, data), not just credentials. This may cause issues with Geodesic which expects credentials in `~/.config`. We recommend Option 1 (re-login) instead. ### Option 3: Move credentials ```bash if [ -d "$HOME/Library/Application Support/atmos" ]; then mkdir -p ~/.config mv "$HOME/Library/Application Support/atmos" ~/.config/ fi ``` ## Benefits ### Seamless Geodesic Integration Geodesic automatically mounts these directories: - `~/.aws` - `~/.config` ← Atmos credentials now stored here - `~/.ssh` - `~/.kube` - `~/.terraform.d` **Configuration needed for Geodesic users:** Geodesic sets system-wide XDG environment variables (`XDG_CONFIG_HOME=/etc/xdg_config_home`) that need to be overridden. Add to your Geodesic Dockerfile: ```dockerfile # Override Geodesic's system XDG paths to use home directory ENV ATMOS_XDG_CONFIG_HOME=$HOME/.config ENV ATMOS_XDG_DATA_HOME=$HOME/.local/share ENV ATMOS_XDG_CACHE_HOME=$HOME/.cache ``` This ensures Atmos credentials are stored in mounted directories (`~/.config`) rather than non-mounted system directories (`/etc/xdg_config_home`). See [Configuring Geodesic](/tutorials/configuring-geodesic) for details. ### Consistent Cross-Platform Paths ```bash # Linux ~/.config/atmos/aws/provider/credentials # macOS (new) ~/.config/atmos/aws/provider/credentials # Same path on both platforms! ``` ### Ecosystem Alignment Your `~/.config` directory now contains configuration for all your CLI tools: ```text ~/.config/ ├── atmos/ # Atmos (now!) ├── gh/ # GitHub CLI ├── git/ # Git ├── packer/ # HashiCorp Packer ├── stripe/ # Stripe CLI └── op/ # 1Password CLI ``` ## Technical Implementation We override the `adrg/xdg` library's macOS defaults using an `init()` function: ```go func init() { if runtime.GOOS == "darwin" { xdg.ConfigHome = filepath.Join(homeDir, ".config") xdg.DataHome = filepath.Join(homeDir, ".local", "share") xdg.CacheHome = filepath.Join(homeDir, ".cache") } } ``` This ensures **all code** in Atmos (even code that directly imports `github.com/adrg/xdg`) gets CLI tool conventions on macOS. ## Documentation Updates - [Configuring Geodesic with Atmos Auth](/tutorials/configuring-geodesic) - Simplified configuration (no setup needed!) - [Auth Usage Guide](/cli/commands/auth/usage) - Updated with correct macOS paths. ## Migration Support If you encounter issues: 1. Check your current credentials location: ```bash ls -la ~/Library/Application\ Support/atmos ls -la ~/.config/atmos ``` 2. Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues) if you need help ## References - [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) - [Stack Overflow discussion on XDG equivalents on macOS](https://stackoverflow.com/questions/3373948/equivalents-of-xdg-config-home-and-xdg-data-home-on-mac-os-x) - [Geodesic](https://github.com/cloudposse/geodesic) --- This change aligns Atmos with CLI tool best practices and ensures seamless integration with containerized development environments. macOS users now enjoy the same consistent experience as Linux users! For usage and configuration, see [CLI Configuration](/cli/configuration). --- ## Configure MCPs once in Atmos, use it from Claude Code, Codex, and Gemini Claude Code, OpenAI Codex CLI, and Google Gemini CLI all speak MCP, but each wants its own config format, its own credentials flow, and its own idea of where binaries live. This post shows how to centralize all of it — server configuration, AWS credentials, and toolchain version — in **one `atmos.yaml`** that every AI coding assistant uses unchanged. [Atmos Auth](/cli/configuration/auth) is the only place AWS credentials live; each MCP server is automatically wrapped with the right identity for the question it'll answer (billing → payer account, CloudTrail → audit, IAM → root, workload queries → dev/rpg/staging). One [`atmos auth login`](/cli/commands/auth/login) covers them all — no API keys in CLI configs, no `AWS_PROFILE` swapping between prompts. The server set spans the [Atmos MCP server](/ai/mcp-server) for project stacks, the [AWS MCP server suite](https://github.com/awslabs/mcp) for live cloud queries, and the [Atmos Pro MCP server](https://atmos-pro.com/mcp/install) for drift, deployment, and audit history. The [Atmos toolchain](/cli/configuration/toolchain) pins binaries so every assistant runs the same binary. See the example: [`examples/mcp-for-ai-coding-assistants/`](/examples/mcp-for-ai-coding-assistants). --- ## Your AI knows your stacks and components. And your cloud. And your history. In one prompt, your AI coding assistant answers: - What's **configured** in your infrastructure - What's **deployed** in your cloud accounts - What **changed** — when, why, how, and by whom Centralized auth. Centralized security and permissions. One `atmos.yaml`. [Try the example →](/examples/mcp-for-ai-coding-assistants) --- ## The Problem If you've tried to use the `awslabs` MCP servers with more than one AI coding assistant, with each MCP server requiring different credentials, you've probably hit this: - **Three different config files.** Claude Code reads `.mcp.json`, OpenAI Codex CLI reads `~/.codex/config.toml` (TOML, not JSON), and Google Gemini CLI reads `.gemini/settings.json`. The schemas are _almost_ the same but not quite, and each CLI looks in a different place. - **Three different ways to register servers.** Each CLI has its own `mcp add` command with subtly different flags. Or you can hand-edit the config file. Or both. - **AWS credentials need to flow through.** Every AWS MCP server needs AWS credentials. Without help, that means `aws configure`, juggling `AWS_PROFILE`, copy-pasting role ARNs into shell wrappers, or worse — baking credentials into config files that get committed by accident. - **Toolchain drift.** `uvx`, `npx`, and friends need to exist on PATH when the AI CLI spawns the MCP server subprocess. If your Codex install uses a different shell session than Claude Code, one of them might not find `uvx`. Each of these is solvable individually. Solving all of them for three CLIs is annoying. ## The Idea `atmos.yaml` already had everything you need: ```yaml toolchain: aliases: uv: astral-sh/uv # pin uvx to a known version mcp: enabled: true # expose Atmos's own tools as an MCP server servers: # external MCP servers Atmos manages atmos: command: atmos args: ["mcp", "start"] aws-billing: command: uvx args: ["awslabs.billing-cost-management-mcp-server@latest"] env: { AWS_REGION: "us-east-1" } identity: "readonly" # ← Atmos Auth identity # … and more auth: providers: sso: kind: aws/iam-identity-center start_url: "https://your-org.awsapps.com/start" region: "us-east-1" identities: readonly: kind: aws/permission-set default: true via: provider: sso principal: name: ReadOnlyAccess account: id: "123456789012" ``` [`atmos mcp export`](/cli/commands/mcp/export) reads the `mcp.servers` block, wraps every server with `identity` in [`atmos auth exec -i --`](/cli/commands/auth/exec), prepends the toolchain's `PATH` so `uvx` resolves, and writes a `.mcp.json` ready for any MCP-compatible client. For Claude Code that's the native format. Gemini's `.gemini/settings.json` is structurally identical, so the same export works with `--output .gemini/settings.json`. For Codex, it's TOML format — and the example [walks you through that](/examples/mcp-for-ai-coding-assistants). The result: many MCP servers, three AI CLIs, one Atmos config. ## What Each AI CLI Gets All three CLIs end up with the same set of MCP servers: | Server | Purpose | Transport | Auth | |-----------------|-----------------------------------------------|-----------|------------------------| | **atmos** | `describe component`, `list stacks`, etc. | stdio | Atmos Auth | | **atmos-pro** | Drift, deployments, workflow runs, audit log | HTTP | Browser OAuth (GitHub) | | aws-docs | Search AWS documentation | stdio | None (public) | | aws-knowledge | Managed AWS knowledge base (remote) | stdio | None (public) | | aws-pricing | Real-time pricing | stdio | AWS (via Atmos Auth) | | aws-billing | Billing summaries | stdio | AWS (via Atmos Auth) | | aws-iam | IAM role/policy analysis | stdio | AWS (via Atmos Auth) | | aws-cloudtrail | Event history | stdio | AWS (via Atmos Auth) | | aws-security | Well-Architected security | stdio | AWS (via Atmos Auth) | | aws-api | Direct AWS CLI (read-only by default) | stdio | AWS (via Atmos Auth) | The AWS servers plus the embedded **atmos** server are stdio-transport and managed through `atmos mcp export`. The **atmos-pro** entry is HTTP- transport (it runs on `https://atmos-pro.com/mcp`), so it's registered directly with each AI CLI alongside the exported config — see the [Atmos Pro section](#the-atmos-pro-mcp-server-saas) below. ### What the `atmos` MCP server does The first entry — **atmos** — is the [Atmos MCP server itself](/ai/mcp-server), running inside the `atmos` binary via [`atmos mcp start`](/cli/commands/mcp/start). Including it in the exported config gives your AI coding assistant programmatic access to **your Atmos project** — the stacks, components, manifests, and the `atmos` CLI as a whole — alongside the AWS introspection tools. It exposes more than 20 tools: - **Stack & component introspection** (read-only). - **Execution** (permission-gated). - **Security & compliance** — `atmos_list_findings`, `atmos_describe_finding`, `atmos_analyze_finding`, `atmos_compliance_report`. Write-side tools (`write_*`, `execute_bash_command`) respect [Atmos's tool permission model](/cli/configuration/ai/tools#permission-modes). The MCP server runs in YOLO mode by default, so the AI coding assistant's own permission UI — Claude Code's per-tool approval, Codex's confirm prompt, Gemini's consent dialog — handles execution approval. ### The Atmos Pro MCP server (SaaS) [Atmos Pro](https://atmos-pro.com/) is the fastest way to deploy your apps on AWS with Terraform and GitHub Actions. The [Atmos Pro MCP server](https://atmos-pro.com/mcp/install) lets your AI assistant query everything Atmos Pro knows about your workspace — drift, deployments, workflow runs, audit log — without leaving the editor. Unlike the local servers, this one runs on `https://atmos-pro.com/mcp` over **HTTP transport**. Auth is a one-time browser OAuth (GitHub login); short-lived tokens land in your OS keychain, revocable from the Atmos Pro UI. No API keys to leak. It exposes capabilities across these areas: - **Workflows & deployments** — workflow runs with approval states and job summaries; deployment history linked to commits and PRs; failed-step logs and failure-pattern analysis over time. - **Triage & diagnostics** — list/inspect drifted or errored instances, stacks, and components; access repair history and recommendations; structured failure explanations. - **Historical context** — when failures began, flapping detection, stability comparisons against previous periods, full audit log. - **Security & access** — audit every agent tool call (actor type, client name, arguments); permission reviews; compliance audit trail. ### How the three layers complement each other > The **AWS servers** tell the assistant what is **deployed**. > The **atmos** server tells it what is **defined**. > The **atmos-pro** server tells it what is **happening over time** — > drift against truth, who/what changed it, why a run failed, when > problems began. So the AI can answer cross-layer questions in one prompt: > _"Why did our vpc deploy fail yesterday — what changed in the > stack config, what does Atmos Pro show for that run, and which > AWS resource is now out of sync?"_ …pulling deployment history from `atmos-pro`, declared config from `atmos` (or directly from your repo Atmos config), and live state from `aws-api` in a single coherent answer. ## Pair with Atmos Agent Skills MCP gives the AI **tools** — to inspect stacks, query AWS, check Atmos Pro. [Atmos Agent Skills](/cli/commands/ai/skill) give it **knowledge** — domain-specific skills (stacks, components, validation, YAML functions, vendoring, toolchain, GitOps, auth, …) that activate based on what you ask. Without skills, an AI assistant falls back to general training data that may generate invalid YAML, miss features like [`!store`](/functions/yaml/store) / [`!terraform.output`](/functions/yaml/terraform.output), or use wrong CLI flags. With skills, the assistant loads the right Atmos context just before answering. The skills are built on the open [AGENTS.md](https://agents.md/) and [Agent Skills](https://agentskills.io/specification) standards, so they work across all three CLIs (Claude Code, Codex CLI, Gemini CLI) plus Cursor, Windsurf, GitHub Copilot, and others. For Claude Code: ```bash /plugin marketplace add cloudposse/atmos /plugin install atmos@cloudposse ``` For other tools, see the [AI Agent Skills announcement](/changelog/ai-agent-skills) for tool-specific install paths. **Why pair them:** MCP answers _"what does this code do?"_ by reading files and live state; skills answer _"what should this code do?"_ by teaching the assistant Atmos's conventions. The same prompt — _"set up cross-stack dependencies with remote state"_ — pulls live data through MCP **and** applies Atmos-native patterns ([`!terraform.state`](/functions/yaml/terraform.state), abstract components, inheritance) from the relevant skill. Stronger together than either alone. ## Wiring Up Each CLI Once you've run `atmos auth login`, each CLI takes a couple of commands. ### Claude Code ```bash # Generate .mcp.json in your project root. atmos mcp export # Start Claude Code from that directory. claude ``` Or use `claude mcp add` to register each server globally: ```bash claude mcp add --transport stdio aws-billing -- \ atmos auth exec -i readonly -- uvx awslabs.billing-cost-management-mcp-server@latest claude mcp add --transport stdio atmos -- atmos mcp start ``` The `atmos auth exec -i readonly --` wrapper is what Atmos Auth's credential injection rides on. The IAM role specified in `atmos.yaml`'s `identities.readonly` gets assumed and the resulting AWS\___KEY__ environment variables land in the MCP server's process. ### OpenAI Codex CLI Codex reads `~/.codex/config.toml`: ```toml [mcp_servers.atmos] command = "atmos" args = ["mcp", "start"] [mcp_servers.aws-billing] command = "atmos" args = ["auth", "exec", "-i", "readonly", "--", "uvx", "awslabs.billing-cost-management-mcp-server@latest"] [mcp_servers.aws-billing.env] AWS_REGION = "us-east-1" FASTMCP_LOG_LEVEL = "ERROR" ``` ### Google Gemini CLI Gemini's `.gemini/settings.json` is the same schema as `.mcp.json`, so you can export directly: ```bash # Per-project (check it in alongside atmos.yaml): atmos mcp export --output .gemini/settings.json # Or per-user: atmos mcp export --output ~/.gemini/settings.json # Then start Gemini in this directory: gemini ``` Or use `gemini mcp add`: ```bash gemini mcp add aws-billing -- \ atmos auth exec -i readonly -- uvx awslabs.billing-cost-management-mcp-server@latest gemini mcp add atmos -- atmos mcp start ``` :::tip Trusted Folders Gemini's [Trusted Folders feature](https://github.com/google-gemini/gemini-cli/blob/main/docs/trusted-folders.md) blocks MCP servers in untrusted directories. Trust the folder once via the Gemini UI/settings before the servers will start. ::: ### Atmos Pro (all three CLIs) The `atmos-pro` server is HTTP-transport and registered separately from the `atmos mcp export` flow. The OAuth handshake runs the first time the server is spawned — log in with GitHub, the token lands in your OS keychain (no API keys to leak, revocable from the Atmos Pro UI). ```bash # Claude Code claude mcp add --transport http atmos-pro https://atmos-pro.com/mcp # Gemini CLI gemini mcp add --transport http atmos-pro https://atmos-pro.com/mcp ``` For Codex CLI, append to `~/.codex/config.toml`: ```toml [mcp_servers.atmos-pro] type = "http" url = "https://atmos-pro.com/mcp" ``` Or merge the JSON entry into `.mcp.json` / `.gemini/settings.json` / `~/.claude.json`: ```json { "mcpServers": { "atmos-pro": { "type": "http", "url": "https://atmos-pro.com/mcp" } } } ``` ## What You Can Ask Same questions, three AIs: ```text List all IAM roles with AdministratorAccess. What did we spend on EC2 across all accounts last month? Is GuardDuty enabled in every region? What stacks are defined in this project? Validate every stack and report any errors. # Atmos Pro questions: Which workspaces have drift right now? Why did the last deploy of vpc in prod fail? Has this stack been flapping over the past week? # Cross-layer (mixes atmos-pro + atmos + aws-api): Why did our vpc deploy fail yesterday — what changed in the stack config, what does Atmos Pro show for that run, and which AWS resource is now out of sync? ``` The AI assistant picks which MCP tools to call. You don't specify them. ## Why This Matters Four benefits: 1. **Central management.** One `atmos.yaml`, versioned with your infrastructure repo. Onboarding a new engineer is `git clone` + `atmos auth login`. Adding a new MCP server is one block in `atmos.yaml` and one re-export. No per-engineer drift. 2. **Security — every credential, in one place.** Atmos Auth is the only place AWS credentials live. Each external MCP server is spawned via `atmos auth exec`, which resolves credentials at runtime and writes them only into that subprocess's env. The exported `.mcp.json` is safe to check into the repo if needed — the worst it leaks is the IAM role name. No static secrets in `~/.aws/credentials`, no `AWS_PROFILE` scattered across shells, no per-server authentication, no token files in each CLI's config dir. 3. **Convenience — one login, every account auto-routed.** Configure all the accounts you care about in `auth.identities`, run `atmos auth login` once, and Atmos picks the right account for every MCP server automatically (billing → payer, CloudTrail → audit, IAM analysis → root, workload introspection → dev). The AI calls a tool, and the right credentials are already there — no identity juggling between prompts, no `AWS_PROFILE` swapping, no re-login to ask a billing question after asking a VPC one. 4. **Toolchain hygiene.** `uvx` and friends are pinned to a known version via the Atmos toolchain. Every engineer's AI assistant uses the same binaries. No "but it works on my machine" with a different `uvx` minor. The example walks through the full setup end to end, including the per-CLI config layouts: [`examples/mcp-for-ai-coding-assistants/`](/examples/mcp-for-ai-coding-assistants). ## Related Examples - **[Atmos MCP integrations](/examples/mcp)** — Same external MCP server config, but Atmos drives the AI loop ([`atmos ai ask`](/cli/commands/ai/ask)) instead of an external CLI. Useful if you'd rather stay inside the `atmos` CLI than spawn Claude Code / Codex / Gemini. - **[Atmos AI with Claude Code](/examples/ai-claude-code)** — Use a Claude Pro/Max subscription as Atmos's AI provider (no Anthropic API key). Atmos hosts the AI conversation; Claude Code provides the model; MCP servers pass through. - **[Atmos AI (multi-provider)](/examples/ai)** — Multi-provider Atmos AI setup (Anthropic API, OpenAI API, Ollama, …). No external CLI; chat with your infra from `atmos ai ask`. ## Related Reading - [MCP Configuration in Atmos](/cli/configuration/mcp) — full reference for the `mcp:` section, including server-config fields and smart-routing. - [Atmos Auth](/cli/configuration/auth) — provider/identity model and `atmos auth login` flow. - [Atmos Toolchain](/cli/configuration/toolchain) — how toolchain aliases and [`atmos toolchain install`](/cli/commands/toolchain/install) work. - [Atmos MCP Server](/ai/mcp-server) — the Atmos-AI-tools-as-MCP-server mode, exposed to your AI coding assistant via the `atmos` entry in `mcp.servers`. - [Atmos Agent Skills](/cli/commands/ai/skill) — 21 domain-specific skills that complement MCP tools by giving AI assistants deep Atmos knowledge (stacks, components, validation, YAML functions, vendoring, GitOps, design patterns, …). - [AI Agent Skills announcement](/changelog/ai-agent-skills) — install paths for Claude Code, Codex, Gemini, Cursor, Windsurf, GitHub Copilot, JetBrains Junie, and Amazon Q. - [Atmos Pro MCP server install](https://atmos-pro.com/mcp/install) — HTTP-transport SaaS server for drift, deployments, and audit context. - [Atmos Pro MCP server announcement](https://atmos-pro.com/changelog/2026-05-09-mcp-server) — full capability list and the design choices behind the OAuth flow. - [AWS MCP servers (awslabs/mcp)](https://github.com/awslabs/mcp) — the upstream source of the AWS MCP servers used in the example. For usage and configuration, see [MCP](/mcp). --- ## Connect Atmos to MCP Servers — Use Cloud Tools Without Reimplementing Them Atmos can now connect to external MCP servers and use their tools directly in AI conversations. Configure any MCP server in `atmos.yaml`, and its tools appear alongside native Atmos tools in [`atmos ai chat`](/cli/commands/ai/chat), [`atmos ai ask`](/cli/commands/ai/ask), and [`atmos ai exec`](/cli/commands/ai/exec) — no custom integration code needed. ## Why This Matters The MCP ecosystem has hundreds of servers — [20+ from AWS](https://github.com/awslabs/mcp) for pricing, security, documentation, and API access; GCP and Azure servers for their respective clouds; plus community servers for databases, monitoring, CI/CD, and custom internal APIs. Instead of waiting for each cloud integration to be built into Atmos, you can now install any stdio-based MCP server and use it from the Atmos CLI. One `atmos.yaml` section, zero glue code. Any MCP server that accepts `command`, `args`, and `env` works — AWS, GCP, Azure, or your own custom server. ## Quick Start Add servers to your `atmos.yaml`: **File:** `atmos.yaml` ```yaml mcp: servers: # Cost Analysis & FinOps aws-billing: command: uvx args: ["awslabs.billing-cost-management-mcp-server@latest"] env: { AWS_REGION: "us-east-1" } description: "AWS Billing — billing summaries and payment history" identity: "readonly" # Atmos Auth identity (from the auth section) aws-pricing: command: uvx args: ["awslabs.aws-pricing-mcp-server@latest"] env: { AWS_REGION: "us-east-1" } description: "AWS Pricing — real-time pricing and cost analysis" identity: "readonly" # Atmos Auth identity (from the auth section) # Security & Compliance aws-security: command: uvx args: ["awslabs.well-architected-security-mcp-server@latest"] env: { AWS_REGION: "us-east-1" } description: "AWS Security — Well-Architected security posture assessment" identity: "readonly" # Atmos Auth identity (from the auth section) aws-iam: command: uvx args: ["awslabs.iam-mcp-server@latest"] env: { AWS_REGION: "us-east-1" } description: "AWS IAM — role/policy analysis and access patterns" identity: "readonly" # Atmos Auth identity (from the auth section) aws-cloudtrail: command: uvx args: ["awslabs.cloudtrail-mcp-server@latest"] env: { AWS_REGION: "us-east-1" } description: "AWS CloudTrail — event history and API call auditing" identity: "readonly" # Atmos Auth identity (from the auth section) # Documentation (no credentials needed) aws-docs: command: uvx args: ["awslabs.aws-documentation-mcp-server@latest"] description: "AWS Documentation — search and fetch AWS docs" ``` Then use them: ```bash # Check what you've configured atmos mcp list # Verify a server works atmos mcp test aws-docs # See what tools a server exposes atmos mcp tools aws-pricing # Cost analysis (uses aws-pricing) atmos ai ask "What's the on-demand price for m7i.xlarge in us-east-1?" # Spend breakdown (uses aws-billing) atmos ai ask "What did we spend on EC2 last month?" # Billing history (uses aws-billing) atmos ai ask "Show our billing summary for the past 3 months" # Security posture (uses aws-security) atmos ai ask "Is GuardDuty enabled in all regions?" # IAM analysis (uses aws-iam) atmos ai ask "List all IAM roles with admin access" # Audit trail (uses aws-cloudtrail) atmos ai ask "Show recent API calls from the root account" # Documentation (uses aws-docs, no credentials needed) atmos ai ask "How do I configure S3 bucket lifecycle rules?" ``` ## Smart Server Routing When multiple MCP servers are configured, Atmos automatically selects only the servers relevant to your question using a lightweight routing call to your configured AI provider. This keeps tool payloads small and responses fast, even with dozens of servers configured: ```text $ atmos ai ask "List all IAM roles with admin access" ℹ MCP routing selected 1 of 8 servers: aws-iam ℹ MCP server "aws-iam" started (29 tools) ℹ Registered 29 tools from 1 MCP server(s) ℹ AI tools initialized: 39 ``` Use `--mcp` to override and specify servers directly: ```bash # Specify one server atmos ai ask --mcp aws-iam "List all admin roles" # Comma-separated or repeated flags atmos ai ask --mcp aws-iam,aws-cloudtrail "Who accessed the admin role?" # Works with all AI commands atmos ai chat --mcp aws-billing atmos ai exec --mcp aws-security,aws-iam "audit our security posture" ``` ## Visibility Atmos shows which MCP servers are active and which tools the AI uses: ```text ℹ MCP routing selected 2 of 8 servers: aws-docs, aws-pricing ℹ MCP server "aws-docs" started (4 tools) ℹ MCP server "aws-pricing" started (7 tools) ℹ Registered 11 tools from 2 MCP server(s) ℹ AI tools initialized: 26 total ``` After the AI responds, tool executions are listed: ```text --- ## Tool Executions (2) 1. ✅ aws-docs → aws.search_documentation (234ms) 2. ✅ aws-pricing → get_pricing (456ms) ``` Tool usage is not inferred — the AI provider explicitly declares which tools it wants to call via the API protocol (`tool_use` stop reason with a `tool_calls` array). Atmos executes the requested tools, sends results back to the AI for the final answer, and records every call. If no tool executions appear, the AI genuinely chose not to use any tools for that question. ## CLI Commands ```bash atmos mcp list # List configured external servers atmos mcp tools # List tools from a server atmos mcp test # Test server connectivity atmos mcp status # Show all server statuses atmos mcp restart # Restart a server atmos mcp export # Generate .mcp.json for Claude Code / IDE ``` ## Standard Config Format The `command`, `args`, `env` fields follow the same format used by Claude Code, Codex CLI, and Gemini CLI. Atmos adds `description`, `identity`, and `timeout` as extensions: ```yaml mcp: servers: my-server: command: "uvx" # Standard args: ["package@latest"] # Standard env: # Standard AWS_REGION: "us-east-1" description: "What this server does" # Atmos extension identity: "my-identity" # Atmos Auth identity (from the auth section) timeout: "30s" # Connection timeout ``` ## Atmos Auth Integration Use [Atmos Auth](/cli/configuration/auth) to inject credentials automatically — no manual `AWS_PROFILE` setup: ```yaml mcp: servers: aws-security: command: uvx args: ["awslabs.well-architected-security-mcp-server@latest"] identity: "security-audit" # Atmos Auth identity (from the auth section) ``` Atmos authenticates through the identity chain, writes isolated credential files, and sets `AWS_SHARED_CREDENTIALS_FILE` + `AWS_PROFILE` on the subprocess. ## Toolchain Integration Map `uv` to the aqua registry and install via the [Atmos Toolchain](/cli/configuration/toolchain): ```yaml toolchain: aliases: uv: astral-sh/uv ``` ```bash atmos toolchain install astral-sh/uv@0.7.12 ``` ## IDE Integration Use the same servers from Claude Code, Cursor, or any MCP-compatible IDE: ```bash # Generate .mcp.json from your atmos.yaml config atmos mcp export ``` Servers with `identity` are automatically wrapped with [`atmos auth exec`](/cli/commands/auth/exec) for credential injection. The generated `.mcp.json` works with Claude Code out of the box. ## Atmos YAML Functions Atmos YAML functions work in env values: ```yaml mcp: servers: my-server: command: uvx args: ["my-server@latest"] env: AWS_REGION: !env AWS_DEFAULT_REGION # Read OS env var API_KEY: !exec "vault kv get -field=key secret/mcp" # Run command PROJECT_ROOT: !repo-root # Git root path ``` ## See It in Action > All outputs below are from real AWS accounts. Account IDs, resource identifiers, > and internal names have been redacted. Cost figures represent an example of real-world spending. List configured servers: ```text $ atmos mcp list NAME STATUS DESCRIPTION ───────────────────────────────────────────────────────────────────────────────────────── aws-api stopped AWS API — direct AWS CLI access with security controls aws-billing stopped AWS Billing — billing summaries and payment history aws-cloudtrail stopped AWS CloudTrail — event history and API call auditing aws-docs stopped AWS Documentation — search and fetch AWS docs aws-iam stopped AWS IAM — role/policy analysis and access patterns aws-knowledge stopped AWS Knowledge — managed AWS knowledge base (remote) aws-pricing stopped AWS Pricing — real-time pricing and cost analysis aws-security stopped AWS Security — Well-Architected security posture assessment ``` Explore tools from a security server: ```text $ atmos mcp tools aws-security TOOL DESCRIPTION ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── CheckSecurityServices Verify if selected AWS security services are enabled in the specified region and account. GetSecurityFindings Retrieve security findings from AWS security services. GetStoredSecurityContext Retrieve security services data that was stored in context from a previous CheckSecurityServices call. CheckStorageEncryption Check if AWS storage resources have encryption enabled. ListServicesInRegion List all AWS services being used in a specific region. CheckNetworkSecurity Check if AWS network resources are configured for secure data-in-transit. ``` Test server connectivity: ```text $ atmos mcp test aws-docs ✓ Server started successfully ✓ Initialization handshake complete ✓ 4 tools available ✓ Server responds to ping ``` Ask AI with MCP tools: ```text $ atmos ai ask "How do I configure S3 bucket lifecycle rules?" ℹ MCP routing selected 1 of 8 servers: aws-knowledge ℹ MCP server "aws-knowledge" started (6 tools) ℹ Registered 6 tools from 1 MCP server(s) ℹ AI tools initialized: 16 👽 Thinking... Configuring S3 Bucket Lifecycle Rules S3 lifecycle rules automate object management by transitioning objects between storage classes, archiving, or expiring them. Here's a comprehensive guide: -------- ## 📋 Lifecycle Rule Structure Each rule contains 3 key components: Component │ Description ───────────┼──────────────────────────────────────────────────────── Metadata │ Rule ID and Status (Enabled/Disabled) Filter │ Which objects the rule applies to (prefix, tags, size) Actions │ What to do (transition, expire, delete) │ A bucket can have up to 1,000 rules per lifecycle configuration. -------- ## 🔧 Available Actions Action │ Description ──────────────────────────────────┼──────────────────────────────────────────────────────── Transition │ Move objects to a different storage class after N days Expiration │ Permanently delete current object versions NoncurrentVersionTransition │ Transition old versions in versioned buckets NoncurrentVersionExpiration │ Delete noncurrent versions in versioned buckets AbortIncompleteMultipartUpload │ Cancel incomplete uploads after N days ExpiredObjectDeleteMarker │ Remove expired delete markers --- ## Tool Executions (1) 1. ✅ aws-knowledge → aws.search_documentation (2874ms) ``` Ask AI about billing with MCP tools: ```text $ atmos ai ask "Show our billing summary for the past 2 months" ℹ MCP routing selected 1 of 8 servers: aws-billing ℹ MCP server "aws-billing" started (25 tools) ℹ Registered 25 tools from 1 MCP server(s) ℹ AI tools initialized: 35 👽 Thinking... I'll retrieve your billing summary for the past 2 months right away! Here's your billing summary for the past 2 months (February–March 2026): -------- ## 📊 AWS Billing Summary — February & March 2026 ### 💰 Monthly Totals Service │ Feb 2026 │ Mar 2026 │ Change ──────────────────────────────────────────┼──────────┼───────────┼────────── Amazon Virtual Private Cloud │ $309.53 │ $261.17 │ ▼ $48.36 Amazon Elastic Container Registry Public │ $103.63 │ $103.66 │ ▲ $0.03 EC2 - Other │ $88.12 │ $123.39 │ ▲ $35.27 AWS Key Management Service │ $50.94 │ $47.98 │ ▼ $2.96 Amazon Redshift │ $50.87 │ $0.30 │ ▼ $50.57 Tax │ $47.80 │ $40.98 │ ▼ $6.82 Amazon Relational Database Service │ $43.99 │ $0.11 │ ▼ $43.88 AWS App Runner │ $28.42 │ $27.55 │ ▼ $0.87 AWS WAF │ $18.00 │ $18.00 │ — Amazon EC2 Compute │ $5.03 │ $15.30 │ ▲ $10.27 AmazonCloudWatch │ $10.08 │ $10.45 │ ▲ $0.37 Amazon Simple Storage Service │ $10.08 │ $9.35 │ ▼ $0.73 Amazon Route 53 │ $5.61 │ $5.68 │ ▲ $0.07 Amazon Elastic File System │ $3.65 │ — │ ▼ $3.65 Amazon EC2 Container Registry (ECR) │ $2.69 │ $2.75 │ ▲ $0.06 Amazon EKS │ — │ $1.86 │ ▲ $1.86 Amazon Registrar │ — │ $14.00 │ ▲ $14.00 AWS Cost Explorer │ $2.07 │ $1.91 │ ▼ $0.16 Other services │ ~$1.65 │ ~$1.41 │ ▼ $0.24 -------- ### 📈 Grand Totals Month │ Total Cost ────────────┼────────────────── Feb 2026 │ ~$782.16 Mar 2026 │ ~$745.26 Difference │ ▼ $36.90 (-4.7%) -------- ### 🔍 Key Observations 1. Amazon VPC is the largest cost at ~$261–$310/month (NAT Gateway data transfer is typically the main driver). Costs dropped notably in March. 2. Redshift & RDS dropped sharply in March ($50.87 → $0.30 and $43.99 → $0.11), suggesting those resources were stopped or deleted. 3. EC2 - Other increased by $35 in March — worth investigating if this correlates with EBS, data transfer, or Elastic IP costs. 4. Amazon EC2 Compute tripled in March ($5.03 → $15.30), possibly linked to the appearance of EKS ($1.86), indicating new compute workloads. 5. Amazon Registrar appeared as a new $14 charge in March (domain renewal). │ ⚠️ Note: Credits and refunds are excluded from these figures. -------- ## Tool Executions (1) 1. ✅ aws-billing → cost-explorer (381ms) ``` Check security posture with MCP tools: ```text $ atmos ai ask "Is GuardDuty enabled in all regions?" ℹ MCP routing selected 2 of 8 servers: aws-api, aws-security ℹ MCP server "aws-api" started (2 tools) ℹ MCP server "aws-security" started (6 tools) ℹ Registered 8 tools from 2 MCP server(s) ℹ AI tools initialized: 18 👽 Thinking... ## ❌ GuardDuty is NOT Enabled in All Regions GuardDuty is only enabled in 1 out of 34 regions checked. ### ✅ Enabled (1 region) Region │ Detector ID ───────────┼──────────────────────────────────── us-east-2 │ ### ❌ Not Enabled (33 regions) Region │ Region │ Region ────────────────┼────────────────┼──────────────── ap-south-1 │ ap-south-2 │ ap-southeast-1 ap-southeast-2 │ ap-southeast-3 │ ap-southeast-4 ap-northeast-1 │ ap-northeast-2 │ ap-northeast-3 ap-east-1 │ ca-central-1 │ ca-west-1 eu-central-1 │ eu-central-2 │ eu-north-1 eu-south-1 │ eu-south-2 │ eu-west-1 eu-west-2 │ eu-west-3 │ me-central-1 me-south-1 │ il-central-1 │ af-south-1 sa-east-1 │ us-east-1 │ us-west-1 us-west-2 │ mx-central-1 │ ... -------- ### 🔒 Recommendations 1. Enable GuardDuty in all active regions — even regions where you don't actively deploy can be targeted by attackers. 2. Use a GuardDuty delegated administrator (via AWS Organizations) to centrally enable and manage across all accounts and regions. 3. Consider AWS Security Hub to aggregate findings from all regions. 4. Prioritize us-east-1, us-west-2, eu-west-1 immediately. -------- ## Tool Executions (4) 1. ✅ aws-api → call_aws (400ms) 2. ✅ aws-api → call_aws (14ms) 3. ✅ aws-api → call_aws (7ms) 4. ✅ aws-api → call_aws (9450ms) ``` Audit IAM roles with smart routing: ```text $ atmos ai ask "List all IAM roles with admin access" ℹ MCP routing selected 1 of 8 servers: aws-iam ℹ MCP server "aws-iam" started (29 tools) ℹ Registered 29 tools from 1 MCP server(s) ℹ AI tools initialized: 39 👽 Thinking... I'll help you find all IAM roles with admin access. Let me fetch the list of IAM roles and policies simultaneously. -------- ## 🔐 IAM Roles with Admin Access ### 1. ✅ Direct AdministratorAccess Policy (4 attachments) Role Name │ Description │ Trust Principal ──────────────────────────────────────────────────┼────────────────────────────────────────────────┼─────────────────────────── AWSReservedSSO_AdministratorAccess_... │ Allow Full Administrator access to the account │ AWS SSO (SAML Federation) AWSReservedSSO_RootAccess_... │ Centralized root access to member accounts │ AWS SSO (SAML Federation) AWSReservedSSO_TerraformApplyAccess_... │ Full Terraform state and account access │ AWS SSO (SAML Federation) AWSReservedSSO_TerraformApplyAccess-Core_... │ Full Terraform access (core backend) │ AWS SSO (SAML Federation) -------- ## 📋 Summary Category │ Count ───────────────────────────────────────────┼────────── Full Admin (AdministratorAccess policy) │ 4 roles Broad Terraform/State access (elevated) │ 4 roles AWS Service-Linked Roles (scoped) │ 13 roles -------- ### 🛡️ Security Recommendations 1. Review SSO assignments for AdministratorAccess and RootAccess roles. 2. Audit TerraformApplyAccess roles — ensure MFA/session policies are enforced. 3. Monitor tfstate roles — cross-account trust across 14 accounts. 4. Enable CloudTrail for AssumeRole calls on high-privilege roles. -------- ## Tool Executions (2) 1. ✅ aws-iam → list_roles (314ms) 2. ✅ aws-iam → list_policies (174ms) ``` ## Try It **Explore the MCP Example** Try a complete example with pre-configured AWS MCP servers — documentation, knowledge base, pricing, API access, and security assessment. Browse Example[Read more](/examples/mcp) ## Learn More - [MCP Configuration](/cli/configuration/mcp) — Full configuration reference - [MCP Commands](/cli/commands/mcp/list) — CLI command reference - [AWS MCP Servers](https://github.com/awslabs/mcp) — All available AWS servers - [Atmos AI](/ai) — AI features overview For usage and configuration, see [MCP](/mcp). --- ## Manage MCP Servers Without Hand-Editing YAML Getting Atmos connected to an MCP server, or letting your AI assistant call Atmos's own tools, used to mean opening `atmos.yaml` and hand-writing a `mcp.servers` entry — then, separately, remembering which `atmos mcp` command actually pushes it into Claude Code, Cursor, or VS Code. If all you wanted was "let my AI assistant use Atmos," there was no single command that got you there. ## The Fix [`atmos mcp add`](/cli/commands/mcp/add) and [`atmos mcp remove`](/cli/commands/mcp/remove) manage `mcp.servers` in `atmos.yaml` directly — no YAML editing required. Two built-in presets skip the boilerplate entirely: `self` wires up Atmos's own MCP server, and `atmos-pro` wires up the Atmos Pro MCP server. Run `atmos mcp add` with no arguments at all and it defaults to `atmos mcp add self`, so the entire "let my AI assistant use Atmos" flow is one command. On the other side, [`atmos mcp uninstall`](/cli/commands/mcp/uninstall) is the mirror image of the existing [`atmos mcp install`](/cli/commands/mcp/install) — it removes servers from an AI client's config without touching `atmos.yaml`. `add`/`remove` manage the declarative source of truth; `install`/`uninstall` push and pull it to your client. They compose, but never step on each other. [`atmos mcp list`](/cli/commands/mcp/list) and [`atmos mcp status`](/cli/commands/mcp/status) also now mention these commands directly instead of just saying "no servers configured," and nudge you to run `atmos mcp add atmos-pro` if Atmos Pro is already configured but its MCP server hasn't been added yet. ## How to Use It ```bash # Let your AI assistant use Atmos's own tools -- writes mcp.servers.atmos, # prompts to enable mcp.enabled if it's off, and installs into detected clients. atmos mcp add self --install # Add the Atmos Pro MCP server. atmos mcp add atmos-pro # Add an external server -- name and transport are inferred. atmos mcp add "uvx awslabs.aws-documentation-mcp-server@latest" atmos mcp add https://mcp.example.com/mcp --header "Authorization: Bearer ${TOKEN}" # Remove a server, and uninstall it from wherever it was pushed. atmos mcp remove aws-docs atmos mcp uninstall aws-docs ``` See the [MCP documentation](/mcp) for the full command reference and built-in preset details. ## Get Involved Questions or feedback? Open an issue on [GitHub](https://github.com/cloudposse/atmos) or join the conversation in our community Slack. --- ## MCP Servers No Longer Need a Second Flag to Do Anything You set `mcp.enabled: true`, ran [`atmos mcp start`](/cli/commands/mcp/start), and got: `failed to initialize AI components: tools are disabled`. MCP was on. Why did anything else need to be "enabled" for a command whose entire job is exposing tools? ## The Problem `ai.tools.enabled` was designed for [`atmos ai chat`](/cli/commands/ai/chat)/`ask`/`exec` — Atmos's own assistant deciding whether it's allowed to call tools during a conversation. `atmos mcp start` reused that same flag as a hard gate, even though the MCP server has nothing to do with Atmos's own chat loop: its only job is exposing Atmos tools to _external_ clients like Claude Desktop or Cursor. `mcp.enabled: true` was already the explicit, purpose-built opt-in for that — requiring a second, differently-named flag on top of it just to get a non-empty server was pure friction, not safety. ## The Fix `atmos mcp start` no longer checks `ai.tools.enabled` at all. Once `mcp.enabled: true`, tools register unconditionally — `ai.tools.enabled` now only governs `atmos ai chat`/`ask`/`exec`'s own tool-use loop, which is what it was always meant for. While in there, we also renamed three related settings for consistency. They're already nested under `tools:`, so the `_tools` suffix was redundant: | Old | New | |---|---| | `tools.allowed_tools` | `tools.allowed` | | `tools.restricted_tools` | `tools.restricted` | | `tools.blocked_tools` | `tools.blocked` | `tools.allowed` also picked up a second job. Previously it only skipped the confirmation prompt — every tool was still registered and callable, just some needed a "yes" first. Now, when non-empty, it also controls which tools _exist_ at all: unlisted tools aren't registered, aren't visible in `tools/list`, and aren't offered to the AI. An empty or unset list still means "everything is registered, subject to normal confirmation rules" — unchanged. ## How to Use It **File:** `atmos.yaml` ```yaml mcp: enabled: true ai: enabled: true tools: # Optional -- omit entirely for a fully-populated MCP server. # When set, ONLY these are registered, and they skip confirmation: allowed: - atmos_describe_* - atmos_list_* blocked: - execute_bash_command ``` ```bash atmos mcp start ``` No `ai.tools.enabled` required. ## Breaking Change `tools.allowed_tools`, `tools.restricted_tools`, and `tools.blocked_tools` are no longer read — there's no fallback or deprecation warning, just the new names. If your `atmos.yaml` sets any of these under `ai.tools`, rename them before upgrading. If you were relying on `allowed_tools` to skip confirmation for a subset of tools _while still allowing everything else to run with a prompt_, note the semantics changed: an unlisted tool is no longer registered at all. Move it to an empty `allowed` (or drop the list) and lean on `restricted`/`blocked` instead. See [AI Tools Configuration](/cli/configuration/ai/tools) for the full reference. For usage and configuration, see [MCP](/mcp). --- ## Metadata Inheritance Metadata now inherits from base components, just like `vars` and `settings`. ## The Problem When managing infrastructure at scale, you want to enforce consistent governance policies across your components. Pin all instances to the same component version. Lock critical infrastructure to prevent accidental changes. Use standardized workspace naming patterns. Document component ownership. But before this release, `metadata` didn't inherit from base components. Even though you could inherit `vars` and `settings`, you had to copy-paste metadata configuration to every derived component: ```yaml # You defined governance once... vpc/defaults: metadata: type: abstract component: vpc/v2 # Pin to version locked: true terraform_workspace_pattern: "{tenant}-{environment}-{stage}" custom: description: "Virtual Private Cloud network configuration" owner: "platform-team" # ...but had to repeat EVERYTHING for each instance vpc/primary: metadata: inherits: [vpc/defaults] component: vpc/v2 # Repeated locked: true # Repeated terraform_workspace_pattern: "{tenant}-{environment}-{stage}" # Repeated custom: # Repeated description: "Virtual Private Cloud network configuration" # Repeated owner: "platform-team" # Repeated vpc/secondary: metadata: inherits: [vpc/defaults] component: vpc/v2 # Repeated again locked: true # Repeated again terraform_workspace_pattern: "{tenant}-{environment}-{stage}" # Repeated again custom: # Repeated again description: "Virtual Private Cloud network configuration" # Repeated again owner: "platform-team" # Repeated again ``` The biggest pain? When upgrading to `vpc/v3`, you had to update the component version in every single instance. Miss one, and you'd have components running different versions with no indication why. ## The Solution Now it just works: ```yaml vpc/defaults: metadata: type: abstract component: vpc/v2 # Define version once locked: true terraform_workspace_pattern: "{tenant}-{environment}-{stage}" custom: description: "Virtual Private Cloud network configuration" owner: "platform-team" vpc/primary: metadata: inherits: [vpc/defaults] # component, locked, terraform_workspace_pattern, and custom are all inherited vpc/secondary: metadata: inherits: [vpc/defaults] # All governance settings inherited - no repetition needed ``` Want to upgrade to `vpc/v3`? Change it once in `vpc/defaults`, and all instances inherit the new version automatically. Two fields are excluded from inheritance: - [`metadata.inherits`](/stacks/components/component-metadata#inherits) - defines the inheritance relationship itself - `metadata.type` - component type is per-component (e.g., `abstract` shouldn't propagate to derived components) ## Configuration Metadata inheritance is now enabled by default. If this causes issues with existing configurations, you can disable it: ```yaml # atmos.yaml stacks: inherit: metadata: false ``` See the [inheritance documentation](/howto/inheritance) for details. --- ## Stable Workspace Keys with metadata.name New `metadata.name` field provides stable Terraform state paths when using versioned component folders. ## The Problem Atmos uses [Terraform workspaces](https://developer.hashicorp.com/terraform/language/state/workspaces) to isolate state for the same component deployed across different stacks. For remote backends like S3, state files are organized in a two-level hierarchy: ``` s3://bucket/ └── {workspace_key_prefix}/ # Component identity (e.g., "vpc") └── {workspace}/ # Stack context (e.g., "ue2-dev") └── terraform.tfstate ``` The `workspace_key_prefix` groups all deployments of a component together—your VPC in dev, staging, and prod all share the same prefix. The workspace name (derived from the stack) keeps each environment's state separate. **The problem:** Atmos auto-generated `workspace_key_prefix` from [`metadata.component`](/stacks/components/component-metadata#component), which includes the folder path. When you organize components in versioned folders (`vpc/v1`, `vpc/v2`), upgrading to `vpc/v3` changed the workspace key prefix from `vpc-v2` to `vpc-v3`. Terraform couldn't find your existing state—your infrastructure looked like it was never created. You could always work around this by explicitly setting `backend.s3.workspace_key_prefix` in a base component (backend config is inherited). But this approach had drawbacks: - Backend-specific: you'd set `workspace_key_prefix` for S3, `prefix` for GCS, or `key` for Azure. - Intent unclear: nothing indicated this was the component's logical name. - Easy to forget when creating new components. ## The Solution `metadata.name` provides a semantic way to declare a component's logical name. Atmos uses it to auto-generate the appropriate backend setting (`workspace_key_prefix`, `prefix`, or `key`) for any backend type: Set it once in your base component: ```yaml # stacks/catalog/vpc.yaml vpc/defaults: metadata: type: abstract name: vpc # Logical name (excludes version) component: vpc/v2 # Physical path (includes version) # stacks/prod.yaml vpc-prod: metadata: inherits: [vpc/defaults] # State path stays at "vpc/" regardless of version ``` Upgrade by changing one line in the catalog: ```yaml vpc/defaults: metadata: name: vpc # Unchanged - state path stable component: vpc/v3 # New version ``` All environments inherit the change. No state migration needed. ## Convention By convention, `metadata.name` should be the **component name without version or release channel prefixes**: | Component Path | `metadata.name` | Version/Channel | |----------------|-----------------|-----------------| | `vpc/v2/` | `vpc` | `v2` (suffix) | | `v2/vpc/` | `vpc` | `v2` (prefix) | | `2024/vpc/` | `vpc` | `2024` (prefix) | | `stable/vpc/` | `vpc` | `stable` (prefix) | The logical name is the component itself—strip the version or release channel whether it appears before or after. See [folder-based versioning](/design-patterns/version-management/folder-based-versioning) for the complete pattern. For usage and configuration, see [Configure Component Metadata](/stacks/components/component-metadata#name). --- ## New Migration Guides: Your Path to Atmos We've added comprehensive migration guides to help teams adopt Atmos regardless of their starting point. ## Meeting You Where You Are Every team has a different starting point. Some are using vanilla Terraform with Makefiles. Others have invested heavily in Terragrunt. Many are using Terraform workspaces to manage environments. We've created dedicated guides for each scenario. ## Three Migration Paths ### From Native Terraform If you're already using Terraform with shell scripts, Makefiles, or just raw commands, you're 90% there. Your Terraform code doesn't need to change—Atmos gives you a documented, conventional way to manage your infrastructure. The guide follows a "Crawl, Walk, Run" approach: - **Crawl**: Get running in 20 minutes - **Walk**: Explore DRY configs and remote state - **Run**: Advanced features when you need them [Read the Native Terraform Migration Guide →](/migration/native-terraform) ### From Terragrunt Terragrunt and Atmos solve similar problems—managing Terraform at scale with DRY configurations. The guide covers key differences in configuration format, reuse mechanisms, dependencies, and more. It also highlights capabilities unique to Atmos: native authentication, vendoring, custom commands, workflows, Terraform shell, affected detection, component validation, and configuration provenance. [Read the Terragrunt Migration Guide →](/migration/terragrunt) ### From Terraform Workspaces Workspaces seem great at first, but they have fundamental limitations: shared state backends become single points of failure, configuration differentiation requires ugly conditionals, and there's no audit trail between environments. The guide explains how Atmos provides explicit state isolation, YAML-based configuration, and clear separation of concerns. [Read the Terraform Workspaces Migration Guide →](/migration/terraform-workspaces) ## Why We Created These Guides Tool fatigue is real. Instead of duct-taping 25 different tools together, Atmos gives you one documented approach. These migration guides are designed to get you from "I've never used Atmos" to "I'm productive with Atmos" as quickly as possible. We believe you should get value in 20 minutes, not 20 hours. --- ## Multi-Cloud Documentation and Design Patterns Atmos has always been cloud agnostic, but our documentation hasn't always reflected that. This release adds comprehensive multi-cloud documentation including a new design pattern for organizing stacks across different cloud providers. ## What Changed ### Multi-Cloud Overview Page A new [Multi-Cloud](/multi-cloud) page explains how Atmos works with any cloud provider that Terraform supports. It includes a cloud concept mapping table that translates equivalent concepts across AWS, Azure, and GCP — from organizational hierarchy (Organizations vs Management Groups vs Folders) to networking (VPC vs VNet vs VPC Network) to identity (IAM Roles vs Managed Identities vs Service Accounts). The page also includes tabbed configuration examples showing how to set up [authentication](/cli/configuration/auth) and [stores](/cli/configuration/stores) for each provider. ### Multi-Cloud Configuration Design Pattern A new [Multi-Cloud Configuration](/design-patterns/stack-organization/multi-cloud-configuration) design pattern shows how to organize your stacks so the directory layout mirrors how your cloud provider organizes its resources. The guiding principle: what you see on disk should match how resources are deployed. Each cloud gets its own example with native terminology in folder names: - **AWS**: `accounts/dev/us-east-1.yaml` - **Azure**: `subscriptions/dev/eastus.yaml` - **GCP**: `projects/dev/us-central1.yaml` ### Application SDLC Environments Design Pattern Not every repository needs deep organizational hierarchy. A new [Application SDLC Environments](/design-patterns/stack-organization/application-sdlc) design pattern shows how application repositories can co-locate infrastructure alongside application code with a minimal flat structure: `dev.yaml`, `staging.yaml`, `prod.yaml`, and optionally `preview.yaml` for ephemeral PR environments. This pattern makes it easy for developers to get up and running with Atmos in their application repositories without being overwhelmed by organizational taxonomy. The directory structure exposes only what the team needs to care about — the SDLC environment — while keeping everything else as inherited defaults. The result is a clean architecture where infrastructure ships with the application in the same repo and the same pull request. For a working example of this pattern, see the [app-on-ecs](https://github.com/cloudposse-examples/app-on-ecs) reference architecture on GitHub. ### Azure and GCP Auth Documentation The [auth usage page](/cli/commands/auth/usage), [providers reference](/cli/configuration/auth/providers), and [identities reference](/cli/configuration/auth/identities) now include Azure and GCP alongside AWS, with tabbed examples for each cloud. ## Why This Matters Teams deploying to Azure or GCP can now find documentation that speaks their cloud's language instead of translating from AWS-centric examples. The concept mapping table helps engineers working across multiple clouds quickly find equivalent services. For application developers, the SDLC pattern provides a low-friction on-ramp to Atmos — co-locate your Terraform next to your app code, define one file per environment, and let Atmos handle the rest. --- ## Native CI Integration: Rich Plan Summaries Without Extra Actions When you see complex bash scripts and conditional logic in GitHub Actions workflows, that's a signal: the underlying tool wasn't designed for CI. Atmos now has built-in CI integration that makes the same command work identically locally and in CI—no wrapper scripts, no extra actions, no hidden complexity. ## The Problem with CI Glue Code Look at any mature infrastructure repository's CI workflows. You'll find bash scripts parsing terraform output with `grep` and `awk`, conditional logic to handle plan files across jobs, and environment variable gymnastics to pass data between steps. This complexity isn't accidental—it's compensation. When tools aren't designed for CI, teams build layers of glue code to bridge the gap. The cost is real: workflows that work in CI fail locally (and vice versa), debugging requires reproducing the entire CI environment, and tribal knowledge accumulates in workflow files. ## The Reproducibility Principle Infrastructure tools should follow a simple principle: **the same command should produce the same behavior everywhere.** ```bash # This should work identically: atmos terraform plan vpc -s prod # locally atmos terraform plan vpc -s prod # in GitHub Actions atmos terraform plan vpc -s prod # in GitLab CI ``` When a tool is truly CI-native, your workflow files become trivial: ```yaml # Before: Complex workflow with hidden logic - name: Plan run: | output=$(atmos terraform plan vpc -s prod 2>&1) echo "$output" # Parse for changes... # Upload artifacts... # Post PR comment... # After: CI-native tool - name: Plan run: atmos terraform plan vpc -s prod ``` ## What This Enables Previously, getting beautiful plan summaries in GitHub Actions required using separate actions like `github-action-atmos-terraform-plan`. These wrapped the CLI with CI-specific behaviors, creating two codebases that evolved separately. Now, Atmos handles everything natively. The CLI detects when it's running in CI and automatically generates the same rich output you're used to—resource badges, collapsible diffs, terraform outputs. ## What You Get - **Rich job summaries** — resource badges, collapsible diffs, plan/apply templates (written to `$GITHUB_STEP_SUMMARY`) - **Live status checks** — real-time progress ("Plan in progress" → "3 to add, 1 to change, 0 to destroy") - **Output variables** — plan/apply results exported to `$GITHUB_OUTPUT` for downstream jobs - **Planfile storage** — store planfiles in S3, GitHub Artifacts, or local filesystem with SHA256 integrity verification - **Custom templates** — Go template syntax for full control over summaries and comments - **Auto-detection** — CI mode enabled automatically from `CI=true` or `GITHUB_ACTIONS=true` - **Same command** works locally and in CI - **PR comments** _(coming soon)_ — auto-updated plan summaries on pull requests ## Quick Start Here's a minimal workflow using [profiles](/cli/configuration/profiles) and [auth](/stacks/auth) with OIDC: ### GitHub Actions Workflow ```yaml name: Terraform Plan on: pull_request: branches: [main] jobs: plan: runs-on: ubuntu-latest permissions: id-token: write contents: read env: ATMOS_PROFILE: ci ATMOS_IDENTITY: plat-dev/admin steps: - uses: actions/checkout@v6 - uses: cloudposse/github-action-setup-atmos@v2 - name: Terraform Plan run: atmos terraform plan mycomponent -s dev-us-east-1 ``` ### profiles/ci/auth.yaml ```yaml auth: providers: github-oidc: kind: github/oidc region: us-east-2 spec: audience: sts.amazonaws.com identities: plat-dev/admin: kind: aws/assume-role via: provider: github-oidc principal: assume_role: arn:aws:iam::111111111111:role/acme-plat-gbl-dev-terraform ``` ### atmos.yaml ```yaml ci: # Auto-enabled when CI detected, or set explicitly enabled: true # Output variables for downstream jobs output: enabled: true variables: - has_changes - has_additions - has_destructions - artifact_key - plan_summary # Job summary with plan/apply results summary: enabled: true # Commit status checks checks: enabled: true context_prefix: "atmos" # Template overrides templates: base_path: ".atmos/ci/templates" terraform: plan: "plan.md" apply: "apply.md" # To store planfiles across CI pipeline stages, add: components: terraform: planfiles: # Stores are tried in priority order priority: - "github" # Named stores stores: github: type: github/artifacts options: retention_days: 7 ``` That's it. Atmos detects GitHub Actions automatically and writes the plan summary to `$GITHUB_STEP_SUMMARY`. ## Example Output ### Changes Found for `vpc` in `dev-us-east-1` [![create](https://shields.io/badge/PLAN-CREATE-success?style=for-the-badge)](#) [![destroy](https://shields.io/badge/PLAN-DESTROY-critical?style=for-the-badge)](#) :::danger\[Caution] **Terraform will delete resources!** This plan contains resource delete operations. Please check the plan result very carefully. ::: Resources: 3 to add, 0 to change, 1 to destroy. To reproduce this locally, run: ```shell atmos terraform plan vpc -s dev-us-east-1 ``` --- #### Create ```diff + aws_vpc.main + aws_subnet.public[0] + aws_subnet.public[1] ``` #### Destroy ```diff - aws_security_group.deprecated ``` Terraform Plan Summary ```hcl # random_id.id2 will be created + resource "random_id" "id2" { + b64_std = (known after apply) + b64_url = (known after apply) + byte_length = 8 + dec = (known after apply) + hex = (known after apply) + id = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: cluster_id = "cluster-754a1c6160064d0b" lb_id = "load-balancer-763d296bbfc8ccc6" vpc_id = "vpc-69e5cf6a55e1eb81" ``` Terraform Outputs | Output | Value | |--------|-------| | `cluster_id` | `cluster-754a1c6160064d0b` | | `lb_id` | `load-balancer-763d296bbfc8ccc6` | | `vpc_id` | `vpc-69e5cf6a55e1eb81` | ## Learn More - [CI Configuration](/cli/configuration/ci) — complete configuration options and permissions reference - [Native CI Overview](/ci) — feature overview and architecture - [Planfile Storage](/ci/planfile-storage) — store and verify planfiles across CI pipeline stages --- ## Plan-then-Deploy in CI: Planfile Storage and Automatic Drift Verification Atmos native CI now supports the full plan-then-deploy workflow with planfiles: [`atmos terraform plan --ci`](/cli/commands/terraform/plan) uploads the planfile to durable storage, and `atmos terraform deploy --ci` automatically downloads it, generates a **fresh** plan, reconciles it against the reviewed plan, and applies the fresh plan — only if they match, failing on drift by default. ## What Changed Two pieces landed together: - **Planfile storage that works in GitHub Actions.** The `github/artifacts` store talks to the GitHub Actions Artifacts API directly — including the runtime download path, so a planfile uploaded by a **plan** job can be consumed by a separate **deploy** job in the same run. (S3 and local stores work too.) - **Automatic, configurable drift verification on `deploy`.** When planfile storage is configured and you run `atmos terraform deploy --ci`, Atmos downloads the stored plan, generates a fresh plan against current state, compares them with a JSON-structural plan-diff, and: - **`fail`** (default under CI): blocks the deploy on drift, - **`warn`**: logs the drift but proceeds, - **`off`**: skips verification entirely. - **A defined answer for "no stored plan found."** `verify` covers the case where a stored plan _exists_ but differs. A companion `required` boolean covers whether a stored plan **must** exist to verify against — previously a silent fresh apply. It defaults to tracking `verify` strictness, so a fail-by-default CI deploy now fails loudly instead of quietly applying something unverified. That means a green `deploy` actually _proves_ verification ran — no log-scraping required. Configure it once: ```yaml components: terraform: planfiles: verify: fail # drift: stored plan exists but differs (fail | warn | off) required: true # must a reviewed stored plan exist? (defaults to tracking `verify`) priority: [github] stores: github: type: github/artifacts ``` Override per run with `--verify-plan` / `--verify-plan=false` (CLI beats config beats the CI default). And because CI is auto-detected (`CI` / `GITHUB_ACTIONS`), the `--ci` flag is optional in a real pipeline — `atmos terraform deploy mycomponent -s prod` behaves natively and exits non-zero on drift or a missing plan, so your workflow asserts via exit codes instead of grepping logs. ## Why This Matters The gap between "the plan you reviewed" and "what actually gets applied" is where infrastructure surprises live. ### Reconcile, don't replay By default `deploy` doesn't replay the stored planfile. It re-plans, checks the fresh plan against the one you reviewed, and applies the fresh plan only if they match. Why not just apply the stored plan directly? A saved plan is brittle. It goes stale the moment the state changes — which, between a PR and its merge, it usually has. And while Terraform bakes the assumed role into the plan, the **base credentials** that authenticate (and assume that role) come from the apply environment, not the plan — so a plan built on the PR can fail to apply on merge. Re-planning at apply time uses the current state and the apply-time credentials; the diff still proves nothing changed since review. Want byte-for-byte replay anyway? Use `deploy --from-plan` (or `apply --planfile`). ### Why a naive diff doesn't work A planfile is a frozen snapshot — but the plan it represents never really is. Between review and apply the details shift: values "known after apply," computed fields, hashes, ordering, timestamps. You adjust course as you go without changing _what_ you set out to do. A byte-for-byte comparison can't tell the difference — it flags every shift as drift, so a plan that's still doing exactly what you reviewed gets rejected. Terraform's own saved-plan apply is stricter still: any movement in state lineage invalidates the snapshot outright, even when the substance hasn't changed. That's the real-world problem: rigid checks make a plan go _invalid_ long before it goes _wrong_. Useful verification needs **wiggle room** — tolerate the benign shifts, catch the real ones. Atmos's verification is **semantic**, not naive. It parses both plans to JSON, normalizes the noise (sorted keys, masked secrets, computed-hash attributes, data-source reads), and compares what matters. So it flags a resource added, removed, or substantively changed — while letting the incidental variation slide. That's what makes plan-then-deploy practical. Verification lives on `deploy`, not `apply`, by construction: `deploy` runs a discrete `plan` step as part of the command, capturing a fresh plan to diff the stored one against. `apply` doesn't capture that separate planfile — it applies a plan you pass, or plans-and-applies in one step like `terraform apply` — so it stays a thin, predictable wrapper. ## How to Use It Inside GitHub Actions, the `github/artifacts` store needs the runner's runtime credentials (`ACTIONS_RUNTIME_TOKEN` / `ACTIONS_RESULTS_URL`), which GitHub withholds from `run:` steps. Surface them once with the in-repo [`github-runtime`](https://github.com/cloudposse/atmos/tree/main/actions/github-runtime) action: ```yaml steps: - uses: cloudposse/atmos/actions/github-runtime@v1 with: mode: env - run: atmos terraform plan mycomponent -s prod --ci # uploads the planfile - run: atmos terraform deploy mycomponent -s prod --ci # downloads, verifies, applies ``` See [Planfile Storage](/ci/planfile-storage), [Planfile drift verification](/components/terraform/planfiles#drift-verification), and [`atmos terraform deploy`](/cli/commands/terraform/deploy#automatic-plan-verification-in-ci). ## Get Involved Planfile storage is evolving — try it in your pipelines and tell us how the `fail` / `warn` / `off` semantics fit your workflow. Open an issue or discussion on [GitHub](https://github.com/cloudposse/atmos). --- ## Native Container Image Summaries in CI Atmos container builds and pushes now write rich image summaries directly to the CI job summary when native CI is enabled. Use `type: container` workflow steps or [`atmos container build/push`](/cli/commands/container/usage) component commands and GitHub Actions gets a readable image report without adding a separate Docker summary action. The summary includes the image reference, digest, image ID, OCI metadata, runtime configuration, environment variables, labels, layer digests, and the formatted raw inspect JSON. Push summaries use the pushed digest when the registry returns one, so reviewers can see the canonical artifact that was published. ```yaml ci: enabled: true summary: enabled: true ``` Summaries are best-effort. A successful build or push is not failed just because image inspection or job-summary writing is unavailable. When `ci.summary.enabled: false` is set, Atmos skips the image summary entirely. This keeps native Atmos container pipelines self-contained: build, push, and review the image metadata from the same workflow or component command you already run locally. For usage and configuration, see [CI Job Summaries](/cli/configuration/ci/summary). --- ## Native Container Steps in Atmos Workflows Atmos workflows and custom commands can now run containers natively. A new `type: container` step builds images, pushes them to registries, and runs containerized tools — Docker or Podman — through the same reusable step library that powers the rest of your automation. This is not about Dev Containers. Dev Containers put a developer in a reproducible workspace. Container steps are procedural actions inside a workflow: build an image, push it, run a one-shot command in a container, and pass the result to the next step. ## Container Actions `type: container` is a small action family — `build`, `push`, and `run` — that reuses the existing Atmos container runtime (Docker/Podman detection, mounts, env, ports, PTY handling). ```yaml steps: - name: build type: container action: build with: context: app tags: - "localhost:5001/app:{{ .git.sha }}" - name: push type: container action: push with: image: "localhost:5001/app:{{ .git.sha }}" - name: smoke type: container action: run with: image: "localhost:5001/app:{{ .git.sha }}" command: uname -a ``` Every action takes its parameters under a single `with:` block: ```yaml - name: hello type: container action: run with: image: alpine:latest command: echo hello ``` Docker Buildx and Buildx Bake are supported for builds; Podman uses the native `podman build` path. Podman machine start is opt-in via `runtime_auto_start: true`, and each step can carry its own `identity` for registry authentication. ## Step Outputs Steps now expose a structured result so a build step can hand an image reference to later steps without shell parsing or temporary files: ```yaml - name: build type: container action: build with: context: . tags: ["123456789012.dkr.ecr.us-east-2.amazonaws.com/app:{{ .env.GIT_SHA }}"] outputs: image: "{{ .metadata.image }}" - name: push type: container action: push with: image: "{{ .steps.build.outputs.image }}" ``` Every named step exposes `value`, `values`, `metadata`, `outputs`, `skipped`, and `error`; command-like steps add `stdout`, `stderr`, and `exit_code`. Existing `{{ .steps..value }}` references keep working. ## Pushing to ECR with an Identity Container steps don't reinvent registry login — they reuse Atmos [auth integrations](/cli/configuration/auth). Put `identity:` on the step (or pass `--identity`) and authenticating that identity auto-provisions its linked integrations. An `aws/ecr` integration performs the Docker login, and the step's `push` uses it — no `aws ecr get-login-password | docker login` dance: ```yaml # atmos.yaml auth: identities: dev-admin: kind: aws/permission-set via: { provider: company-sso } principal: { name: AdministratorAccess, account: dev } integrations: dev/ecr/primary: kind: aws/ecr via: { identity: dev-admin } spec: registry: { account_id: "123456789012", region: us-east-2 } ``` ```yaml # workflow step - name: push type: container action: push identity: dev-admin with: image: "{{ .steps.build.outputs.image }}" ``` The identity-resolved environment — including the `DOCKER_CONFIG` the integration exports and any `AWS_*` credentials — is forwarded to the container runtime subprocess, so private base-image pulls during `build` and pushes to private registries work even with an isolated Docker config directory. ## Try It The new `examples/container-step` example demonstrates build, push, run, Bake, and step outputs: ```shell cd examples/container-step atmos workflow build -f container-step ``` [View the full example](/examples/container-step) ## What Comes Next Container steps are the procedural, ephemeral building block. The next pieces extend the same idea into stack-scoped, declarative infrastructure, and are specified as PRDs today: - **Container components** (`components.container`) — stack-scoped image artifacts and long-running containers with component-instance identity, secrets, and lifecycle commands. - **Compose components** (`components.compose`) — wrap an existing native `compose.yaml` project as an Atmos component. - **Compositions** — group the components that make up a system and operate them together across environments with one command. The goal is consistent: run the same declared system locally, in CI, and against real environments, without GitHub Actions-only logic or one-off shell scripts. For usage and configuration, see [container](/workflows/steps/type/container). --- ## Native Dev Container Support: Solving "Works on My Machine" Once and For All Running Atmos and managing cloud infrastructure inevitably means depending on dozens of tools—Terraform, kubectl, Helmfile, AWS CLI, and many more. But here's the problem every platform team faces: **"It works on my machine."** Different versions. Missing dependencies. Subtle configuration differences. Onboarding a new team member becomes a day-long exercise in installing and configuring tools. Something that worked perfectly on your laptop fails in CI. You spend more time managing your toolchain than actually using it. Today, we're solving this problem once and for all with **native Development Container support in Atmos**. ## The DevOps Toolbox Pattern The concept of containerized development environments—what we call "DevOps toolboxes"—isn't new. Companies like CoreOS pioneered the toolbox pattern years ago, recognizing that developers need consistent, reproducible environments without installing dozens of tools locally. This pattern has been proven in DevOps long before the Development Containers specification existed. The idea is simple but powerful: **package all your tools into a container, and developers just need Docker and a shell**. ### Development Containers: The Modern Standard The software development world caught on, and the [Development Containers specification](https://containers.dev/) emerged as an industry standard. Today, every major IDE supports devcontainers: - **VS Code** with the Dev Containers extension - **JetBrains IDEs** (IntelliJ IDEA, PyCharm, WebStorm, etc.) - **Cloud development environments**: GitHub Codespaces, Gitpod, DevPod, Coder, CodeSandbox The specification provides a simple, declarative JSON format (`devcontainer.json`) that describes your development environment. It's become the lingua franca for reproducible dev environments, especially popular in web development and software engineering. **But here's the irony**: While devcontainers are incredibly useful for DevOps workflows, they're primarily supported by IDEs. To use them from the command line—where DevOps teams actually work—you need to install yet another tool (the official devcontainer CLI). ### Why Native Support in Atmos? We asked ourselves: if devcontainers are just JSON configuration describing which container to run, why not support them natively in Atmos? After all, launching a container, mounting volumes, forwarding ports, and executing commands is straightforward. And Atmos can bring superpowers that go beyond basic devcontainer support. **The result? Install Atmos and a container runtime (Docker or Podman), and you're done.** No separate devcontainer CLI, no additional tools. Our implementation is actually slicker than the official CLI—it natively integrates with [`atmos auth`](/cli/commands/auth/usage) to pass authentication credentials to running containers, and includes automatic output masking so you can operate securely. From that point on, everything can happen from a Docker image that gets pulled automatically. ## Introducing `atmos devcontainer shell` The heart of Atmos devcontainer support is one command: **[`atmos devcontainer shell`](/cli/commands/devcontainer/shell)** ```bash # Launch an interactive shell in your devcontainer atmos devcontainer shell geodesic # That's it. You're in a fully-equipped DevOps environment. ``` When using Geodesic as your devcontainer, you have everything pre-installed: - Terraform with all major providers - kubectl and Kubernetes tools - Helmfile and Helm - AWS, Azure, and GCP CLIs - Atmos itself - All your workspace files mounted and ready No installation. No version conflicts. No "works on my machine." Just a consistent, containerized environment that works everywhere. ### Interactive Selection Don't remember the devcontainer name? No problem. Atmos prompts you interactively: ```bash $ atmos devcontainer shell ? Select a devcontainer: ❯ geodesic terraform python-dev ``` Just like [`atmos auth login`](/cli/commands/auth/login), Atmos makes the experience smooth and intuitive. ### Shell Autocomplete Tab completion works for all devcontainer names: ```bash atmos devcontainer shell geo # Autocompletes to: atmos devcontainer shell geodesic ``` ### Multiple Instances Need multiple environments? Launch the same devcontainer configuration with different instance names: ```bash # Development instance atmos devcontainer shell geodesic --instance dev # Production instance atmos devcontainer shell geodesic --instance prod # Each team member can have their own atmos devcontainer shell geodesic --instance alice atmos devcontainer shell geodesic --instance bob ``` Each instance is an independent container with its own state, perfect for running multiple environments or isolating work. ## Configuration in atmos.yaml Devcontainers are configured under the top-level `devcontainer` key in `atmos.yaml`. You can define them inline or import existing `devcontainer.json` files — both approaches are fully supported. ```yaml # atmos.yaml devcontainer: geodesic: spec: name: "Geodesic DevOps Toolbox" image: "cloudposse/geodesic:latest" workspaceFolder: "/workspace" workspaceMount: "type=bind,source=${PWD},target=/workspace" forwardPorts: - 8080 containerEnv: ATMOS_BASE_PATH: "/workspace" remoteUser: "root" terraform: spec: name: "Terraform Development" image: "hashicorp/terraform:1.10" workspaceFolder: "/workspace" forwardPorts: - 3000 mounts: - "type=bind,source=${HOME}/.aws,target=/root/.aws,readonly" ``` ### Use Existing devcontainer.json Files Already have `.devcontainer/devcontainer.json` files? Atmos can use them directly with the [`!include`](/functions/yaml/include) function: ```yaml # atmos.yaml devcontainer: geodesic: spec: !include .devcontainer/devcontainer.json ``` Or include and override specific fields: ```yaml # atmos.yaml devcontainer: geodesic: spec: - !include .devcontainer/devcontainer.json - containerEnv: ATMOS_BASE_PATH: "/workspace" CUSTOM_VAR: "value" ``` This is the **real Atmos `!include` function**—the same powerful YAML processing you use everywhere else in Atmos. It supports deep merging, overrides, and all the template functions you know. ## Geodesic: A Production-Ready Devcontainer While Atmos supports any devcontainer configuration, **[Geodesic](/changelog/geodesic-production-ready-devcontainer) is a proven DevOps toolbox** that's been battle-tested for almost 10 years. Geodesic comes pre-loaded with Atmos, Terraform, kubectl, cloud CLIs (AWS, Azure, GCP), and all the tools you need for infrastructure work. It's multi-platform (amd64/arm64), Debian-based, customizable, and has nearly a decade of production usage. **Read the full post**: [Geodesic: A Production-Ready DevOps Toolbox for Development Containers](/changelog/geodesic-production-ready-devcontainer) ## Getting Started in 2 Minutes Here's how fast you can go from zero to productive: ```bash # 1. Install Atmos (one binary) brew install atmos # 2. Navigate to your infrastructure repo cd my-infrastructure # 3. Launch your devcontainer atmos devcontainer shell # You're in. Start working immediately. ``` **That's the ingenious part**: All you need to install is Atmos. Everything else—Terraform, cloud CLIs, Kubernetes tools—gets pulled from the container image automatically. Your host machine stays clean. Your environment stays consistent. Your team uses identical tool versions. ### Quick Start with Examples Check out the live examples in the Atmos repository to get started immediately: ```bash # Clone Atmos repo (or just browse examples on GitHub) git clone https://github.com/cloudposse/atmos.git cd atmos/examples/devcontainer # The example includes a complete configuration cat atmos.yaml # Launch it atmos devcontainer shell ``` The `examples/devcontainer` folder contains: - Complete `atmos.yaml` with devcontainer configuration - Example `devcontainer.json` file showing the `!include` pattern - Shell aliases for convenience - Ready-to-use configurations **Use this as a starting point** for your own configuration. Copy it, customize it, make it yours. ## Shell Aliases for One-Word Access Make it even easier with shell aliases in your `atmos.yaml`: ```yaml # atmos.yaml aliases: shell: "devcontainer shell" ``` Now you can just type: ```bash atmos shell # Immediately launches your devcontainer ``` If you have a default devcontainer you always use, you can hardcode it: ```yaml aliases: shell: "devcontainer shell geodesic" ``` ## Additional Lifecycle Commands While `shell` is the primary command you'll use, Atmos provides full lifecycle management for advanced scenarios: ```bash # Start a container (create if needed, then start and attach) atmos devcontainer start geodesic --attach # Attach to an already-running container atmos devcontainer attach geodesic # Stop without removing atmos devcontainer stop geodesic # View logs atmos devcontainer logs geodesic # Remove container atmos devcontainer remove geodesic # Rebuild image and recreate atmos devcontainer rebuild geodesic ``` These commands give you fine-grained control when you need it, but **`shell` is what you need 99% of the time**. ## Atmos Superpowers Beyond standard devcontainer support, Atmos brings unique capabilities: ### 1. Zero Additional Dependencies Install Atmos, and you're done. No devcontainer CLI, no separate tools to manage. ### 2. Named Containers with Multiple Instances Unlike traditional devcontainer tools, Atmos supports named devcontainer configurations and multiple instances per configuration. ### 3. Interactive Selection and Autocomplete Atmos prompts you to select from available devcontainers and provides full tab completion. ### 4. Rich Terminal UI Built with the [Charm ecosystem](https://charm.sh/), Atmos provides beautiful progress indicators and status messages while keeping structured output pipeline-friendly. ### 5. Docker and Podman Support Works with both Docker and Podman, with automatic runtime detection. No vendor lock-in. ```yaml # Per-devcontainer runtime selection devcontainer: geodesic: settings: runtime: docker # or podman, or omit for auto-detect ``` ### 6. Identity Injection Atmos supports injecting authenticated identities directly into devcontainers with the `--identity` flag: ```bash # Launch with AWS identity atmos devcontainer shell geodesic --identity aws-prod # Launch with GitHub identity atmos devcontainer shell geodesic --identity github-main # Works with ANY provider - Azure, GCP, custom providers atmos devcontainer shell geodesic --identity azure-prod ``` Inside the container, cloud provider SDKs automatically use the authenticated identity. The implementation is provider-agnostic - each provider's credentials are injected via environment variables without devcontainer code knowing provider-specific details. ### 7. XDG Base Directory Support Atmos automatically configures XDG Base Directory environment variables inside containers, ensuring Atmos and other tools use the correct paths for config, cache, and data files. ### 8. Run Atmos from Atmos The inception pattern—run Atmos inside a devcontainer that already has Atmos installed. Your host machine only needs the Atmos binary; everything else lives in the container. ## Use Cases for Development Containers Development containers are incredibly valuable across different domains: ### Software Development & Web Development - Consistent Node.js, Python, Ruby, or Go environments - Database tools and clients pre-installed - IDE integration for seamless development ### DevOps & Infrastructure - **This is where the pattern originated** with toolboxes like CoreOS Toolbox - Consistent Terraform, kubectl, and cloud CLI versions - No conflicts between different project requirements - Onboarding new team members in minutes instead of hours Development containers are **equally valuable—if not more valuable—for DevOps** than traditional software development. Infrastructure teams juggle more tools, more versions, and more environmental complexity than most application developers. ## Comparison with Traditional Approaches ### Before: Manual Environment Setup ```bash # Install Terraform brew install terraform # Wait, wrong version for this project... tfenv install 1.10.0 tfenv use 1.10.0 # Install AWS CLI pip install awscli # Conflicts with other Python packages... # Install kubectl brew install kubectl # Different version than CI uses... # Install Helmfile brew install helmfile # Repeat for every tool... # Repeat for every team member... # Repeat when versions change... # Repeat when you switch projects... ``` ### After: Atmos Devcontainer ```bash atmos devcontainer shell geodesic # Everything installed, versioned, ready to use ``` ## Practical Subset of the Spec Atmos implements a **practical subset** of the [Development Containers specification](https://containers.dev/implementors/spec/), focusing on the features that matter most for DevOps workflows: ### ✅ Supported - Container image and Dockerfile builds - Volume mounts and workspace configuration - Port forwarding (critical for development) - Environment variables - Container runtime arguments - Build arguments - Remote user configuration ### ❌ Intentionally Unsupported - `features` - Use Dockerfile instead for explicit dependencies - Lifecycle scripts (`postCreateCommand`, etc.) - Use Dockerfile `ENTRYPOINT`/`CMD` - Editor customizations - Use official IDE extensions - Host requirements - Keep it simple This approach keeps the implementation lean, maintainable, and focused on solving the actual problem: **reproducible development environments for infrastructure teams**. ## Get Started Now ### 1. Upgrade Atmos ```bash brew upgrade atmos # or download from GitHub releases ``` ### 2. Check Out the Examples [View the full example](/examples/devcontainer) Prefer to build your own image instead of pulling one? Here's the same workflow with a custom devcontainer build: [View the full example](/examples/devcontainer-build) ### 3. Add to Your Project ```yaml # atmos.yaml devcontainer: geodesic: spec: image: "cloudposse/geodesic:latest" workspaceFolder: "/workspace" workspaceMount: "type=bind,source=${PWD},target=/workspace" aliases: shell: "devcontainer shell geodesic" ``` ### 4. Launch Your Environment ```bash atmos shell # Or: atmos devcontainer shell geodesic ``` ## Conclusion The DevOps toolbox pattern has been proven for years. Development containers brought the pattern into the modern age with an industry-standard specification. Now, **Atmos brings native devcontainer support with DevOps superpowers**. The result? **Install Atmos, run one command, and everything just works.** No more "works on my machine." No more installation marathons. No more version conflicts. Just consistent, reproducible, containerized development environments that work everywhere—on your laptop, in CI, on your team member's machine. **Want a production-ready solution?** Check out [Geodesic: A Production-Ready DevOps Toolbox](/changelog/geodesic-production-ready-devcontainer) to get started in minutes. Or create your own devcontainer configuration. The `examples/devcontainer` folder has live examples you can use immediately. It's a pretty ingenious system, if we do say so ourselves. ## Resources - [Devcontainer Command Documentation](/cli/commands/devcontainer) - [Geodesic: A Production-Ready DevOps Toolbox](/changelog/geodesic-production-ready-devcontainer) - [Development Containers Specification](https://containers.dev/) - [Atmos Examples - Devcontainer](https://github.com/cloudposse/atmos/tree/main/examples/devcontainer) - [Geodesic GitHub Repository](https://github.com/cloudposse/geodesic) - [Atmos GitHub Repository](https://github.com/cloudposse/atmos) --- _Have feedback or questions? Join our [Slack community](https://slack.cloudposse.com/) or [open an issue on GitHub](https://github.com/cloudposse/atmos/issues)._ --- ## Native Helm Components and `atmos helmfile template` Atmos now treats Helm as a first-class component type. Define a Helm release — local chart, remote repository chart, or OCI chart — in your stack configuration, then `template`, `diff`, `apply`, and `delete` it through the Helm Go SDK. No `helm` or `helmfile` binary required. And because Atmos owns rendering, values, lifecycle events, and credentials, an `apply` can publish the rendered manifests to a Git deployment repository instead of a cluster — the producer side of a GitOps workflow. For existing Helmfile users, we also added `atmos helmfile template`, which renders a Helmfile component to manifests and can deliver them to the same provision targets — closing the long-standing request in [#2069](https://github.com/cloudposse/atmos/issues/2069). ## What Changed `components.helm.` is a native component type with the same stack semantics as Terraform and Kubernetes — `vars`, `env`, `auth`, `metadata`, `settings`, `dependencies`, `hooks`, inheritance, and overrides — plus Helm-specific `chart`, `version`, `repositories`, `values`, `values_files`, and `namespace`: ```yaml components: helm: monitoring: chart: prometheus-community/kube-prometheus-stack version: "65.1.1" repositories: - name: prometheus-community url: https://prometheus-community.github.io/helm-charts namespace: monitoring values: grafana: adminPassword: !secret grafana_admin_password dependencies: components: - cert-manager ``` The component `values:` map **is** the chart's values, merged through Atmos inheritance. Charts can be local (`chart: .`), from a repository, or OCI (`chart: oci://...`). The Helm Go SDK renders everything in-process, so [`atmos helm template`](/cli/commands/helm/template) works with no cluster and no credentials. ## Why This Matters Helm has no native secrets concept — the ecosystem reaches for the `helm-secrets` plugin and SOPS. Atmos provides it directly: secret values flow in through the [`!secret`](/functions/yaml/secret) YAML function and are masked automatically. Helm has no native dependency ordering across releases — Atmos provides it through the component DAG (`dependencies.components`), so [`atmos helm apply --all`](/cli/commands/helm/apply) and `--affected` deploy in the right order. ## How to Use It Render, preview, and deploy: ```shell atmos helm template monitoring -s plat-ue2-dev atmos helm diff monitoring -s plat-ue2-dev atmos helm apply monitoring -s plat-ue2-dev ``` Here's the full lifecycle end to end: [View the full example](/examples/helm) Publish to a GitOps repo instead of a cluster: ```yaml components: helm: monitoring: provision: default: cluster targets: cluster: kind: kubernetes deployment-repo: kind: git repository: deployments path: "clusters/{{ .vars.stage }}/monitoring" ``` ```shell atmos helm deploy monitoring -s plat-ue2-dev --target deployment-repo ``` ### Real diffs — no plugin required [`atmos helm diff`](/cli/commands/helm/diff) (alias `plan`) shows a true unified diff, not just a dry-run dump. Atmos renders the chart client-side and compares it with the [helm-diff](https://github.com/databus23/helm-diff) engine embedded directly in the binary — so you get the familiar `+`/`-` output **without installing the `helm-diff` CLI plugin**. Secret values are redacted automatically. It diffs against whichever baseline fits your workflow: ```shell # Against the deployed release (requires cluster access). atmos helm diff monitoring -s plat-ue2-dev # Against a local baseline manifest — fully offline. atmos helm diff monitoring -s plat-ue2-dev --from-manifest=current.yaml # Against the GitOps deployment repository — the producer-side # "what will this change in the repo?" diff, offline (git access only). atmos helm diff monitoring -s plat-ue2-dev --against=target ``` In CI, the diff is published to the job summary as a collapsible block (Secrets omitted), matching the native Kubernetes component. Already on Helmfile? Render and publish without migrating: ```shell atmos helmfile template echo-server -s tenant1-ue2-dev --target deployment-repo ``` ## Get Involved See the [`atmos helm`](/cli/commands/helm/usage) and [`atmos helmfile template`](/cli/commands/helmfile/template) docs to get started, and the [`examples/helm`](https://github.com/cloudposse/atmos/tree/main/examples/helm) example for a runnable local-chart project. --- ## Automatically Create the Configured Namespace for Helm Releases Atmos lets you configure the Kubernetes namespace where a Helm release should be installed. This prevents charts that do not specify a namespace from being installed into `default`. That namespace may not exist. Native Helm creates it for you automatically, so a release deploys in a single operation instead of requiring a separate command, component, or workflow to create the namespace first. Sometimes, though, the deployment should not be creating namespaces at all. ## The Problem A Helm chart does not always specify the namespace where its resources should be installed. Atmos solves this by installing the release into the namespace configured for the component: ```yaml components: helm: backend-api: chart: "backend-api" namespace: backend-api ``` This keeps the release out of the `default` namespace, and native Helm creates the namespace automatically when it is missing, so the deployment is self-contained. Automatic creation is not always wanted. When a platform team owns the namespace along with its labels, quotas, and NetworkPolicies, the release should not create it. And when the deploy identity is scoped to a single namespace and cannot create namespaces, the forced creation fails with a `403`, even when the namespace already exists. ## The Fix Native Helm components now support `create_namespace`. It defaults to `true`, so the namespace is created automatically as before. Set it to `false` to install into a pre-existing namespace instead: ```yaml components: helm: backend-api: chart: "backend-api" namespace: backend-api create_namespace: false values: replicaCount: 1 ``` The release then deploys into the namespace that already exists, with no cluster-level permission to create namespaces: ```shell atmos helm apply backend-api -s plat-ue2-dev ``` Left at its default, Helm creates the namespace when it is missing. Set to `false`, Helm installs into the namespace that is already there. ## When to Use It Keep `create_namespace` enabled (the default) when the Helm deployment should manage its own namespace and the deploying identity has permission to create namespaces. Set it to `false` when namespaces are managed separately, for example when a platform component is responsible for namespace labels, quotas, NetworkPolicies, or other guardrails, or when the deploy identity is scoped to a single namespace. For usage and configuration, see [atmos helm apply](/cli/commands/helm/apply). ## Get Involved Atmos is open source and we'd love your feedback. Join the conversation in the [Atmos community Slack](https://cloudposse.com/slack) or open an issue on [GitHub](https://github.com/cloudposse/atmos). --- ## Declarative Release Lifecycle for Native Helm Components Shipping a Helm release is rarely a single `helm upgrade --install`. A first install onto a cold cluster needs a generous timeout; a routine upgrade should fail fast. A broken upgrade should roll back and clean up the resources it left behind; a broken first install should uninstall itself so it doesn't wedge the next attempt. Some releases must wait for their Jobs to finish, others only for a readiness gate, and a few shouldn't wait at all. Getting this right usually means memorizing a pile of Helm flags and threading them into shell wrappers per command — and trusting that every environment runs the same incantation. Native Helm components in Atmos now let you declare that entire lifecycle as configuration — per operation — and inherit it the same way you inherit everything else in a stack. ## The Problem Helm exposes the knobs — waiting, timeouts, atomic upgrades, cleanup-on-failure, history retention, waiting for Jobs, CRD handling, hook control — but only as flags on individual command invocations. That has two costs: - **The policy lives in shell history, not in your repo.** The intent behind a release ("wait for the watcher, keep ten revisions, roll back on a failed upgrade") is invisible to the next person and drifts between laptops, CI, and production because nothing pins it down. - **One flat set of flags can't express operation-specific intent.** A slow first install and a quick upgrade want different timeouts. Rollback is the right recovery for an upgrade but meaningless on a first install — there is no previous revision to return to. Uninstall-on-failure is the opposite: exactly right for a first install, wrong for an upgrade you want to preserve. A single set of flags forces one compromise across all three. ## The Fix Native Helm components take a `release` policy with release-wide defaults and operation-specific overlays for `install`, `upgrade`, and `delete`. The whole tree deep-merges through your stack defaults, abstract/base components, and the concrete component — so a platform team can set org-wide defaults and individual components override only what they need. Explicit CLI flags still win at the highest precedence, so incident-time overrides remain a one-liner. The policy covers the lifecycle end to end: - **Wait strategy** — pick the status watcher, a hook-only wait, or legacy waiting, and optionally wait for Jobs to complete. - **Per-operation timeouts** — a long budget for the first install, a short one for upgrades and deletes. - **Failure recovery** — uninstall a failed first install; roll back a failed upgrade and clean up its partial resources. - **History retention, chart hooks, and CRD policy** — keep N revisions, enable or skip chart hooks, and choose whether CRDs are created or left alone. - **Dry run** — preview an apply without persisting a release or touching the cluster. Invalid combinations fail early and clearly — asking for `rollback` on an install, or a field that doesn't apply to the selected operation, is rejected before anything runs rather than silently ignored. ## How to Use It Declare the lifecycle on the component. Release-wide settings sit at the top; `install`, `upgrade`, and `delete` refine them: ```yaml components: helm: my-app: chart: "." namespace: my-app release: timeout: 4m wait: strategy: watcher history: max: 10 install: timeout: 10m # a cold-start install gets more room on_failure: uninstall # don't leave a wedged half-install behind upgrade: on_failure: rollback # return to the last good revision cleanup_on_failure: true delete: wait: strategy: legacy values: replicaCount: 2 ``` Atmos selects the operation for you and applies the matching overlay: ```shell atmos helm apply my-app -s dev # install or upgrade, per the release's state atmos helm apply my-app -s dev --dry-run # preview, no release persisted atmos helm apply my-app -s dev --timeout 15m # incident-time override wins ``` Native Helm support is still experimental (Atmos prints a 🧪 notice when you use it), so the surface may evolve — but the release policy above is what you'll reach for to make deployments deterministic across environments. For usage and configuration, see [atmos helm apply](/cli/commands/helm/apply). ## Get Involved The full contract lives in the native Helm release lifecycle PRD in the repo, and the `atmos-helm` skill documents day-to-day usage. If you run Helm through Atmos, try declaring a `release` policy on a component and tell us where the defaults or precedence surprised you — that feedback is what shapes the feature out of experimental. --- ## Native Kubernetes Components: Render, Apply, and Publish to GitOps Repos Atmos now treats Kubernetes as a first-class component type. Define Kubernetes objects — inline manifests, files, directories, or Kustomize overlays — in your stack configuration, then `render`, `diff`, `apply`, and `delete` them through the Kubernetes Go SDK with server-side apply. No `kubectl` or `kustomize` binary required. And because Atmos owns rendering, lifecycle events, and credentials, an `apply` can publish the rendered manifests to a Git deployment repository instead of a cluster — the producer side of a GitOps workflow. ## What Changed `components.kubernetes.` is a native component type with the same stack semantics as Terraform and Helmfile — `vars`, `env`, `auth`, `metadata`, `settings`, `dependencies`, `hooks`, inheritance, and overrides — plus Kubernetes-specific `paths`, `manifests`, and `render` sections: ```yaml components: kubernetes: argocd: provider: kustomize vars: { cluster: ue2-dev, namespace: argocd } paths: - bootstrap/argocd manifests: - apiVersion: v1 kind: Namespace metadata: { name: "{{ .vars.namespace }}" } ``` Run it like any other component: ```shell atmos kubernetes render argocd -s plat-ue2-dev # print the manifests atmos kubernetes diff argocd -s plat-ue2-dev # server-side diff atmos kubernetes apply argocd -s plat-ue2-dev # server-side apply ``` [`--all`](/cli/commands/kubernetes/usage) and [`--affected`](/cli/commands/kubernetes/usage) run components in dependency order, so the same change detection that drives Terraform pipelines works for Kubernetes. [View the full example](/examples/kubernetes) When native CI is enabled, Kubernetes commands now write a compact job summary to the CI provider summary file, such as `$GITHUB_STEP_SUMMARY` in GitHub Actions. The summary reports object actions for `plan`/`diff`, `apply`/`deploy`, `delete`, and `validate`, plus an error section on failure. For `plan`/`diff` it also includes a collapsible **Kubernetes Diff** block with the per-object unified diff (GitHub renders the `+`/`-` lines in green and red); the same diff appears in the terminal output. `Secret` objects are omitted from the diff so their data is never printed or written to the summary. This first CI slice is deliberately summary-only: Kubernetes commands do not emit `$GITHUB_OUTPUT` values, commit statuses, PR comments, or stored artifacts. ## Deliver to a Cluster — or a GitOps Repository By default `apply`/`deploy` applies to the cluster. But many teams don't apply directly: they commit rendered manifests to a deployment repository that Argo CD or Flux reconciles. Atmos models delivery destinations as **provision targets**, selected by `kind`: ```yaml git: repositories: deployments: uri: https://github.com/acme/deployments.git branch: main components: kubernetes: argocd: provision: default: cluster targets: cluster: kind: kubernetes deployment-repo: kind: git repository: deployments path: "clusters/{{ .vars.cluster }}/argocd" commit: message: "Render {{ .vars.app_name }} for {{ .vars.stage }}" ``` ```shell # apply to the cluster (default) atmos kubernetes apply argocd -s plat-ue2-dev # render and publish to the deployment repo instead atmos kubernetes apply argocd -s plat-ue2-dev --target=deployment-repo ``` The git target clones (or fast-forwards) the repository, replaces the managed `path` with the freshly rendered manifests, commits that path with provenance trailers (`Atmos-Stack`, `Atmos-Component`), and pushes. Re-delivering identical manifests is a clean no-op. It is built on the reusable [Atmos Git](/cli/commands/git/usage) service, so credentials come from Atmos Auth (GitHub STS) and never land in the manifests. This is the same target convention Atmos uses elsewhere for "where to put things," and the `git` target is component-agnostic: the rendered-artifact model that Kubernetes produces today is the same one future component types will use to publish to `git`, `oci`, and other destinations. ## Why This Matters GitOps pipelines have always needed glue: ad hoc scripts to render manifests into a deployment repo, commit them, survive push races, and wire credentials. Atmos already owns rendering, lifecycle events, and authentication — provision targets add the delivery step with centralized safety rules, so the same component configuration can apply to a cluster in dev and publish to a GitOps repo in prod with a single flag. ## How to Use It - Add a `components.kubernetes.` to a stack with `paths`/`manifests`. - Run `atmos kubernetes render|diff|apply|delete -s `. - To publish to a deployment repo, declare a `git.repositories.` and a `provision.targets` entry with `kind: git`, then `apply ... --target=`. See the [Kubernetes component](/stacks/components/kubernetes) and [`atmos kubernetes`](/cli/commands/kubernetes/usage) documentation to get started. --- ## Fixed: Nested Maps in Terraform Backend Configurations Atmos now properly handles nested maps in Terraform backend configurations when using HCL format. This fixes an issue where complex backend settings like `assume_role` were silently dropped. ## The Problem When generating backend configurations with [`atmos terraform generate backends --format hcl`](/cli/commands/terraform/generate/backends), nested maps were missing from the output. For example: ```yaml terraform: backend: s3: bucket: "my-terraform-states" assume_role: role_arn: "arn:aws:iam::123456789012:role/terraform" session_name: "terraform-backend" ``` The generated `backend.tf` was missing the `assume_role` configuration entirely. ## The Fix We added a recursive type converter that handles nested maps, arrays, and all Go types when generating HCL output. The JSON and `backend-config` formats already worked correctly. ## Example Now all nested configurations are properly preserved: ```hcl terraform { backend "s3" { bucket = "my-terraform-states" assume_role = { role_arn = "arn:aws:iam::123456789012:role/terraform" session_name = "terraform-backend" } } } ``` ## Usage Upgrade Atmos and regenerate your backend configurations: ```bash atmos terraform generate backends --format hcl ``` All three formats (HCL, JSON, backend-config) now correctly handle nested structures. For usage and configuration, see [State Backends](/components/terraform/backends). --- ## oci:// Sources Now Work with JIT Auto-Provisioning A component with an `oci://` source and `provision.workdir.enabled: true` failed the moment you ran any Terraform operation against it. JIT auto-provisioning (the `before.terraform.init` hook) failed with go-getter's `download not supported for scheme 'oci'`, even though [`atmos vendor pull`](/cli/commands/vendor/pull) handled the exact same source fine. Registries distributing modules in OpenTofu's native OCI module-package format couldn't be pulled by either path at all. ## The Problem - JIT auto-provisioning and `atmos vendor pull` had two separate, non-overlapping implementations of "download a source." Only the vendor-pull one understood `oci://`. - Registries publishing modules via OpenTofu's native "install modules from OCI registries" format ship their content as a ZIP-archive layer, not the tar+gzip layer Atmos expected. Pulling one failed with `archive/tar: invalid tar header` regardless of which path you used. - OCI pulls also had no timeout, so a slow or unresponsive registry could hang a command indefinitely. ## The Fix - The OCI-fetch implementation moved into a shared `pkg/oci` package so both `atmos vendor pull` and JIT auto-provisioning use the same implementation instead of two divergent ones. - The puller now recognizes ZIP-layer OCI artifacts (OpenTofu's module-package format) in addition to Atmos's own tar+gzip format, with the same directory-traversal and decompression-size guards either way. - OCI pulls are now bounded by a 10-minute timeout, matching the existing go-getter download path. ## How to Use It ```yaml components: terraform: my-component: source: uri: "oci://ghcr.io/my-org/my-module:v1.2.0" provision: workdir: enabled: true ``` Running [`atmos terraform plan my-component -s `](/cli/commands/terraform/plan) now auto-provisions the source before running, the same way it already does for git and HTTP sources. Authentication works the same way it already does for `atmos vendor pull`: your existing registry credentials are picked up automatically, with an anonymous fallback if none are configured. For usage and configuration, see [Source](/vendor/component-manifest/source). ## Get Involved See the [Workdir Provisioning](/stacks/components/provision/workdir) docs for the full `provision.workdir` reference, or open an issue if you hit an OCI registry format that still doesn't extract cleanly. --- ## AI Skills, Now Bundled in the Atmos Binary The official Atmos [agent skills](/cli/commands/ai/skill) are now embedded directly in the Atmos binary. `atmos ai skill install ` works **fully offline** — no network call, no Git clone — and `atmos ai skill list` shows a single merged view of every skill available to you alongside what's already installed. ## What Changed Previously, installing an Atmos skill meant fetching it from a GitHub repository. But this also meant that the skills could be out of sync with the version of Atmos that you're running. Now the complete catalog of official skills (and their reference files) ships inside the binary via an embedded filesystem. Two user-facing improvements fall out of that: - **Offline install by name.** `atmos ai skill install atmos-terraform` copies the skill straight out of the binary — instant, deterministic, and air-gap friendly. Community skills can still be installed from any GitHub repository. - **A unified available-vs-installed listing.** `atmos ai skill list` merges the bundled catalog with what's installed locally, alphabetically sorted, with a status marker per skill (`●` installed, `○` available). ## How to Use It ```shell # Browse everything — bundled catalog plus anything you've installed atmos ai skill list # Show only what's installed atmos ai skill list --installed # More detail per skill atmos ai skill list --detailed # Install an official skill offline, by its bare name atmos ai skill install atmos-terraform ``` The [`--installed`](/cli/commands/ai/skill) view is also bound to `ATMOS_AI_SKILL_INSTALLED` for scripting. Once a skill is installed, use it with any command via the global `--skill` flag (with `--ai`): ```shell atmos terraform plan vpc -s plat-ue2-prod --ai --skill atmos-terraform ``` ## Why It Matters - **Works anywhere.** No network or Git access required to install the official skills — ideal for locked-down CI runners and air-gapped environments. - **Discoverable.** One command shows the full catalog and your install state together, instead of guessing what's available. - **Versioned with Atmos.** The bundled skills travel with the binary, so the catalog always matches the Atmos you're running. ## Get Involved See the [`atmos ai skill`](/cli/commands/ai/skill) command reference. Skill ideas and contributions are welcome in the [Atmos community](https://github.com/cloudposse/atmos). --- ## Run Ordered Steps in Lifecycle Hooks Atmos lifecycle hooks can now run an ordered list of steps with `kind: steps`. Use it when one lifecycle event needs sequencing but the orchestration should stay local to the component being operated on. ## What Changed `kind: step` runs one registered step type as a lifecycle hook. `kind: steps` runs several registered step types in order: ```yaml hooks: test-fixtures-up: events: [before.terraform.test] kind: steps on_failure: fail with: - type: emulator component: aws stack: fixtures action: up - type: atmos command: terraform apply vpc -s fixtures -auto-approve ``` The hook envelope stays the same: `events`, `when`, `on_failure`, `retry`, and `env` are still hook-level controls. The ordered payload lives under `with:`, the same conventional payload key used by `kind: step`. ## Why This Matters Some test fixtures need real lifecycle ordering. For example, a component test may need a local AWS emulator before it can provision a VPC fixture, and the fixture must be destroyed after the test even if the test fails. Before `kind: steps`, the choices were awkward: - split the lifecycle across multiple hooks and rely on unordered map iteration, - move component-specific fixture setup into a custom command or workflow, or - fall back to a helper script. `kind: steps` keeps the fixture lifecycle declarative and component-local: ```yaml hooks: test-fixtures-up: events: [before.terraform.test] kind: steps on_failure: fail with: - type: emulator component: aws stack: fixtures action: up - type: atmos command: terraform apply vpc -s fixtures -auto-approve test-fixtures-down: events: [after.terraform.test] when: always kind: steps with: - type: atmos command: terraform destroy vpc -s fixtures -auto-approve - type: emulator component: aws stack: fixtures action: down ``` Steps run in list order. If a step fails, Atmos stops the list and applies the hook's `on_failure` policy. A hook-level `retry` retries the whole ordered list. For Terraform tests, Atmos still owns the normal component preparation. It generates the tested component varfile from the selected stack and can pass a second generated test varfile from `test.vars`. Because `test.vars` resolves after `before.terraform.test` hooks run, it can use [`!terraform.state`](/functions/yaml/terraform.state) to pass fixture outputs into Terraform test without scripts. ## Learn More See the [Hooks reference](/stacks/hooks#kind-steps-run-ordered-steps) for the full `kind: steps` syntax. --- ## Packer Directory-Based Templates for Multi-File Configurations Atmos now supports directory-based Packer templates by default. Instead of requiring a single HCL template file, you can organize your Packer configurations across multiple files following HashiCorp's recommended patterns. Atmos automatically passes the component directory to Packer, which loads all `*.pkr.hcl` files. ## What Changed Previously, Atmos required users to specify a single HCL file via `--template` flag or `settings.packer.template` configuration. Running Packer commands without this setting would result in an error: ```bash # Old behavior atmos packer build my-ami -s prod # Error: packer template is required ``` Now, Atmos defaults the template to `.` (component working directory) when not specified, allowing Packer to load all `*.pkr.hcl` files from the component directory automatically: ```bash # New behavior - works out of the box atmos packer build my-ami -s prod # Packer loads all *.pkr.hcl files from the component directory ``` ## Why This Matters HashiCorp recommends organizing Packer configurations across multiple files for better maintainability: - `variables.pkr.hcl` - Variable declarations - `main.pkr.hcl` or `template.pkr.hcl` - Source and build blocks - `locals.pkr.hcl` - Local values - `plugins.pkr.hcl` - Required plugins When users followed this practice, Atmos would only load the specified template file, causing "Unsupported attribute" errors when variables were defined in separate files. This change aligns Atmos with Packer's native behavior and HashiCorp best practices. ## How to Use It ### Directory Mode (New Default) Simply organize your Packer component with multiple files and run commands without specifying a template: ``` components/packer/my-ami/ ├── variables.pkr.hcl # Variable declarations ├── main.pkr.hcl # Source and build blocks ├── locals.pkr.hcl # Local values (optional) └── plugins.pkr.hcl # Required plugins (optional) ``` ```yaml # Stack configuration - no template needed components: packer: my-ami: vars: region: us-east-1 instance_type: t3.medium ``` ```bash # All commands work without --template atmos packer init my-ami -s prod atmos packer validate my-ami -s prod atmos packer build my-ami -s prod atmos packer inspect my-ami -s prod ``` ### Single File Mode (Backward Compatible) If you prefer to use a single template file, you can still specify it explicitly: ```yaml # Stack configuration with explicit template components: packer: my-ami: settings: packer: template: main.pkr.hcl # Use specific file vars: region: us-east-1 ``` Or use the `--template` flag: ```bash atmos packer build my-ami -s prod --template main.pkr.hcl ``` ## Examples ### Multi-File Component Structure ```hcl # components/packer/my-ami/variables.pkr.hcl variable "region" { type = string description = "AWS region to build the AMI" } variable "instance_type" { type = string default = "t3.medium" description = "EC2 instance type for the builder" } variable "ami_name" { type = string description = "Name for the resulting AMI" } ``` ```hcl # components/packer/my-ami/main.pkr.hcl packer { required_plugins { amazon = { version = ">= 1.2.0" source = "github.com/hashicorp/amazon" } } } source "amazon-ebs" "base" { region = var.region instance_type = var.instance_type ami_name = "${var.ami_name}-{{timestamp}}" # ... other settings } build { sources = ["source.amazon-ebs.base"] # ... provisioners } ``` ### Validating Multi-File Components ```bash # Validate all files in the component directory atmos packer validate my-ami -s prod # Inspect the combined template atmos packer inspect my-ami -s prod ``` ## Backward Compatibility All existing configurations continue to work unchanged: - Components with explicit `settings.packer.template` use the specified file - The `--template` flag overrides both the default and settings values - Single-file components work exactly as before ## Documentation For more details, see: - [Packer Components](/stacks/components/packer) - [Packer Build Command](/cli/commands/packer/build) - [Packer Usage](/cli/commands/packer/usage) For usage and configuration, see [Using Packer](/components/packer). --- ## Pager Default Behavior Corrected We've identified and corrected a regression in Atmos where the [pager](/cli/global-flags#pager-control-examples) was incorrectly enabled by default, contrary to the intended behavior documented in a previous release. ## What Changed The pager is now correctly **disabled by default** in Atmos. This aligns with the behavior that was intended in PR #1430 (September 2025) but was not fully implemented. ## Background In May 2025, pager support was added to Atmos with the default set to `true` (enabled). Later, in September 2025, PR #1430 was merged with the intention of changing this default to improve the scripting and automation experience. The PR included: - A global [`--pager`](/cli/global-flags#pager-control-examples) flag - Support for the `NO_PAGER` environment variable - Documentation stating: "**BREAKING CHANGE**: Pager is now disabled by default" However, the actual default value in the configuration system was never changed from `true` to `false`, causing the pager to remain enabled by default despite the documentation. ## Impact If you've been experiencing unexpected pager behavior (output being displayed through a pager like `less` when you didn't expect it), this fix resolves that issue. If your workflow relied on the pager being enabled by default, you'll need to explicitly enable it using one of these methods: ### Enable Pager via Configuration Add to your `atmos.yaml`: ```yaml settings: terminal: pager: true ``` ### Enable Pager via CLI Flag Use the `--pager` flag on any command: ```bash atmos describe component myapp -s prod --pager ``` ### Enable Pager via Environment Variable Set the `ATMOS_PAGER` or `PAGER` environment variable: ```bash export ATMOS_PAGER=true atmos describe component myapp -s prod ``` Or specify a custom pager: ```bash export ATMOS_PAGER=less atmos describe component myapp -s prod ``` ## Why This Change Matters Having the pager disabled by default provides several benefits: 1. **Better automation/scripting**: Output can be piped and processed without unexpected pager interaction 2. **Predictable behavior**: Commands behave consistently whether run interactively or in CI/CD 3. **Explicit opt-in**: Users who want pagination can easily enable it per their preferences ## Migration Guide Most users won't need to change anything. If you were relying on the pager being enabled by default: 1. Add `pager: true` to your `atmos.yaml` settings 2. Or use the `--pager` flag when you want paginated output 3. Or set the `ATMOS_PAGER` environment variable in your shell profile ## Related Links - [PR #1642: Pager Default Correction](https://github.com/cloudposse/atmos/pull/1642) - [Original PR #1430: Pager Improvements](https://github.com/cloudposse/atmos/pull/1430) - [Terminal Configuration Documentation](/cli/configuration/settings/terminal) We apologize for any confusion this regression may have caused and thank the community for bringing it to our attention. --- ## Parallel and Matrix Steps for Atmos Workflows Atmos workflows can now run independent work concurrently with first-class `parallel` and `matrix` control steps. Add dependency-aware fan-out, readable grouped or live-prefixed output, and explicit failure behavior directly to your workflow YAML. ## The Problem Workflows are where teams encode the operational knowledge that should not live in someone's shell history: run the checks, build the thing, deploy the dependencies, then summarize what happened. Until now, those steps were sequential. That was easy to reason about, but it meant a workflow with four independent checks took the sum of all four runtimes. The usual workaround was to drop into shell scripts, background jobs, `wait`, temp files, and hand-rolled log prefixes. That works until it doesn't: - Output from concurrent commands interleaves into unreadable logs. - Failure behavior is implicit and different in every script. - Dependency relationships are hidden in shell control flow. - Local workflows and CI matrices drift apart. Infrastructure automation should not force you to choose between "simple but slow" and "fast but fragile." ## What's New Atmos now supports two new workflow control step types: - **`parallel`** runs sibling steps concurrently. - **`matrix`** expands literal axes and schedules the generated child steps. Both support: - **`needs`** dependencies between sibling steps. - **`max_concurrency`** to bound parallelism. - **Failure modes**: `wait_all`, `fail_fast`, and `best_effort`. - **Output modes**: `grouped`, `prefixed`, and `none`. - **Parent-owned summaries** with success, failed, skipped, and canceled counts. This is built into the workflow engine, so the orchestration rules are visible in the workflow file instead of buried in shell glue. ## Parallel Checks Run independent checks together, then run a dependent summary step only after both prerequisites succeed: ```yaml title="stacks/workflows/checks.yaml" workflows: checks: steps: - name: checks type: parallel max_concurrency: 4 fail: mode: wait_all output: mode: grouped order: completion show_summary: true prefix: "{{ .step.name }}" steps: - name: lint type: shell command: make lint - name: test type: shell command: make test - name: summarize type: shell needs: [lint, test] command: ./scripts/summary.sh ``` The workflow is still declarative: `summarize` says what it needs, not how to poll for it. Atmos schedules everything else. ## Matrix Fan-Out Use `matrix` when the same step should run across combinations: ```yaml title="stacks/workflows/test-matrix.yaml" workflows: test-matrix: steps: - name: test-matrix type: matrix max_concurrency: 3 output: mode: grouped order: definition matrix: os: [linux, darwin] go: ["1.22", "1.23"] steps: - name: test type: shell command: make test OS={{ .matrix.os }} GO_VERSION={{ .matrix.go }} ``` That gives you CI-style fan-out without requiring the workflow to become a GitHub Actions-only construct. The same workflow can run locally, in CI, or inside a larger operational runbook. ## Output That Stays Readable Concurrent output is only useful if humans can read it. The control step owns child output rendering: - `grouped` captures child stdout/stderr and prints labeled blocks. - `prefixed` streams live output with complete-line prefixes. - `none` suppresses terminal output while still capturing metadata. For live logs: ```yaml output: mode: prefixed prefix: "{{ .step.name }}" ``` Example output: ```text [lint] checking formatting [test] running unit tests [lint] passed [test] passed [checks] summary: 2 succeeded, 0 failed, 0 skipped, 0 canceled ``` The summary uses the same Atmos UI formatter as other command output, so success, warning, and failure states are immediately visible. ## Explicit Failure Semantics Parallel work needs a clear answer to "what happens when one branch fails?" ```yaml fail: mode: wait_all # wait_all | fail_fast | best_effort max_failures: 2 # 0 means unlimited ``` - **`wait_all`** lets independent ready/running branches continue, skips dependents of failed children, and fails the parent after schedulable work settles. - **`fail_fast`** cancels pending and running siblings once the failure threshold is reached. - **`best_effort`** records failures and skips dependents, but lets the parent succeed unless the control step itself is invalid. That makes failure behavior reviewable. Operators can choose fast feedback for checks, complete collection for reports, or best-effort fan-out where partial success is still useful. ## Guardrails for v1 The first version intentionally allows only non-interactive child steps inside concurrent groups: - `shell` - `atmos` - `sleep` Interactive prompts, terminal-owning renderers, file editors, pagers, spinners, environment-mutating steps, and `exec` are kept outside concurrent groups for now. That boundary is deliberate: concurrent workflows should not start by letting multiple children fight over the same terminal. You can still use rich UI steps before or after a `parallel` or `matrix` control step to frame the workflow, show tables, render markdown, or summarize the result. ## Why This Matters Parallel and matrix workflow steps make Atmos workflows feel like real orchestration instead of a sequential macro runner. - Local runbooks get faster without becoming bash concurrency puzzles. - CI and local automation can share the same workflow definition. - Dependency relationships are visible as `needs`, not hidden in scripts. - Output remains readable by default. - Failure behavior is part of the contract. - Matrix fan-out is available anywhere Atmos runs, not only inside a CI provider. This is especially useful for validation workflows, multi-component smoke tests, cross-platform checks, reporting jobs, and any operational task where several independent branches can run safely at the same time. ## Try It This PR includes a runnable example: ```shell cd examples/parallel-steps atmos workflow checks -f parallel atmos workflow prefixed -f parallel atmos workflow matrix -f parallel ``` Here are the control steps running end to end: [View the full example](/examples/parallel-steps) Start with validation and reporting workflows first. They usually have the safest fan-out shape: independent checks, obvious dependencies, and low risk if one branch fails. For the full field reference, see the [`parallel`](/workflows/steps/type/parallel) and [`matrix`](/workflows/steps/type/matrix) step type documentation. ## Get Involved Try the new control steps on real workflows and tell us where the v1 guardrails feel too strict or exactly right. We're especially interested in feedback on output modes, failure semantics, and which additional non-interactive step types should be allowed inside concurrent groups next. --- ## Faster, Safer Parallel Toolchain Installs Atmos toolchain installs can now run independent packages in parallel. Batch installs use four workers by default, while preserving the compact terminal UI: every active package keeps its own spinner, download size, verification state, and final result line. ## Faster installs without shared-state races Tool downloads, extraction, version markers, `toolchain.lock.yaml`, and `.tool-versions` are all coordinated with granular advisory locks. Independent tools continue in parallel, but operations that mutate the same cache asset, installation directory, or configuration file wait safely instead of corrupting one another. The locks work across separate Atmos processes as well as parallel workers in one process. The progress display now keeps download totals visible on the right edge of the terminal and changes the active state to **Verifying** once the asset is available. Non-interactive output and the existing completed-result summary remain unchanged. ## Configure concurrency Set the maximum number of simultaneous installs in `atmos.yaml`: ```yaml toolchain: max_concurrency: 4 ``` Or override it for a single command: ```shell atmos toolchain install --max-concurrency 8 ``` Atmos rejects values lower than one. The command-line flag takes precedence over configuration. ## Reliable signature verification Atmos also isolates its bootstrap verifier from concurrent use and retries only transient, pre-verdict verifier failures. Legacy releases that publish detached Cosign certificates and signatures are verified with the compatible Cosign v2 path; newer bundle-based verification continues to use the current verifier. Verification remains required—retries never treat an invalid signature or identity mismatch as success. See the [toolchain documentation](/cli/commands/toolchain/usage) for installation and configuration details. For usage and configuration, see [atmos toolchain install](/cli/commands/toolchain/install). --- ## Parent Directory Search and Git Root Discovery Atmos now searches parent directories for `atmos.yaml` and discovers `.atmos.d/` at the git repository root, making it easier to run commands from anywhere in your project. ## Parent Directory Search Atmos now automatically searches parent directories for `atmos.yaml` when not found in the current directory. Run Atmos from any subdirectory without specifying `--config-path`: ```bash cd /repo/components/terraform/vpc atmos terraform plan vpc -s prod # Finds /repo/atmos.yaml automatically ``` ## Git Root Discovery for `.atmos.d/` Atmos automatically discovers `.atmos.d/` at your git repository root, even when running from subdirectories. Define shared custom commands once at the repo root and use them from anywhere. ``` repo/ ├── .atmos.d/ │ └── commands.yaml # Available repo-wide ├── atmos.yaml └── components/terraform/vpc/ └── main.tf # Run atmos from here ``` ## Documentation - [CLI Configuration](/cli/configuration) — Parent directory search, git root discovery, and `.atmos.d/` auto-imports --- ## Split One Logical Stack Across Focused Manifests Large stacks eventually turn into a shared bottleneck. Network, platform, and application owners all need to contribute components, but putting every change in one manifest makes reviews noisy and parent-level configuration easy to accidentally share. Atmos now lets multiple top-level manifests represent one logical stack while keeping each manifest's components and parent scope independent. ## The Problem Teams often organize shared catalog imports and component instances by who owns them. Before this change, splitting one logical environment across parent manifests meant commands treated those files as separate stacks, or required a single large manifest that mixed unrelated scope and ownership. That made it difficult to divide ownership without also changing how teams named, discovered, and operated the stack. ## The Fix Atmos recognizes parent manifests with the same stack identity as one logical stack for discovery and component selection. Each manifest still resolves its own imports, globals, and component configuration, so values from one parent do not leak into another. Within that logical stack: - Distinct components from every parent appear together in stack discovery. - Equivalent duplicate components choose a stable, lexical canonical source. - Conflicting duplicate components remain errors with the parent manifests identified in the diagnostic. - Inheritance stays self-contained: a base must be defined inline or explicitly imported by the parent that uses it. ## How to Use It Give the related parent manifests the same stack identity through `name`, `name_template`, or `name_pattern`. For example, two manifests can use the same template-derived environment and stage: ```yaml # stacks/catalog/shared.yaml components: terraform: chatops-base: metadata: component: mock vars: notifications_enabled: true ``` ```yaml # stacks/parents/01-network.yaml vars: environment: dev stage: shared components: terraform: dns-primary: metadata: component: mock ``` ```yaml # stacks/parents/02-platform.yaml import: - catalog/shared vars: environment: dev stage: shared components: terraform: chatops: metadata: component: mock inherits: - chatops-base ``` With a matching stack name configuration, [`atmos describe stacks`](/cli/commands/describe/stacks) reports one logical stack, while [`atmos describe component chatops -s dev-shared`](/cli/commands/describe/component) retains the platform parent's scope and resolves its base through the platform manifest's explicit catalog import. A component defined only by the network parent is discoverable in the logical stack, but is not an implicit inheritance dependency of the platform parent. For usage and configuration, see [Stack Behavior](/cli/configuration/stacks). ## Get Involved Try splitting a large logical stack along your team's ownership boundaries. If you encounter an import, inheritance, or duplicate-resolution case that is not clear, please [open an issue](https://github.com/cloudposse/atmos/issues) with a minimal stack example. --- ## Path-Based Component Resolution for Terraform, Helmfile, Packer, Describe, and Validate Commands Atmos now supports using filesystem paths instead of component names for all component commands. Use `.` for the current directory, relative paths like `./vpc` or `../eks`, or absolute paths. This might feel more natural for users accustomed to running commands on folders rather than remembering specific component names. ## What's New You can now use filesystem paths with all component-related commands: ```bash # Navigate to component directory cd components/terraform/vpc # Use . to reference current directory atmos terraform plan . --stack dev atmos describe component . --stack dev atmos validate component . --stack dev ``` This works with all Terraform, Helmfile, and Packer commands that accept a component argument. ## Supported Path Formats Atmos supports all standard ways of specifying paths to components, including: - **`.`** - Current directory - **`./component`** - Relative path from current directory - **`../other-component`** - Relative path to sibling directory - **`/absolute/path/to/component`** - Absolute path ## Features - **Auto-detection** - Atmos automatically detects component type (terraform/helmfile/packer) from the filesystem path - **Stack validation** - Validates the component exists in the specified stack configuration - **Interactive selection** - When multiple components reference the same path, choose from an interactive menu (in terminals) - **Tab completion** - Shell completion works with both paths and component names - **Error handling** - Clear error messages when paths can't be resolved - **CI/CD friendly** - Gracefully handles non-interactive environments with appropriate errors ## Examples ### Terraform Commands ```bash # Current directory cd components/terraform/vpc atmos terraform plan . --stack dev # Relative path from repo root atmos terraform apply ./components/terraform/vpc --stack prod # Relative path to sibling component cd components/terraform/vpc atmos terraform output ../eks --stack staging ``` ### Describe Component ```bash # Current directory with options cd components/terraform/infra/vpc atmos describe component . --stack dev --format json # Relative path atmos describe component ./components/terraform/rds --stack dev --provenance # Absolute path atmos describe component /Users/dev/myproject/components/terraform/vpc --stack prod ``` ### Validate Component ```bash # Current directory cd components/terraform/vpc atmos validate component . --stack dev # Relative path with schema validation atmos validate component ./components/terraform/eks --stack dev --schema-path eks-schema.json --schema-type jsonschema ``` ### Helmfile Commands ```bash # Current directory cd components/helmfile/my-app atmos helmfile diff . --stack dev # Relative path to another helmfile component atmos helmfile apply ../nginx-ingress --stack prod ``` ## Handling Multiple Component Instances When multiple components in a stack reference the same filesystem path, Atmos provides an interactive selection menu in terminal environments. **Example scenario with multiple instances:** ```yaml # stacks/deploy/dev.yaml components: terraform: station/1: metadata: component: weather # Points to components/terraform/weather vars: name: station-1 station/2: metadata: component: weather # Also points to components/terraform/weather vars: name: station-2 ``` ### Interactive Selection (Terminal) When you're in an interactive terminal (like your local command line), Atmos presents a selection menu: ```bash cd components/terraform/weather atmos terraform plan . --stack dev Component path 'weather' matches multiple instances in stack 'dev' Select which component instance to use (ctrl+c to cancel) ❯ station/1 station/2 ``` Use arrow keys to select the component instance you want, press Enter to confirm, or Ctrl+C/Esc to cancel. ### Non-Interactive Environments (CI/CD) In non-interactive environments like CI/CD pipelines or when output is piped, Atmos returns an error with all matching component names: ```bash cd components/terraform/weather atmos terraform plan . --stack dev | tee log.txt Error: ambiguous component path Path resolves to 'weather' which is referenced by multiple components in stack 'dev' Matching components: station/1, station/2 Hint: Use the exact component name instead of a path Example: atmos terraform plan station/1 --stack dev ``` You can always use the explicit component name in any environment: ```bash atmos terraform plan station/1 --stack dev # ✓ Explicit and unambiguous atmos terraform plan station/2 --stack dev # ✓ Explicit and unambiguous ``` ## Backward Compatibility All existing component name syntax continues to work unchanged: ```bash # Traditional component names still work atmos terraform plan vpc --stack dev atmos terraform plan infra/vpc --stack dev ``` ## Requirements - Must be inside a component directory under the configured base path - Must specify `--stack` flag when using paths - Component must exist in the specified stack configuration - **Multiple component instances**: If multiple components reference the same path, Atmos will: - In interactive terminals: Show a selection menu to choose which instance - In CI/CD or piped output: Return an error requiring an explicit component name ## Documentation For more details, see: - [Terraform Commands](/cli/commands/terraform/usage) - [Describe Component](/cli/commands/describe/component) - [Validate Component](/cli/commands/validate/component) For usage and configuration, see [Terraform Stack Configuration](/components/terraform/stack-config). --- ## Path-Based Custom Commands Atmos custom commands now support path-based names. Instead of writing a deeply nested command tree just to describe a command path, you can put the full command path in `name`. The result is easier to read, easier to review, and keeps the same recursive merge behavior that existing custom commands rely on. ## What Changed Before, command hierarchies had to be expressed as nested `commands` arrays: ```yaml commands: - name: casts commands: - name: generate commands: - name: examples commands: - name: custom-commands commands: - name: hello-greet description: Regenerate the Custom Commands example cast steps: - type: shell command: atmos casts setup ``` That shape is programmatically consistent, but it is not very friendly when the nesting only exists to spell out the command path. Now you can write the same command as: ```yaml commands: - name: casts generate examples custom-commands hello-greet description: Regenerate the Custom Commands example cast steps: - type: shell command: atmos casts setup ``` Atmos expands the path-based name into the normal recursive command tree before merging configuration. ## Merge Behavior Is Preserved This is syntax sugar, not a new command model. A command named `casts generate demo` is normalized to the same internal structure as: ```yaml commands: - name: casts commands: - name: generate commands: - name: demo ``` Because normalization happens before config merge and unmarshal, later configs and imports still override matching command fields by segment name. Nested `commands` arrays still merge recursively, and sibling commands that share a prefix still collapse into one command tree. That means you can define a shared branch in one config: ```yaml commands: - name: casts generate description: Generate casts ``` And add leaves elsewhere: ```yaml commands: - name: casts generate examples sops-secrets secret-lifecycle description: Regenerate the SOPS secrets example cast ``` Atmos treats both definitions as part of the same `casts -> generate` command branch. ## When to Use It Use path-based names when the intermediate command levels do not carry their own behavior. This is ideal for demo generation, validation commands, and operational command groups where the path is the structure. Keep the nested form when an intermediate command needs its own description, flags, environment, steps, or child-specific organization that is clearer when written explicitly. Both forms can coexist. The important rule is simple: spaces in `commands[].name` are treated as command path separators, so literal spaces inside one command segment are not supported. For usage and configuration, see [steps](/cli/configuration/commands/steps). --- ## Test PR Features with --use-version Test features from any Atmos pull request or commit SHA without compiling from source or manually downloading artifacts. ## What's New The `--use-version` flag now accepts PR numbers and commit SHAs, making it trivial to test features from any open pull request or specific commit: ```bash # Test a feature from PR #2040 atmos --use-version pr:2040 terraform plan # Numbers are auto-detected as PRs atmos --use-version 2040 describe stacks # Test a specific commit by SHA atmos --use-version sha:ceb7526 version # SHAs are auto-detected (7-40 hex chars) atmos --use-version ceb7526be terraform plan ``` ## How It Works When you specify a PR number or SHA, Atmos: 1. **Fetches the artifact** - Downloads the correct platform binary (Linux, macOS, Windows) from GitHub Actions 2. **Caches locally** - Stores the binary in `~/.local/share/atmos/toolchain/bin/` with metadata for fast subsequent runs 3. **Re-executes** - Runs your command using the specified version of Atmos For PR versions, the cache uses a 1-minute TTL before checking for new commits, so repeated runs within that window are instant. For SHA versions, the cache is permanent since commits are immutable - once installed, a SHA version is always ready to use. ## Clear Error Messages When things go wrong, you get actionable guidance: - **Invalid version format**: Clear message showing valid formats (PR number, `pr:NNNN`, `sha:XXXXXXX`, `ref:`, or semver) - **Missing GitHub token**: Instructions for authenticating via `gh` CLI or environment variable - **PR/commit not found**: Direct link to the PR or commit page - **CI workflow failing**: Explanation of why artifacts might be missing - **Unsupported platform**: Suggests alternatives ## Requirements PR artifact installation requires GitHub authentication. The feature checks for tokens in this order: 1. `ATMOS_GITHUB_TOKEN` environment variable 2. `GITHUB_TOKEN` environment variable 3. GitHub CLI (`gh`) authentication ## Getting Started ```bash # Authenticate with GitHub CLI (if not already) gh auth login # Test a PR atmos --use-version pr:2040 version # Test a specific commit atmos --use-version sha:ceb7526 version ``` This feature complements our existing PR feature releases infrastructure, making it even easier to validate changes before they merge. --- ## Server-Side Commits via Atmos Pro GitHub App Atmos now supports **server-side commits** via the Atmos Pro GitHub App. The new [`atmos pro commit`](/cli/commands/pro/commit) command sends your changes to Atmos Pro, which creates the commit using its GitHub App installation — ensuring commits trigger CI workflows. ## Why This Matters When running in GitHub Actions, commits made with `GITHUB_TOKEN` do not trigger subsequent workflow runs. This is a deliberate GitHub limitation to prevent infinite loops, but it blocks common autofix patterns like running `tofu fmt` and committing the result. Previously, teams worked around this by minting PATs (insecure and static), creating GitHub App tokens (tedious, and secrets need to be saved somewhere), or using dedicated services like [autofix.ci](https://autofix.ci/). Now this capability is built directly into Atmos. ## How It Works 1. Your workflow makes changes (e.g., [`atmos toolchain exec -- tofu fmt -recursive`](/cli/commands/toolchain/exec)) 2. `atmos pro commit` detects staged changes, base64-encodes file contents, and authenticates via GitHub OIDC 3. Atmos Pro creates the commit server-side using its GitHub App 4. Because the commit comes from the app (not `GITHUB_TOKEN`), GitHub triggers CI normally The workflow never receives a write token — Atmos Pro controls exactly what gets committed. **Built-in loop prevention:** The command automatically detects when it's running in a workflow triggered by `atmos-pro[bot]` and exits early. No workflow guards needed — though you can optionally add `if: github.actor != 'atmos-pro[bot]'` to skip the entire job for efficiency. ## Quick Start ```yaml name: autocommit on: pull_request permissions: contents: read id-token: write jobs: format: runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:1.214.0 steps: - uses: actions/checkout@v6 - run: atmos toolchain exec -- tofu fmt -recursive components/terraform/ - run: atmos pro commit -m "[autocommit] formatting fixes" --all ``` ## Staging Options Control what gets committed with flexible staging flags: ```bash # Commit whatever is already staged atmos pro commit -m "formatting fixes" # Stage everything first atmos pro commit -m "formatting fixes" --all # Stage only specific files atmos pro commit -m "formatting fixes" --add "*.tf" ``` ## Safety - `.github/` paths are rejected to prevent workflow injection - File size limit of 2 MiB per file (larger files are skipped with a warning) - Maximum 200 changed files per commit (enforced in [`pkg/pro/commit.go`](https://github.com/cloudposse/atmos/blob/main/pkg/pro/commit.go)) - Atmos Pro RBAC required for commits and scoped to branches, workflows, and GitHub environments - Branch is validated against the OIDC JWT's `head_ref` claim server-side **Get Started** Read the full CLI reference for `atmos pro commit`. [Read more](/cli/commands/pro/commit) --- ## See Every Atmos Command That Ran in CI, Without Parsing a Single Log A pipeline runs a dozen Atmos commands across a dozen jobs. Something looks off — a plan that should have shown changes didn't, a command that used to take seconds now takes minutes. The only way to find out what actually happened is to open each CI job, scroll through raw log output, and reconstruct the timeline by hand. There was no automatic record of what ran, how long it took, or how much it cost. ## The Problem CI logs are the system of record for infrastructure changes, but they're not built for review. Finding "which command ran against which stack, and did it succeed" means opening job after job and reading output meant for a terminal, not a dashboard. Resource usage — how long a command actually took, how much memory it needed — was never captured at all, so slow pipelines were diagnosed by guesswork. ## The Fix When Atmos runs in a recognized CI environment with Atmos Pro configured, it now reports a lightweight execution record for every command automatically — no extra setup beyond what you already have. The record covers the Atmos version, the command and its exit code, and how long it took to run. Supported platforms also report CPU time and maximum resident memory for the Atmos process itself. These measurements exclude Terraform/OpenTofu and provider subprocesses and do not represent peak CPU usage or whole-job memory. See [Atmos Pro configuration](/cli/configuration/settings/pro) for integration setup. Most commands report this in the background and never slow anything down. `terraform plan`, `terraform apply`, and `describe affected` wait up to the configured timeout to confirm the record made it to Atmos Pro before the command finishes — and if delivery is slow or fails, the command still completes; it just logs a warning so a missed record doesn't disappear silently. ## How to Use It There's nothing to turn on. If Atmos Pro is already configured and Atmos is running in a recognized CI environment with the usual CI prerequisites in place, [execution records](/cli/configuration/settings/pro) start flowing immediately — not every CI provider is supported yet. The only knob is how long a critical command (`terraform plan`, `terraform apply`, `describe affected`) is willing to wait for Atmos Pro to confirm it received the record before moving on — 10 seconds by default: ```yaml settings: pro: exec: sync_timeout: 20s ``` Raise it if your network to Atmos Pro is slow; the default already covers most setups. ## Get Involved This is the foundation for richer execution history in Atmos Pro — itemized resource changes from `terraform plan`/`apply` are next. Tell us what you'd want to see in an execution's timeline. Open an issue or start a discussion at [github.com/cloudposse/atmos](https://github.com/cloudposse/atmos). --- ## Atmos Pro: Instances API Migration to Query Parameters Updated the Atmos Pro integration to use query parameters for the instances API endpoint, fixing issues with stack and component names containing slashes and improving API compatibility. ## What Changed The Atmos CLI now uses query parameters instead of path parameters when communicating with the Atmos Pro instances API. **Before:** ``` /api/v1/repos/{owner}/{repo}/instances/{stack}/{component} ``` **After:** ``` /api/v1/repos/{owner}/{repo}/instances?stack={stack}&component={component} ``` ## Why This Change? This update aligns with changes made to the Atmos Pro API that provide several important improvements: ### Fixed 500 Errors for Special Characters Previously, component names containing slashes would cause 500 errors. For example: - Component: `eks/cluster` These names would create ambiguous URL paths that the API couldn't parse correctly. With query parameters, these names are properly URL-encoded and handled without errors. ### Backward Compatibility The old path-based endpoint remains available on the Atmos Pro side for backward compatibility, with deprecation headers. This ensures smooth migration for all users. ### Query Parameter Benefits Query parameters provide: - **Proper encoding**: Special characters like slashes are correctly URL-encoded - **Clear structure**: No ambiguity about where the stack name ends and component name begins - **API consistency**: Matches common REST API patterns for filtering and querying ## Impact on Users This change is **transparent to end users**. If you're using Atmos Pro features: - ✅ No configuration changes required - ✅ No workflow changes needed - ✅ Works with both old and new Atmos Pro API versions - ✅ Automatically handles special characters in stack/component names ## Technical Details The change updates the `UploadInstanceStatus` function in the Atmos Pro API client to use proper URL query parameter encoding: ```go // Use query parameters for stack and component targetURL := fmt.Sprintf("%s/%s/repos/%s/%s/instances?stack=%s&component=%s", c.BaseURL, c.BaseAPIEndpoint, url.PathEscape(dto.RepoOwner), url.PathEscape(dto.RepoName), url.QueryEscape(dto.Stack), // Query param encoding url.QueryEscape(dto.Component)) // Query param encoding ``` The key change is using `url.QueryEscape()` instead of `url.PathEscape()` for stack and component values, ensuring proper encoding for use as query parameters. ## References - [Atmos Pro Documentation](https://atmos-pro.com/docs) --- This change improves reliability for users with complex naming conventions and ensures the Atmos CLI stays in sync with the latest Atmos Pro API improvements. For usage and configuration, see [atmos list instances](/cli/commands/list/list-instances). --- ## Atmos Pro now reports check status on GitHub merge queue commits [`atmos describe affected --upload`](/cli/commands/describe/affected) now works under `GITHUB_EVENT_NAME=merge_group`, so Atmos Pro can correctly conclude check runs on the synthetic commits GitHub creates when a PR enters a [merge queue](/cli/configuration/settings/pro#merge-queue-support). To control what runs on those synthetic commits, declare a new `settings.pro.merge_group.checks_requested.workflows` block in your stack config and point it at the workflow you want the queue to dispatch (in most cases, the same plan workflow you already use for `pull_request.synchronize`). ## What Changed Until now, the CLI rejected `merge_group` events at the `--upload` boundary, and Atmos Pro had no way to correlate the synthetic merge-queue commits with stack-affected results. Required "Atmos Pro" checks would stay in **Expected — Waiting for status to be reported** until a delayed reconciler swept them. This release lifts that guard and makes the CLI fully aware of merge-queue events: - `atmos describe affected --upload` accepts `GITHUB_EVENT_NAME=merge_group` alongside `pull_request` and `pull_request_target`. - The diff base is resolved from `event.merge_group.base_sha` (the target-branch commit the synthetic commit was merged on top of), with `merge_group.head_sha` and `merge_group.base_ref` used for upload correlation and target-branch derivation. - The full `settings.pro.merge_group` schema rounds-trips through the upload payload, so the new optional `merge_group.checks_requested.workflows` block reaches Atmos Pro intact. On the Atmos Pro side, check-suite webhooks filtered on `head_branch` matching `gh-readonly-queue//pr--` now drive a full check-run lifecycle: create on the synthetic SHA, watch for the customer's workflow to complete, and conclude (success / failure / no-affected-stacks neutral). Comment updates are posted to the originating PR so queue check resolution shows up on the same thread. ## Why This Matters GitHub merge queues add a second SHA to every PR's lifecycle: the PR head commit, then a synthetic merge commit when the PR enters the queue. Required status checks must report against both. Before this release, customers using merge queues with Atmos Pro saw the queue check run hang — there was no way for the CLI to upload the affected stacks against the synthetic SHA. This release closes that gap so a PR added to a merge queue gets the same affected-stacks correlation it gets on the PR itself. Workflow filenames in `settings.pro` are arbitrary, so Atmos Pro cannot tell from a filename alone whether a workflow plans, applies, or does something else. Rather than infer queue behavior from your existing `pull_request.synchronize` block, declare what you want the queue to dispatch explicitly. That keeps the queue's check outcome deterministic and reviewable from stack config. ## How to Use It ### Declare a `merge_group` block Add a `merge_group` block alongside `pull_request` and point it at the workflow you want dispatched on merge-queue synthetic commits. In most cases that is the same plan workflow you already use for `pull_request.synchronize`, optionally with stricter inputs (fail-on-drift, required approvals, etc.): ```yaml settings: pro: enabled: true pull_request: synchronize: workflows: atmos-terraform-plan.yaml: inputs: component: "{{ .atmos_component }}" stack: "{{ .atmos_stack }}" merge_group: checks_requested: workflows: atmos-terraform-plan.yaml: inputs: component: "{{ .atmos_component }}" stack: "{{ .atmos_stack }}" fail_on_drift: "true" ``` `merge_group` is a sibling of `pull_request` / `release` / `drift_detection` (not nested under `pull_request`). The only meaningful activity today is `checks_requested`. Apply continues to fire on `pull_request.merged` — not on `merge_group` events. ### Resolution order When Atmos Pro receives a merge-queue trigger for a stack, it picks the workflow to dispatch in this order: 1. `settings.pro.merge_group.checks_requested.workflows` if defined — **the recommended path.** 2. Otherwise, `settings.pro.pull_request.synchronize.workflows` is used as a backstop so existing customers do not regress when a queue is enabled. Because workflow filenames are arbitrary, this can dispatch a workflow that is not appropriate for the queue; treat it as transitional, not as a recommended configuration. 3. Otherwise, no workflow is dispatched. ## Get Involved See the [full configuration reference](/cli/configuration/settings/pro#merge-queue-support) for the schema, prerequisites, and migration notes. GitHub's docs on [managing a merge queue](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue) are a good starting point if you have not enabled queues yet. --- ## Introducing the Atmos Product Roadmap We're sharing the [Atmos Product Roadmap](/roadmap)—a transparent view of where we've been, where we're headed, and what's coming next. Infrastructure teams evaluating Atmos often ask _"What's the long-term vision?"_ The [roadmap](/roadmap) answers that question openly. ## What's on the Roadmap The page is organized around a visual quarter timeline from Q1 2025 through Q2 2026. Featured improvement cards highlight major capabilities—click any card to see details, documentation links, and associated PRs. Below that, expandable initiative cards group milestones by theme, with an animated progress bar tracking overall completion. ## Get Involved The roadmap isn't just for viewing. Here's how you can shape it: - **Star issues** on GitHub that matter to you - **Open feature requests** for capabilities you need - **Contribute directly** to any roadmap item ## Learn More - [View the Roadmap](/roadmap) - Browse everything we've shipped and what's planned - [GitHub Issues](https://github.com/cloudposse/atmos/issues) - Vote on features or request new ones --- ## Profiles Can Now Import Shared Configuration Auth profiles and team defaults are often managed centrally, but workload or app repositories still need to use them. Until now, that usually meant copying profile YAML into each repository and keeping those copies in sync by hand. ## The Problem Many teams keep authentication, identity, terminal, and CI defaults in a central infrastructure repository. That works well for governance, but it creates friction for workload or app repositories: every repo needs the same profile definitions, and every update has to be copied everywhere. That duplication is especially painful for auth profiles. The central platform team owns the provider and identity shape, while each app repository only wants to consume the right profile and add a small local override. ## The Change Profile config files can now use `import:`, including remote imports. That means a profile in a workload or app repository can pull shared configuration from a central repository and then override only the local pieces it owns. ```yaml title="profiles/developer/atmos.yaml" import: - "github.com/my-org/platform-atmos//profiles/shared-auth.yaml?ref=v1.4.0" logs: level: Debug settings: terminal: max_width: 140 ``` The imported file provides the baseline. Values in the local profile are merged over it, so the workload repository can keep its local preferences without forking the shared auth profile. ## Why It Matters - **Centralize auth profiles.** Platform teams can publish shared provider and identity configuration once. - **Keep app repos small.** Workload or app repositories can consume the shared profile instead of duplicating it. - **Override locally when needed.** Imported profile configuration is still just layered Atmos configuration, so local profile values win. ## Get Involved See the [profiles documentation](/cli/configuration/profiles) for profile discovery, activation, and merge behavior. --- ## Configuration Provenance Tracking: Know Where Every Value Comes From We've shipped a feature that developers working with complex infrastructure configurations have been asking for: **[provenance tracking](/cli/commands/describe/component#flags)**. With the new [`--provenance`](/cli/commands/describe/component#flags) flag in `atmos describe component`, you can now see exactly where every configuration value originated—down to the file, line number, and column. ## The Problem: Configuration Archaeology If you've worked with Atmos's hierarchical stack configurations, you know the power of inheritance and deep merging. But that power comes with a challenge: when you see a configuration value, where did it actually come from? Previously, if you saw an unexpected value like `cidr: "10.100.0.0/16"`, you'd have to: - Mentally trace through import chains - Open multiple YAML files - Grep through your stack configurations - Reconstruct the merge order in your head The existing `sources` system showed top-level keys, but couldn't tell you about nested values, array elements, or map entries. And it didn't include line numbers—just file paths. ## The Solution: Line-Level Provenance The new provenance tracking system records the exact source of **every value** in your component configuration. Not just top-level keys—every nested field, every array element, every map entry. ### Basic Usage ```bash atmos describe component vpc -s prod-ue2 --provenance ``` This displays your component configuration with inline comments showing where each value originated: ```shell # Provenance Legend: # ● [1] Defined in parent stack # ○ [N] Inherited/imported (N=2+ levels deep) # ∴ Computed/templated import: # ○ [2] orgs/acme/_defaults.yaml:2 - catalog/vpc/defaults # ○ [2] orgs/acme/_defaults.yaml:2 - mixins/region/us-east-2 # ● [1] orgs/acme/prod/us-east-2.yaml:3 - orgs/acme/_defaults # ○ [2] orgs/acme/prod/_defaults.yaml:2 vars: # ○ [3] catalog/vpc/defaults.yaml:8 cidr: "10.100.0.0/16" # ● [1] orgs/acme/prod/us-east-2.yaml:10 name: vpc # ○ [3] catalog/vpc/defaults.yaml:9 region: us-east-2 # ○ [2] mixins/region/us-east-2.yaml:2 namespace: acme # ○ [3] orgs/acme/_defaults.yaml:2 ``` ### Understanding the Symbols Provenance uses three symbols to show how values were defined: - **● (black circle)** - Defined in the parent stack `[1]` - **○ (white circle)** - Inherited/imported `[N]` where N indicates depth in the import chain - **∴ (therefore)** - Computed/templated value The depth indicator `[N]` tells you how many levels deep in the import chain a value came from: - `[1]` - Defined directly in the stack you're describing - `[2]` - Inherited from a first-level import - `[3+]` - Inherited from deeper in the import chain When displayed on a TTY, the depth indicators are color-coded: - Cyan for depth 1 (parent stack) - Green for depth 2 (first import) - Orange for depth 3 (second import) - Red for depth 4+ (deeper imports) ## Real-World Use Cases ### 1. Debugging Unexpected Values ```bash $ atmos describe component vpc -s prod-ue2 --provenance | grep cidr cidr: "10.100.0.0/16" # ● [1] orgs/acme/prod/us-east-2.yaml:10 ``` Instantly see that the CIDR is defined in the parent stack at line 10. No more guessing. ### 2. Understanding Inheritance Chains For complex configurations with multiple inheritance levels, provenance shows the complete picture. Use `grep` to find specific values: ```bash $ atmos describe component app -s staging-uw2 --provenance | grep replicas replicas: 3 # ○ [2] catalog/app/defaults.yaml:15 ``` You can see that `replicas` is inherited from a catalog default at depth 2. ### 3. Auditing Security Settings For compliance and security audits, verify where sensitive configurations originated: ```bash $ atmos describe component bastion -s prod-ue2 --provenance | grep -A5 security security: # ○ [2] catalog/bastion/defaults.yaml:20 allowed_cidr_blocks: # ● [1] stacks/prod/security.yaml:8 - "10.0.0.0/8" # ● [1] stacks/prod/security.yaml:9 - "172.16.0.0/12" # ● [1] stacks/prod/security.yaml:10 ``` Confirm all security settings come from approved configuration files. ### 4. Pipe-able Output for Automation The YAML output with provenance is still valid YAML, so it works with standard tools: ```bash $ atmos describe component vpc -s prod-ue2 --provenance | yq '.vars.cidr' 10.100.0.0/16 ``` Comments are preserved for human review while the data remains machine-parseable. :::note The [`--query`](/cli/commands/describe/component#flags) flag is not supported with `--provenance`. To filter provenance output, pipe it through tools like `grep`, `yq`, or `jq`. ::: ## Terminal vs Non-Terminal Output Provenance adapts to your environment: **On TTY (interactive terminal):** - Two-column side-by-side layout (Configuration │ Provenance) - Color-coded depth indicators - Syntax highlighting - Visual symbols **Non-TTY (pipes, CI/CD):** - Single-column layout (preserves valid YAML) - Inline comments without color codes - Plain text symbols - Optimized for scripting ## How It Works Provenance tracking follows your configuration through its entire journey: As Atmos processes your stacks, it reads each YAML file and tracks every value it encounters. When it reads a line like `cidr: "10.100.0.0/16"` from `orgs/acme/prod/us-east-2.yaml`, it records: "I saw this value at line 10, column 3 in this file." When Atmos imports another file, it remembers where it came from and how deep in the import chain it is. If `_defaults.yaml` imports `mixins/region.yaml`, and that defines `region: us-east-2`, Atmos tracks that this value came from two levels deep in the import chain. As configurations merge together—when a child stack overrides a parent's value, or when imports stack on top of each other—Atmos remembers each step. It knows which file provided the final value, but also which files were overridden along the way. Finally, when you run `atmos describe component --provenance`, it annotates the output with all this information: which file, which line, how deep in the import chain, and whether the value was defined directly, inherited, or computed from a template. Performance overhead is minimal—less than 10% when provenance tracking is enabled, and zero overhead when disabled (the default). ## Get Started Provenance tracking is available now in Atmos. To try it: ```bash # View provenance for any component atmos describe component -s --provenance # Save to file atmos describe component vpc -s prod --provenance --file vpc-config.yaml # Find specific values with grep atmos describe component vpc -s prod --provenance | grep cidr # JSON format atmos describe component vpc -s prod --provenance --format json ``` For complete documentation, see [atmos describe component](/cli/commands/describe/component). ## Future Enhancements The provenance system is built on an extensible interface that will enable: - **atmos.yaml provenance** - Track where Atmos configuration settings come from - **Vendor provenance** - Show origins of vendored components - **Workflow provenance** - Track workflow step origins - **IDE integration** - Hover-to-see-provenance in editors - **Diff mode** - Show provenance changes between versions We'd love to hear how you're using provenance tracking. Share your use cases in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) or open an issue if you find any bugs. --- ## New !random YAML Function: Generate Random Values in Your Configurations Need to generate random port numbers, worker IDs, or other numeric values in your Atmos configurations? The new [`!random`](/functions/yaml/random) YAML function makes it easy. ## What's New We've added a new `!random` YAML function that generates cryptographically secure random integers. Use it anywhere in your Atmos stack manifests: ```yaml vars: app_port: !random 49152 65535 # Random ephemeral port worker_id: !random 1000 9999 # Random worker ID ``` The function accepts 0, 1, or 2 arguments: - `!random` - Random number 0-65535 - `!random 100` - Random number 0-100 - `!random 1024 65535` - Random number 1024-65535 ## Common Use Cases ### DevContainer Port Forwarding Avoid port conflicts when running multiple devcontainer instances: ```yaml components: devcontainer: backend-api: spec: image: mcr.microsoft.com/devcontainers/go:1.24 forwardPorts: - !random 8000 9000 - !random 5432 5442 ``` ### Dynamic Configuration Generate unique identifiers and random values for testing: ```yaml vars: worker_id: !random 1000 9999 test_port: !random 20000 29999 backend_ports: api: !random 10000 10999 metrics: !random 11000 11999 ``` ## Resources - [`!random` Function Documentation](/functions/yaml/random) - [YAML Functions Overview](/functions/yaml) - [DevContainer Documentation](/cli/commands/devcontainer) --- ## Remote Imports: Automatic GitHub Auth, Failure Warnings, and Caching Centralizing shared configuration in one private Git repository, and reusing it across projects, is not a new idea. Mergify and Dependabot both support extending a project's configuration from a shared repository. Doing the same for a general-purpose tool usually means a git submodule or subtree, and both are cumbersome. Atmos supports this natively: a project's `atmos.yaml` or stack manifest adds a remote import, a `git::` URL that points at the centralized repository, with no submodule or subtree involved. Resolving that import still had rough edges. Git fetch authentication was separate from a developer's GitHub CLI session, so `gh auth login` alone was not enough for a private import. A broken import failed silently: a tag name with a typo, an unreachable host, or a token without read access all added nothing to the configuration, with no warning. And a `git::` import with a subdirectory re-cloned on every single command, even when nothing had changed. Atmos already reused a developer's GitHub CLI session for other GitHub operations, such as toolchain installs. Atmos now reuses that same session for private `git::` imports too. This applies to both stack configuration imports and `atmos.yaml` configuration imports. Atmos also warns you when an import fails. Atmos also lets you cache imports to avoid unnecessary re-fetching. ## The Problem - **Git fetch authentication was separate from GitHub CLI authentication.** A private `git::` import used its own credential chain. This chain did not include the GitHub CLI. A developer who ran only `gh auth login` still needed a separate token for private imports to work. - **A broken import failed with no warning.** A typo in a `?ref=` value, an unreachable host, or an unreadable private repository all produced the same result. The import added nothing to the configuration. No error appeared. The command exited successfully. - **Every command re-cloned a `git::` import.** A root `atmos.yaml` import that pointed at a subdirectory in a Git repository had no cache. A command as simple as checking the current identity re-cloned the remote repository first. ## The Fix - Private `git::` imports now fall back to a developer's GitHub CLI session automatically. This covers config imports, vendoring, and private Terraform module fetches. Atmos already used this fallback for other GitHub operations. If `gh auth login` is already done, no other setup is needed. - A remote `import:` entry that fails to resolve now prints a warning by default. The warning names the import path and the underlying error. The command no longer continues silently with an empty or partial configuration. - Set `imports: { ttl: ... }` once in `atmos.yaml` to apply the same expiry policy to every remote import form. A `git::` import with a subdirectory then reuses its clone across commands instead of re-cloning every time. A plain remote URL, or a `git::` import without a subdirectory, then expires after `ttl` instead of being cached forever. The two forms track freshness differently under the hood (a marker file in the cloned directory for the first, a cache entry for the second), but `ttl` now governs both. ## How to Use It Sign in once. Private imports then work automatically: ```bash gh auth login ``` If an import breaks, Atmos reports it immediately: ```text WARN failed to resolve import path="git::https://github.com/acme/config.git//auth.yaml?ref=v1.2.3" error="..." ``` Set a cache TTL to stop re-fetching a stable, pinned `git::` import on every command: ```yaml title="atmos.yaml" imports: ttl: 5m import: - "git::https://github.com/acme/config.git//auth.yaml?ref=v1.2.3" ``` Leave `ttl` unset to keep each import form's default: a `git::` subdirectory import refreshes on every command, and a plain remote URL is cached forever. Set `ttl` once to apply the same expiry to every remote import in the file, of either form. For usage and configuration, see [Imports](/cli/configuration/imports). ## Get Involved Did you find another case where a remote import fails silently? Did you find an operation where GitHub CLI auth does not reach, but should? Open an issue. Include the command and the result you saw. --- ## Remote Stack Imports Atmos now supports importing stack configurations from remote URLs. Reference shared configurations from GitHub, S3, GCS, or any HTTP endpoint directly in your stack files. ## What Changed Stack imports now support remote URLs alongside local file paths. The import syntax remains the same, but Atmos detects remote URLs and downloads them automatically using go-getter: ```yaml import: # Local import - works as before - catalog/base # Remote imports - NEW - https://raw.githubusercontent.com/cloudposse/atmos/main/stacks/catalog/shared.yaml - github.com/myorg/shared-config//stacks/defaults.yaml?ref=v1.0.0 - s3::https://s3.amazonaws.com/my-bucket/configs/base.yaml ``` ## Why This Matters Teams often need to share stack configurations across multiple repositories or organizations. Previously, this required vendoring shared configs or maintaining copies in each repository. Now you can reference shared configurations directly from their source: - **Central catalogs**: Maintain organization-wide defaults in a single repository - **Version pinning**: Reference specific versions with Git refs (`?ref=v1.0.0`) - **Cross-team sharing**: Import configurations from other teams without duplication - **External standards**: Pull in compliance or security baselines from external sources ## Supported URL Formats Remote imports leverage [go-getter](https://github.com/hashicorp/go-getter) for downloading. Here are some common formats: | Format | Example | |--------|---------| | HTTP | `http://example.com/config.yaml` | | HTTPS | `https://example.com/config.yaml` | | GitHub | `github.com/org/repo//path/to/file.yaml` | | Git with ref | `github.com/org/repo//path?ref=v1.0.0` | | S3 | `s3::https://s3.amazonaws.com/bucket/key.yaml` | | GCS | `gcs::gs://bucket/path/config.yaml` | | Git SSH | `git@github.com:org/repo.git//path/to/file.yaml` | See the [go-getter documentation](https://github.com/hashicorp/go-getter#url-format) for additional supported formats. ## How to Use It ### Basic Remote Import Reference a remote configuration directly: ```yaml # stacks/deploy/production.yaml import: - https://raw.githubusercontent.com/myorg/shared-configs/main/base.yaml vars: environment: production components: terraform: vpc: vars: cidr_block: "10.0.0.0/16" ``` ### Version-Pinned Imports Pin imports to specific Git refs for reproducibility: ```yaml import: # Pin to a specific tag - github.com/myorg/shared-configs//catalog/defaults.yaml?ref=v2.1.0 # Pin to a specific commit - github.com/myorg/shared-configs//catalog/security.yaml?ref=abc123 ``` ### Skip Missing Remote Imports Use `skip_if_missing` for optional remote configurations: ```yaml import: - path: "https://internal.example.com/optional-overrides.yaml" skip_if_missing: true ``` ## Example We've added a complete example demonstrating remote stack imports: ```bash cd examples/remote-stack-imports atmos describe stacks ``` Here's the remote import resolving end to end: [View the full example](/examples/remote-stack-imports) The example shows both local and remote imports working together, with proper configuration merging and inheritance. :::note This feature was previously documented but not yet implemented. We apologize for this oversight. Remote stack imports now work as documented, exactly like [remote imports for atmos.yaml](/cli/configuration/imports). ::: ## Get Involved We'd love to hear how you're using remote stack imports. Please [open an issue](https://github.com/cloudposse/atmos/issues) if you have questions or encounter edge cases. For more details, see the [Stack Imports](/stacks/imports) documentation. --- ## Check Workflow Prerequisites with require and assert Workflows often depend on tools, files, and directories being present before they run. Without a first-class check, those prerequisites tend to hide inside brittle shell snippets and ad hoc command checks. ## The Problem Shell checks work until they have to be portable, readable, and friendly. A workflow might need `vhs`, `ffmpeg`, a generated config file, and a local output directory before the real work starts. Encoding that as inline shell makes the workflow harder to scan and usually produces a poor error when something is missing. ## The Change Atmos now has a declarative `require` step type for workflow and custom command prerequisites. The `assert` step type is an alias for the same behavior. ```yaml steps: - name: require recording tools type: require tools: - vhs - ffmpeg files: - ./Taskfile.yml dirs: - ./demo hint: "on macOS run: brew install vhs ffmpeg" ``` The step checks that tools are executable on `PATH`, files exist, and directories exist. If anything is missing, Atmos fails fast with one aggregated error and the remediation hint. ## Why It Matters - **Prerequisites are visible.** The workflow declares what it needs before the work starts. - **Errors are actionable.** Missing tools and paths are reported together with a hint. - **It is read-only.** `require` never installs tools and never mutates `PATH`; use `dependencies.tools` when you want Atmos to manage tool installation. ## Get Involved See the [`require` step reference](/workflows/steps/type/require) for all supported fields and examples. --- ## Component retry now covers Helmfile, Packer, and Ansible too Terraform components already recover from a transient error automatically — a 502, a dropped registry connection, an S3 backend timeout. Helmfile, Packer, and Ansible components in that same pipeline did not. The exact same class of failure that Terraform shrugged off would still fail your Helmfile sync or Ansible playbook outright, because a `retry:` block on either was silently inert. Now all four component types share the same retry engine. ## The Problem The same class of error can just as easily hit `helmfile sync` pulling a chart, `packer build` downloading a plugin, or `ansible-playbook` reaching a remote inventory. None of those got the same protection until now. That gap got harder to miss once stacks could set retry once for the whole stack. A stack-root `retry:` block reads as "every component here recovers from this." But that was only true for the Terraform components. ## The Fix `retry:` now works the same way for every component type that shells out to a binary. Terraform, Helmfile, Packer, and Ansible all share the same retry engine. It captures the subprocess output, matches it against your `conditions` regex patterns, and retries with backoff only on a match. ```yaml components: helmfile: myapp: retry: max_attempts: 5 backoff_strategy: exponential initial_delay: 2s conditions: - /Bad Gateway/ - /connection reset/ packer: ami: retry: max_attempts: 3 conditions: - /rate limit/ ``` Nothing changes for existing Terraform retry configuration. This change only adds new coverage. A real failure still fails immediately. Examples include a bad Helmfile release, a Packer template error, or a broken playbook. Only output that matches your `conditions` triggers a retry. ## How to Use It Add `retry:` to any Helmfile, Packer, or Ansible component the same way you would for Terraform. See the [component retry docs](/stacks/components/terraform/retry) for the full field reference. A stack-root `retry:` block now protects every supported component in the stack. Native Kubernetes and native Helm components don't shell out to a binary. They call Go SDKs directly. So they aren't covered by this change yet. That gap is tracked as a follow-up. ## Get Involved Open a discussion in the [Atmos repo](https://github.com/cloudposse/atmos). Let us know if you hit a transient failure pattern in Helmfile, Packer, or Ansible that `conditions` doesn't catch cleanly. --- ## Retry Support for Vendoring and Source Operations Atmos now supports automatic retry with exponential backoff for vendoring and source operations. This makes component downloads more resilient to transient network failures, connection resets, and GitHub API rate limits. ## What Changed We've added native retry support to git operations and HTTP downloads used by vendoring and the source provisioner. The retry behavior is configurable per-source using the new `retry` field: ```yaml components: terraform: vpc: source: uri: github.com/cloudposse/terraform-aws-components//modules/vpc version: 1.450.0 retry: max_attempts: 5 initial_delay: 2s max_delay: 60s backoff_strategy: exponential ``` ## Why This Matters Network operations are inherently unreliable. Transient failures like connection resets, timeouts, and rate limits can cause vendoring to fail even when there's nothing wrong with your configuration. Previously, you'd have to manually retry [`atmos vendor pull`](/cli/commands/vendor/pull) or [`atmos terraform source pull`](/cli/commands/terraform/source/pull) when these failures occurred. Now Atmos handles this automatically with intelligent retry logic: - **Exponential backoff** prevents thundering herd problems - **Jitter** adds randomness to avoid synchronized retries across parallel operations - **Smart detection** only retries transient errors (not auth failures or missing repos) - **GitHub rate limit awareness** waits for rate limit reset when limits are hit ## Configuration Options The `retry` field supports the following options: | Field | Default | Description | |-------|---------|-------------| | `max_attempts` | 3 | Maximum number of download attempts | | `initial_delay` | 2s | Initial delay before first retry | | `max_delay` | 30s | Maximum delay between retries | | `backoff_strategy` | exponential | Strategy: `constant`, `linear`, or `exponential` | | `multiplier` | 2.0 | Backoff multiplier for exponential/linear strategies | | `random_jitter` | 0.1 | Randomness added to delays (0.1 = 10%) | | `max_elapsed_time` | 5m | Maximum total time for all retries | ## How to Use It ### In Source Configuration Add `retry` to your component's source configuration: ```yaml components: terraform: vpc: source: uri: github.com/cloudposse/terraform-aws-components//modules/vpc version: 1.450.0 retry: max_attempts: 5 initial_delay: 2s backoff_strategy: exponential ``` ### In Vendor Configuration Add `retry` to your vendor source specifications: ```yaml # vendor.yaml spec: sources: - component: vpc source: github.com/cloudposse/terraform-aws-components//modules/vpc?ref=1.450.0 retry: max_attempts: 5 initial_delay: 2s ``` ### Default Behavior When no `retry` configuration is specified, sensible defaults are used: | Field | Default | |-------|---------| | `max_attempts` | 3 | | `initial_delay` | 2s | | `max_delay` | 30s | | `backoff_strategy` | exponential | | `multiplier` | 2.0 | | `random_jitter` | 0.1 (10%) | | `max_elapsed_time` | 5m | ## Retryable Errors The retry logic automatically detects transient errors including: - Connection reset / connection refused - Timeouts and temporary failures - SSL/TLS handshake errors - GitHub rate limit exceeded (429 responses) - "Remote end hung up unexpectedly" during git operations Non-retryable errors (like authentication failures or repository not found) fail immediately without retry. For usage and configuration, see [Source](/vendor/component-manifest/source). ## Get Involved We'd love to hear your feedback! Please [open an issue](https://github.com/cloudposse/atmos/issues) if you have questions or encounter edge cases. For more details, see the [Source-Based Version Pinning](/design-patterns/version-management/source-based-versioning) design pattern and [vendor configuration](/cli/configuration/vendor). --- ## Safe Logout: Preserve Keychain Credentials by Default `atmos auth logout` now preserves keychain credentials by default for faster re-authentication. Only session data is cleared. Use [`--keychain`](/cli/commands/auth/logout#flags) to permanently delete credentials. ## What Changed **Before:** `atmos auth logout` deleted everything (keychain credentials + session data) **After:** `atmos auth logout` only clears session data, preserves keychain credentials This means you don't have to re-enter access keys every time you log back in. ## Why This Matters Most logouts are temporary (end of day, switching identities), not permanent credential removal. Forcing credential re-entry every time encourages users to skip logout entirely. Now logout is fast and reversible: ```shell # End of day $ atmos auth logout dev-admin ✓ Cleared session data ✓ Preserved keychain credentials # Next morning - instant re-authentication $ atmos auth login dev-admin ✓ Authenticated as dev-admin ``` ## Permanently Delete Credentials Use `--keychain` when you need to remove credentials permanently: ```shell atmos auth logout dev-admin --keychain ``` Interactive confirmation prevents accidents. Use [`--force`](/cli/commands/auth/logout#flags) for CI/CD: ```shell atmos auth logout dev-admin --keychain --force ``` ## Migration If your scripts expect the old behavior (delete everything), add `--keychain`: ```shell atmos auth logout dev-admin --keychain --force ``` For most users, no changes needed. Read more: [atmos auth logout](/cli/commands/auth/logout) --- ## Atmos Can Finally Speak for Itself Workflows now have a `say` step type that speaks a message out loud using text-to-speech — an audible cue for when a long-running workflow finishes or needs your attention, even after you've switched to another window. When no speech engine is available (or you're in CI), it degrades gracefully and prints the message instead, so the same workflow works everywhere. ## The Problem Long-running workflows — a multi-stack `terraform apply`, a build pipeline, a vendoring sweep — often outlast your attention. You kick one off, switch to another window, and forget about it. The existing `alert` step rings the terminal bell, but a bell doesn't tell you _what_ happened, and it's easy to miss. People have worked around this by shelling out to `say` on macOS, but that breaks the moment a teammate runs the same workflow on Linux or in CI, where `say` doesn't exist. ## The Solution A new `say` step speaks its `content` aloud and works across platforms: ```yaml workflows: deploy: steps: - name: apply type: atmos command: terraform apply vpc - name: notify type: say content: "Deployment to {{ .steps.env.value }} is complete" ``` Under the hood it detects an available speech engine per OS — macOS `say`, Linux `spd-say`/`espeak`/`espeak-ng`, and Windows PowerShell's `System.Speech` — so the same workflow runs unchanged on any machine. ### Pick a voice, font-family style Voice names are platform-specific, so `voice` is an **ordered list** — just like a CSS `font-family` stack. The first voice actually installed on the host wins; if none match, the engine's default is used: ```yaml - name: notify type: say content: "Build finished" voice: [Samantha, Microsoft Zira, en-us] # macOS, Windows, Linux rate: normal # slow | normal | fast ``` That single list resolves to Samantha on macOS, Zira on Windows, and `en-us` (espeak) on Linux — no per-OS branching required. ### Graceful degradation you control `say` only makes sense on an interactive workstation, so it never fails a workflow and never hangs CI. The `print` field decides what happens when speech is (or isn't) available: - `fallback` (default) — speak when possible; otherwise print the message as a Markdown blockquote so the information is never lost. - `always` — always print the blockquote **and** also speak when possible. - `never` — speak when possible; otherwise stay silent. In CI, or on any host with no speech engine, `say` automatically prints instead of speaking — so you can leave `say` steps in workflows that run both locally and in pipelines. ## Try It A new `examples/say-something/` example demonstrates voices, rates, print policies, and composing `say` into a build pipeline: ```bash cd examples/say-something atmos workflow notify -f say # speak a completion message atmos workflow voices -f say # cross-platform voice stack atmos workflow print-modes -f say # the three print policies atmos workflow pipeline -f say # announce each build milestone ``` Here's the build pipeline announcing its milestones end to end: [View the full example](/examples/say-something) The cross-platform plumbing lives in a reusable `pkg/say` package, mirroring the existing browser-opening abstraction, so other parts of Atmos can adopt audible notifications too. For usage and configuration, see [say](/workflows/steps/type/say). ## Get Involved Add a `say` step to the end of your slowest workflows and let Atmos tell you when it's done. Feedback and ideas are welcome on [GitHub](https://github.com/cloudposse/atmos). --- ## Generate Verifiable Terraform Provenance SBOMs Compliance reviews often start with a deceptively simple question: what exactly is in this infrastructure release? Answering it by hand means reconciling vendored sources, provider locks, module downloads, and registry artifacts—without accidentally mistaking missing evidence for an empty dependency set. Atmos now makes that evidence visible in one SBOM while keeping coverage boundaries explicit. It also integrates with native CI to upload the SBOM as a GitHub Actions workflow artifact when enabled. ## The Problem An SBOM that quietly excludes a dependency category can create more risk than no SBOM at all. The person deciding whether an artifact is ready for compliance review needs to see gaps like these: - A provider version without its checksum - A module selected from a mutable ref - An unavailable module graph Terraform's provider lock file, vendored source receipts, and OCI artifact digests already contain much of the evidence that infrastructure teams need. Until now, that evidence lived in separate places, and no tool could render it as a single standards-based document. ## The Fix Generate a provenance/build-input SBOM for Terraform-managed infrastructure: ```shell atmos sbom generate --format cyclonedx-json --output infra.sbom.json ``` The first release inventories four evidence domains: - Atmos-managed sources recorded in `vendor.lock.yaml`, including `vendor.yaml`, `component.yaml`, and mixins - OCI source artifacts pinned by their selected manifest digest - Terraform providers from `.terraform.lock.hcl`, including recorded archive checksums - Terraform modules from `terraform modules -json`, resolved to a commit, OCI digest, or content hash when possible The command emits CycloneDX JSON or SPDX JSON from the same graph. The two documents describe the same components and relationships. ### Compliance Without False Confidence An SBOM is only useful when its coverage is clear. The default `provenance` mode includes coverage diagnostics for each adapter and emits the evidence that is available. It deliberately does not claim that the document represents every dependency or deployed workload in the environment. For a Terraform-scoped NTIA-baseline check, use `--mode ntia` and identify the subject: ```shell atmos sbom generate \ --mode ntia \ --subject-name infra-live \ --subject-version "$(git rev-parse --short HEAD)" \ --subject-supplier "Example, Inc." \ --format spdx-json \ --output infra-live.spdx.json ``` NTIA mode fails when required provenance is incomplete, such as: - Missing subject information - A provider lock without a SHA-256 archive checksum - A module without immutable resolution evidence - An unavailable module-graph interface A failed compliance-mode invocation is more useful than a document that silently leaves evidence out. ## How to Use It Start with the default provenance mode to inspect the evidence and coverage for initialized Terraform components: ```shell atmos sbom generate --format cyclonedx-json --output infra.sbom.json ``` Add [`--include-files`](/cli/commands/sbom/usage) when the exact `vendor.lock.yaml` file inventory belongs in the document: ```shell atmos sbom generate --include-files --output infra.sbom.json ``` The command uses the Terraform executable configured by `components.terraform.command` in each component directory. If it is unset, Atmos uses `terraform`. Module inventory currently requires Terraform 1.10 or later because it relies on the stable `terraform modules -json` interface. If your project configures `tofu`, Atmos honors that choice. Atmos does not parse Terraform's internal module-installation files as a fallback. Until OpenTofu exposes an equivalent stable structured interface, Atmos marks module coverage unavailable, and NTIA mode refuses the scope. ### What's Next This is the foundation for broader dependency coverage — it's not yet feature complete. The next adapters extend coverage across the other component types Atmos already supports: - Helm and Helmfile locks - Kubernetes manifests - Typed deployed-container image discovery - OCI SBOM attestations or scanner integration for image contents - OpenTofu module graphs The shared graph and coverage contract mean those additions extend both CycloneDX and SPDX consistently. Read the full [SBOM command reference](/cli/commands/sbom/usage) and the [provenance design](https://github.com/cloudposse/atmos/blob/main/docs/prd/sbom-provenance.md). ## Get Involved The initial release is experimental. Questions about the evidence model, new adapters, or compliance workflows are welcome in the [Atmos GitHub repository](https://github.com/cloudposse/atmos) and the community Slack. --- ## Scaffolds turn your golden paths into a platform product Platform teams build golden paths so application teams can begin with the right architecture, guardrails, and operational conventions. A GitHub template repository is an excellent way to distribute that starting tree, but its job ends at the copy. It cannot ask project-specific questions, apply a policy to CI-supplied answers, tailor the file set, or evolve the template without every team manually reconciling a fork. Atmos scaffolds turn a golden path into an executable contract. Use a local template, register one in `atmos.yaml`, or point to a Git repository—including a GitHub template repository—and let the same template guide developers, automate CI, and evolve with the platform. Creation is only day one. Golden paths accumulate improvements after projects have adopted them: updated CI conventions, guardrails, shared configuration, and boilerplate. Atmos scaffolds include an optimistic three-way merge process so a project can take those upstream improvements without blindly replacing the custom work that happened after initialization. [View the full scaffolding example](/examples/scaffolding) ## From Repository Copy to Golden Path The usual alternative is either a generic template that every team must customize by hand or a matrix of near-duplicate templates for every language, environment, compliance rule, and deployment option. The first creates drift. The second makes the platform team responsible for maintaining an ever-growing catalog of almost-the-same repositories. A scaffold keeps one template and makes it adapt to the project being created. The platform team owns the contract; the consuming team supplies only the choices that belong to its project. ## What the Contract Enforces - **Validated inputs.** Required fields, patterns, select and multiselect options, and boolean values are validated for interactive answers, defaults, presets, persisted values, and `--set` flags. Platform policy does not disappear when a template runs in CI. - **Conditional prompts and files.** A field or file can use `when:` and the answers collected so far. A template can ask for a vendored component version only when vendoring is enabled, then generate the corresponding manifest only in that case. - **Lifecycle hooks.** Templates can run declared work before or after generation—formatting, validation, or setup—with the same condition engine used by Atmos workflows and CI hooks. Teams can use [`--skip-hooks`](/cli/commands/scaffold/generate) as an explicit per-invocation escape hatch. - **Day-two updates.** The [`atmos scaffold generate --update`](/cli/commands/scaffold/generate) command re-runs a template against an existing project and performs an optimistic three-way merge. It is a strong fit for shared boilerplate that changes infrequently across many projects: non-overlapping improvements can be carried forward, while real conflicts stay visible instead of silently overwriting local work. The [`--merge-strategy`](/cli/commands/scaffold/generate) option selects manual, ours, or theirs conflict handling. Both `when:` forms use the condition language shared by Atmos workflows and CI hooks. The `answers` variable exposes the values collected so far, allowing a golden path to express its decisions once rather than encode them in a pile of repository variants. ## Keep GitHub Templates, Add a Platform Contract GitHub template repositories remain a useful ownership and discovery mechanism. Atmos adds the behavior they intentionally do not provide: typed choices, conditional generation, hooks, and a safe path for bringing template improvements back to an existing project. A remote source can be pinned to a branch, tag, or commit with `--ref`, so platform teams can make a deliberate release available instead of distributing an accidental snapshot. See the [Scaffold Command Documentation](/cli/commands/scaffold/generate) for the field, hook, remote-source, and update reference. --- ## Prompt choices that follow earlier answers Every interactive form with a fixed set of choices eventually hits the same problem: at some point, what a question _should_ offer depends on how an earlier question was answered. List every possible environment when asking "which one deploys first," and people have to hunt through options they never actually set up. Show raw values like `dev` or `prod` just to avoid maintaining a separate label mapping, and people have to mentally translate those into what they actually mean. The only real fix is letting choices adapt to prior answers — and letting them look nicer than the underlying values. ## The Problem In a scaffold template, `select` and `multiselect` fields normally take their choices from `options:`, a static list fixed at authoring time. That's fine when the choices really are fixed, but two situations don't fit: - Narrowing to a previous answer. Someone selects their environments (`dev`, `staging`, `production`) via a `multiselect`; a later `select` field should default to offering just those chosen environments — not the entire list the template supports. - Friendly labels vs. stored values. You want to display something readable while still storing the actual value (`dev`/`staging`/`production`) that generated files and conditions rely on. Previously this meant either showing raw values on screen, or hand-maintaining a separate label mapping elsewhere in the template. A static `options:` list can't handle either case — it's decided once and never revisited. ## The Fix A field's `options:` can now be computed dynamically, using the same `answers.`-dot-path syntax and Go-template conventions already available elsewhere in scaffold templates: ```yaml spec: fields: - name: envs type: multiselect options: [dev, staging, prod] - name: default_env type: select options: answers.envs ``` If someone picks `staging` and `prod`, `default_env` will only offer those two — `dev` is excluded. The source field doesn't need to be a `multiselect`; a plain text input works too, combined with a template expression that converts it to a list: ```yaml spec: fields: - name: csv_owners type: input default: "platform-team,security-team" - name: primary_owner type: select options: '{{ splitList "," answers.csv_owners }}' ``` Both forms resolve `options:` based on whatever was supplied for the earlier field, whether answers come from an interactive prompt or are passed in non-interactively. The same `options:` field also accepts a list of label/value pairs, so what's displayed and what's stored can differ: ```yaml spec: fields: - name: envs type: multiselect options: - label: Development value: dev - label: Staging value: staging - label: Production value: prod - name: default_env type: select options: answers.envs ``` Here, `envs` displays "Development," "Staging," and "Production" while storing `dev`/`staging`/`prod` everywhere they're actually needed — generated files, `when:` conditions, and other fields' `options:`. Since `default_env` pulls its choices dynamically from `envs`, it inherits those same friendly labels for whatever was picked, rather than falling back to raw stored values. ## How to Use It Point `options:` at `answers.` (or a template expression) on any `select`/`multiselect` field to source its choices from an earlier answer instead of a fixed list, and switch a plain string list to label/value object entries wherever the stored value and the display text should differ. See the [`atmos scaffold generate`](/cli/commands/scaffold/generate) docs for the full field reference. ## Get Involved [Open an issue](https://github.com/cloudposse/atmos/issues) with feedback, or share templates that put dynamic options to use in your own projects. --- ## Generate one file per selection with matrix Every file a scaffold template declares renders at most once. `when:` can skip a file, but it can never multiply one. You could work around that by authoring every combination up front and letting `when:` prune down to what applies — but that only works if every combination is knowable in advance. It breaks down for environments picked interactively from a longer list, or names typed in by hand that no template author could have enumerated ahead of time. Until now, that meant hand-rolling files outside the template, or maintaining a pile of near-duplicate ones inside it. ## The Problem The number of files a project needs often depends on what gets selected when the project is generated. Pick three environments out of five, and you want three stack files, not five. That output was already possible: declare all five stack files in the template up front, and gate each one with its own `when:` on whether that specific environment got picked. The real cost was authorship — five nearly-identical files, one per environment the template author had to anticipate, kept in sync by hand as the shared parts drifted. Nothing let a template say "generate one of these per selected value" from a single file; only "generate this specific file" or "skip this specific file." ## The Fix Declaring `matrix:` on a file entry expands it into one generated file per combination of one or more axes. It reuses the same shape Atmos workflow `matrix:` steps already use, so the syntax should feel familiar. `when:` still prunes combinations that don't apply — the same conditional engine that already gates whole files, now scoped to a single combination at a time: ```yaml spec: fields: - name: environments type: multiselect options: [dev, staging, production] files: - path: stacks/deploy/environment.yaml target: "stacks/deploy/{{ .matrix.environment }}.yaml" matrix: environment: answers.environments ``` Selecting `dev` and `staging` generates exactly `stacks/deploy/dev.yaml` and `stacks/deploy/staging.yaml`. Declaring more than one axis expands their full combination, and each resolved combination is available as `.matrix.` in Go-template fields such as the output path and generated content, and as `matrix.` in `when:` conditions — so a file can name itself and branch on its own combination. ### Computed axes Real answers aren't always a flat, pre-selected list. Say `environments` were a structured answer instead of a `multiselect` — supplied through `--set` or a preset value — shaped like this: ```yaml environments: dev: regions: us-east-1: {} production: regions: us-east-1: {} us-west-2: {} ``` The full list of regions actually used isn't something anyone picked directly — it has to be derived from every environment's own `regions`. The `collectKeys` function does that: called with one argument, it returns a map's keys; called with a second argument, it collects that key from every value in the map, flattening and deduplicating across all of them. ```yaml files: - path: deploy.yaml target: "deploy/{{ .matrix.environment }}/{{ .matrix.region }}.yaml" matrix: environment: '{{ collectKeys answers.environments }}' region: '{{ collectKeys answers.environments "regions" }}' when: "matrix.region in answers.environments[matrix.environment].regions" ``` `environment` resolves to `dev` and `production`; `region` resolves to every region used by any of them (`us-east-1` and `us-west-2`). Their combination fans a single `deploy.yaml` out into `deploy/dev/us-east-1.yaml`, `deploy/production/us-east-1.yaml`, and `deploy/production/us-west-2.yaml` — `target:` names each one from `.matrix.`, and `when:` prunes the combination down to each environment's actual regions, so `dev` never gets a `us-west-2` file. ## How to Use It The [scaffolding-matrix example](/examples/scaffolding-matrix) is a minimal, runnable template — one `multiselect` field driving one matrix axis: ```shell cd examples/scaffolding-matrix atmos scaffold generate example ./my-project ``` Answering the `environments` prompt with `dev` and `staging` generates `stacks/dev.yaml` and `stacks/staging.yaml` from the template's single `environment.yaml` file — or skip the prompt entirely with `--set environments=dev,staging` for scripted, non-interactive use. Add `matrix:` to any `spec.files[]` entry alongside `target:` to do the same in your own templates, using a literal list, a `multiselect` answer, or a computed expression for each axis. ## Get Involved See the [`atmos scaffold generate`](/cli/commands/scaffold/generate) docs for the full reference, or [open an issue](https://github.com/cloudposse/atmos/issues) with feedback. --- ## Keep your formatting through atmos scaffold --update Atmos scaffolds can perform genuinely complex three-way merges of YAML templates: keys can merge intelligently, and comments and local customizations are preserved when changes don't conflict. That capability comes from parsing YAML into a structured document and re-serializing it—and structured serialization is lossy by nature. Formatting that isn't part of the data model, like blank lines separating sections, doesn't survive the round trip. If your team treats that whitespace as a convention rather than noise, `atmos scaffold generate --update` (and `atmos init --update`) used to flatten it every time, whether or not the file had actually changed. ## The Problem [`--update`](/cli/commands/scaffold/generate) re-runs a template against an existing project and 3-way merges the result. Scaffold picks its merge algorithm by file extension: YAML-aware for `.yaml`/`.yml`, line-oriented text for everything else. The YAML-aware path is what makes the complex merges possible in the first place—but re-encoding the whole document through a YAML parser and serializer means anything the parser doesn't model, blank lines between top-level blocks being the common case, gets dropped unconditionally. That's a real cost for files where formatting _is_ a convention—many CI pipeline definitions use blank lines to visually separate jobs, stages, and other top-level blocks. ## The Fix [`--merge-driver`](/cli/commands/scaffold/generate) lets you override which merge algorithm runs, named after git's own merge driver concept: - **`auto`** (default, unchanged) — YAML-aware for `.yaml`/`.yml`, text otherwise. - **`text`** — forces every file, YAML included, through a line-oriented, diff3-style merge. Comments, blank lines, and other structural formatting the YAML-aware merger doesn't model are preserved in non-conflicting regions. Because it's a flag on the `--update` invocation itself, not a project-wide setting, you're not choosing one mode for the project's entire lifetime. Most updates can stay on the default `auto` merge, and you reach for `--merge-driver=text` on the specific update that needs it—bundle up a template's formatting-sensitive changes (a CI pipeline overhaul, say) and pull them in with one deliberate `--merge-driver=text` run, rather than running every future update through the coarser text merger just to protect that one file. This is a different axis from [`--merge-strategy`](/cli/commands/scaffold/generate), which decides how a genuine conflict resolves (manual, ours, or theirs) once a merge algorithm has already run. `--merge-driver` decides which algorithm runs in the first place. ## How to Use It ```shell # Everyday updates: the default auto merge is usually what you want. atmos scaffold generate my-template ./my-project --update # This update brings in a batch of formatting-sensitive template changes— # override the driver just for this run. atmos scaffold generate my-template ./my-project --update --merge-driver=text atmos init --update --merge-driver=text ``` ## Get Involved See the [`atmos scaffold generate`](/cli/commands/scaffold/generate) and [`atmos init`](/cli/commands/init) docs for the full flag reference. Have feedback on this feature? [Open an issue](https://github.com/cloudposse/atmos/issues) or join the conversation in the [Cloud Posse community Slack](https://cloudposse.com/slack). --- ## Distribute scaffold templates through your existing OCI registry If your team already publishes container images, Atmos components, or Helm charts to a private OCI registry, that registry is probably the most secure, versioned, and well-understood distribution channel you have. Reusable project scaffolding rarely gets to use it — instead it tends to live in its own git repository, with its own access controls and its own line in the onboarding docs, just to hand out a `scaffold.yaml` and a handful of files. ## The Problem Both [`atmos scaffold generate`](/cli/commands/scaffold/generate) and [`atmos init`](/cli/commands/init) could already pull templates from git, HTTPS, and S3 sources, but nothing else. A team standardizing on OCI for internal distribution — components, container images, Helm charts — had no way to publish a scaffold template the same way. Templates were the one artifact type still forced onto a separate channel. ## The Fix Both commands now accept an `oci://` source directly, pulled through the same client Atmos already uses for [`atmos vendor pull`](/cli/commands/vendor/pull) and just-in-time component provisioning. Authentication follows the identical precedence: Docker credentials from `docker login`, then `ATMOS_GITHUB_TOKEN` for `ghcr.io`, then anonymous — nothing new to configure if OCI sources are already working elsewhere in the project. ## How to Use It Point either command at an OCI reference the same way you would a git URL: ```shell atmos scaffold generate oci://ghcr.io/example/templates:v1.0.0 ./components/terraform/vpc atmos init oci://ghcr.io/example/templates:v1.0.0 ./my-project ``` A version lives directly in the reference (`:v1.0.0`), the same way an OCI vendor source is pinned — there's no separate `--ref` flag to learn for this case, since that flag only ever applied to git sources. [`--update`](/cli/commands/scaffold/generate)'s three-way merge, [`--git`](/cli/commands/scaffold/generate)/[`--no-git`](/cli/commands/scaffold/generate), and `--base-ref` all work exactly as they already do for every other source type: the merge base always comes from the generated project's own git history, never from re-fetching the template, so switching a template to OCI changes nothing about how updates behave. A `scaffold.templates` entry in `atmos.yaml` can point at an `oci://...` source too, right alongside existing git-hosted entries. ## Get Involved [Open an issue](https://github.com/cloudposse/atmos/issues) with feedback, or let us know what registry you're publishing scaffold templates to. --- ## Scanner Findings as Inline PR Annotations and Code Scanning Alerts Security scanner hooks (Checkov, Trivy, KICS) can now surface findings as **inline GitHub annotations** on the pull request diff and upload them to **GitHub Code Scanning** (the Security tab) — natively, with no `github/codeql-action` step. Custom hooks running any SARIF-emitting tool get the same treatment by adding `format: sarif`. ## The Problem [Scanner hooks](/stacks/hooks) already render a findings summary to the terminal, the job step summary, and the Atmos Pro run page. But the two richest GitHub surfaces were missing: - **Inline annotations** — findings pinned to the exact file and line on the PR diff, where reviewers actually look. - **Code Scanning alerts** — tracked findings in the Security tab with an open → fixed lifecycle across runs. The findings were right there in the parsed SARIF; they just had nowhere to go. ## The Solution Three independent CI reporting outputs, all under `ci:` and gated by the `ci.enabled` master switch: ```yaml ci: enabled: true summary: true # markdown report in the job step summary (default: on) annotations: true # inline ::error/::warning on the PR diff (default: on) results: true # upload SARIF to GitHub Code Scanning (default: off) ``` - **`ci.annotations`** turns each finding into a GitHub `::error`/`::warning` annotation anchored at its file and line. This is the **non-Code-Scanning** path — it needs no GitHub Advanced Security, so it works on any repo. - **`ci.results`** uploads the raw SARIF to Code Scanning. Atmos derives the analysis category from the scan target automatically, so a `terraform plan` across many components tracks each as its own analysis instead of overwriting. Both are implemented as **native CI provider capabilities** (extending the same provider interface that already powers job summaries, check runs, and PR comments), not by shelling out to a third-party action. Everything is best-effort: a reporting failure never fails your hook or your plan, and outside CI it all no-ops. Because both outputs can attach line-level feedback to a pull request, enable both only when you want both the lightweight Actions annotation and the tracked Code Scanning alert. If duplicate comments on the same lines are noisy for your team, enable only one of `ci.annotations` or `ci.results`. ### Built-in and custom tools The three SARIF scanners — `checkov`, `trivy`, `kics` — participate automatically. Any **custom** tool does too, just by declaring `format: sarif`: ```yaml hooks: tfsec: events: [after.terraform.plan] kind: command command: tfsec args: ["--format", "sarif", "--out", "$ATMOS_OUTPUT_FILE", "$ATMOS_COMPONENT_PATH"] format: sarif ``` ### Permissions ```yaml permissions: contents: read security-events: write # only needed for ci.results (SARIF upload) ``` Annotations and the summary need no special permissions and no paid add-on — they work on any repo. `ci.results` uploads to Code Scanning, which needs `security-events: write` and, on **private repos**, **GitHub Advanced Security — a paid add-on that GitHub licenses per active committer**. Code Scanning is free on public repos. That's exactly why annotations default on and `ci.results` defaults off — everyone gets inline feedback for free, and only GHAS subscribers opt into the Security-tab integration. ## How to Use It See [CI Reporting](/stacks/hooks#ci-reporting) in the hooks documentation for the full reference. For usage and configuration, see [CI Status Checks](/cli/configuration/ci/checks). ## Get Involved Want findings routed somewhere else — GitLab security dashboards, threaded PR review comments? [Open an issue](https://github.com/cloudposse/atmos/issues) or join us in the [Atmos community](https://cloudposse.com/slack). --- ## Security Scanner Findings Now Surface in the CI Job Summary Security scanner hooks (Checkov, Trivy, KICS) now write their findings to the GitHub Actions job step summary automatically — so scan results show up in the pipeline run instead of being buried in the [`atmos terraform plan`](/cli/commands/terraform/plan) log stream. The official Atmos Docker image was also updated so Checkov runs without a glibc error. ## The Problem When you wire a scanner into a component with the [`hooks`](/stacks/hooks) framework, Atmos already renders a clean markdown report of the findings: ```yaml components: terraform: bucket: hooks: security: events: [after.terraform.plan] kind: checkov ``` That report printed to your terminal — and to the Atmos Pro run page when Pro is connected — but **nowhere a CI pipeline could show it**. In GitHub Actions, the findings were mixed into the multi-thousand-line plan log. There was no report in the checks, no job summary, nothing to glance at to see whether the scan was clean. A second, quieter problem: inside the official `cloudposse/atmos` Docker image, Checkov crashed before it could produce any findings: ``` [PYI-243:ERROR] Failed to load Python shared library 'libpython3.9.so.1.0': /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.38' not found ``` Checkov ships as a PyInstaller bundle that needs GLIBC 2.38+, but the image was built on `debian:bookworm-slim` (glibc 2.36). Because scanner hooks default to `on_failure: warn`, the crash was silently downgraded to a warning and the scan reported "no findings" — when in reality it never ran. ## The Solution Scanner summaries are now appended to the GitHub Actions job step summary whenever Atmos detects it is running in GitHub Actions (i.e. `GITHUB_STEP_SUMMARY` is set). It requires **no configuration** — the same markdown you already see in your terminal shows up in the run summary: - Your terminal - The GitHub Actions [job step summary](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary) - The Atmos Pro run page (when Pro is connected) Write the markdown once, get it everywhere. Writing to the step summary is best-effort: if the summary file can't be written, it never fails the hook or the terraform command — the findings already rendered to the log. And the Docker image now builds on `debian:trixie-slim` (glibc 2.41), so Checkov — and any other PyInstaller-bundled tool installed via the Atmos toolchain — loads its frozen Python runtime and actually produces findings instead of crashing. ## How to Use It Nothing to configure. Run your existing scanner hooks in a GitHub Actions workflow and open the job summary on the run — the findings table is there. See the [hooks documentation](/stacks/hooks#format-symmetry) for details on where summaries are rendered. For usage and configuration, see [CI Job Summaries](/cli/configuration/ci/summary). ## Get Involved Want findings surfaced somewhere else — Code Scanning / SARIF upload, PR annotations, JUnit results? [Open an issue](https://github.com/cloudposse/atmos/issues) or join us in the [Atmos community](https://cloudposse.com/slack). --- ## 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`](/functions/yaml/secret) YAML function: ```yaml 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: ```shell 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`](/cli/commands/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, run 1Password Connect to serve secrets to services inside your VPCs, or use the Mockoon-backed `mockoon/1password-connect` emulator for offline demos and integration tests. - **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. Here's the 1Password Connect emulator in action, backed by the Mockoon-based mock: [View the full example](/examples/onepassword-secrets) ## 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`](/cli/commands/describe/usage) and the [`atmos list`](/cli/commands/list/usage) 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: ```shell # 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`](/cli/commands/secret/exec) and [`atmos secret shell`](/cli/commands/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: ```shell # 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 1. Configure a `secret: true` store in `atmos.yaml`. 2. Declare your secrets under a component's `secrets.vars`. 3. Provision values with [`atmos secret set`](/cli/commands/secret/set) (or `init` to be prompted for missing ones). 4. Reference them with `!secret NAME` in your component vars. 5. Verify with [`atmos secret list`](/cli/commands/secret/list) and gate CI with [`atmos secret validate`](/cli/commands/secret/validate). See the [secrets configuration guide](/cli/configuration/secrets), the [`atmos secret` command reference](/cli/commands/secret/usage), and the [`!secret` YAML function](/functions/yaml/secret) 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. [View the full example](/examples/sops-secrets) ## 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](https://github.com/cloudposse/atmos). --- ## Critical Fix: Proper Shell Argument Quoting in Custom Commands We've fixed a critical bug in how Atmos handles arguments passed to custom commands via `{{ .TrailingArgs }}`. This fix improves security and ensures whitespace and special characters are preserved correctly. ## What Changed Custom commands that use `{{ .TrailingArgs }}` now properly quote arguments before passing them to the shell. This prevents data loss and potential command injection vulnerabilities. ### The Problem Previously, when you used trailing arguments in custom commands, Atmos would join them with spaces but **not apply proper shell quoting**. This caused issues when arguments contained: - **Multiple spaces**: `"hello world"` became `"hello world"` (spaces lost) - **Special characters**: `"$VAR"` would expand instead of staying literal - **Shell metacharacters**: `"foo;bar"` could split into two commands (security risk!) #### Example of the Bug ```yaml # Custom command definition commands: - name: run-script steps: - "my-script {{ .TrailingArgs }}" ``` ```bash # User runs with double spaces atmos run-script -- echo "hello world" # Before fix: Shell saw my-script echo hello world # Double space lost! # After fix: Shell sees my-script echo 'hello world' # Properly quoted, spaces preserved! ``` ### Security Impact The most serious issue was potential **command injection** when shell metacharacters weren't properly quoted: ```bash # Dangerous input atmos mycmd -- echo "foo;rm -rf /" # Before fix: Shell parsed as TWO commands echo foo rm -rf / # DANGEROUS! # After fix: Shell sees ONE safe command echo 'foo;rm -rf /' # Semicolon is literal ``` ## What You Need to Do ### If You're NOT Using `{{ .TrailingArgs }}` **No action required.** This fix only affects custom commands that use the `{{ .TrailingArgs }}` template variable. ### If You ARE Using `{{ .TrailingArgs }}` **Most users need no changes** - your commands will now work correctly with whitespace and special characters. However, if you **worked around the bug** by manually escaping or quoting, you may need to adjust: #### Scenario 1: Manual Escaping (Unlikely) ```bash # If you were doing this to preserve double spaces: atmos mycmd -- echo 'hello\ \ world' # After fix, this becomes: echo 'hello\ \ world' # Double-escaped, backslashes will show # Change to: atmos mycmd -- echo "hello world" # Natural quoting works now ``` #### Scenario 2: Nested Quoting ```bash # If you had complex nested quoting to work around the bug: atmos mycmd -- bash -c 'echo "hello world"' # This still works, but you can now simplify to: atmos mycmd -- echo "hello world" ``` ## Testing Your Commands We recommend testing any custom commands that use `{{ .TrailingArgs }}`, especially if they: 1. Pass arguments with **multiple consecutive spaces** 2. Use **special shell characters** like `$`, `;`, `|`, `&`, `` ` `` 3. Include **variable names** that should stay literal 4. Have **complex quoting** that might have been a workaround ### Test Examples ```bash # Test whitespace preservation atmos mycmd -- echo "hello world" # Should output: hello world (with 2 spaces) # Test special characters stay literal atmos mycmd -- echo '$HOME' # Should output: $HOME (literal, not expanded) # Test semicolons are safe atmos mycmd -- echo "foo;bar" # Should output: foo;bar (not execute 'bar' as command) ``` ## Technical Details ### How Arguments Are Now Quoted Atmos uses the industry-standard shell quoting library (`mvdan.cc/sh/v3/syntax`) to properly quote each argument: | Input | Quoted Output | Why | |-------|---------------|-----| | `hello world` | `'hello world'` | Preserves spaces | | `$VAR` | `'$VAR'` | Prevents expansion | | `foo;bar` | `'foo;bar'` | Treats `;` as literal | | Empty string | `''` | Preserves empty argument | | `line1\nline2` | `$'line1\nline2'` | Preserves newlines | ### Other Commands Not Affected This fix **only** affects custom commands using `{{ .TrailingArgs }}` in shell execution. These commands are **not affected**: - ✅ [`atmos terraform`](/cli/commands/terraform/usage) - passes arguments directly to Terraform SDK - ✅ [`atmos helmfile`](/cli/commands/helmfile/usage) - passes arguments directly to Helmfile - ✅ [`atmos packer`](/cli/commands/packer/usage) - passes arguments directly to Packer - ✅ [`atmos auth exec`](/cli/commands/auth/exec) - passes arguments directly to subprocess - ✅ [`atmos auth shell`](/cli/commands/auth/shell) - passes arguments to shell initialization ## What We Learned This bug highlights the importance of **proper shell quoting**. Key lessons: 1. **Never use `strings.Join()` for shell arguments** - always use proper quoting 2. **Test with edge cases** - whitespace, special characters, and injection attempts 3. **Fragmentation breeds bugs** - we consolidated 5 different parsing implementations into one safe utility ## Unified Argument Parsing As part of this fix, we've consolidated all argument parsing logic into a single, well-tested utility (`ExtractSeparatedArgs`). This reduces code duplication and makes the `--` separator pattern consistent across all Atmos commands. **Test coverage**: 70+ test cases covering edge cases, security scenarios, and integration with the actual shell parser. ## Migration Guide ### Common Scenarios #### You're passing simple arguments ```bash # This always worked and continues to work atmos mycmd -- arg1 arg2 arg3 ``` **No change needed.** #### You're passing arguments with spaces ```bash # Before: Spaces might have been lost atmos mycmd -- echo "hello world" # After: Spaces are preserved correctly atmos mycmd -- echo "hello world" ``` **Your commands now work correctly!** No changes needed. #### You have complex shell commands ```yaml commands: - name: deploy steps: - "kubectl apply -f - << EOF\n{{ .TrailingArgs }}\nEOF" ``` If you're doing **complex shell scripting** with heredocs or multi-line commands, test your custom commands to ensure they still work as expected. ## Documentation Updates We've updated the [Custom Commands documentation](/cli/configuration/commands) with: - Security best practices for `{{ .TrailingArgs }}` - Examples showing proper quoting behavior - Guidance on when to use trailing arguments We've also added a comprehensive [Safe Argument Parsing PRD](https://github.com/cloudposse/atmos/blob/main/docs/prd/safe-argument-parsing.md) documenting the issue, fix, and best practices. ## Getting Help If you encounter any issues with this change: 1. Check if you were manually escaping arguments (and can now remove that) 2. Test your custom commands with the examples above 3. [Open an issue](https://github.com/cloudposse/atmos/issues) if you find unexpected behavior ## Conclusion This fix makes Atmos custom commands **safer and more correct**. Arguments with whitespace, special characters, and shell metacharacters now work as users naturally expect. Thank you to everyone who uses Atmos! Your security and data integrity are our top priorities. For technical details, see the [Safe Argument Parsing PRD](https://github.com/cloudposse/atmos/blob/main/docs/prd/safe-argument-parsing.md). --- ## Source Cache TTL for JIT-Vendored Components Atmos now supports a `ttl` field on component source configuration to control how long cached JIT-vendored sources are reused before automatically re-pulling from the remote. This is especially useful when working with floating refs like branch names during active development. ## What Changed The `source` specification now accepts an optional `ttl` (time-to-live) field that controls cache expiration for JIT-vendored components. When the TTL expires, the source is automatically re-pulled on the next command invocation. ```yaml components: terraform: my-module: source: uri: "git::https://github.com/org/repo.git" version: "main" ttl: "0s" # Always re-pull from upstream ``` A global default TTL can be set in `atmos.yaml` and overridden per-component: ```yaml # atmos.yaml components: terraform: source: ttl: "1h" # Re-pull sources older than 1 hour ``` If no TTL is set, behavior is unchanged: cached sources are reused indefinitely and only re-pulled when the version or URI changes. ## Why This Matters When using floating refs like branch names (`version: "main"`), the version string never changes even though the upstream content does. Previously, Atmos would silently reuse stale cached code, forcing developers to manually delete `.workdir/` or run `source pull --force` before every plan. This created a painful inner loop during active development and a behavioral gap between local environments (cached) and CI (ephemeral, always fresh). With `ttl`, you describe how stale is acceptable and Atmos enforces it automatically. ## How to Use It **Active development** (always get latest): ```yaml source: uri: "git::https://github.com/org/repo.git" version: "develop" ttl: "0s" ``` **Team collaboration** (hourly refresh): ```yaml source: uri: "git::https://github.com/org/repo.git" version: "main" ttl: "1h" ``` **Stable releases** (no TTL needed): ```yaml source: uri: "github.com/cloudposse/terraform-aws-components//modules/vpc" version: "1.450.0" # No ttl - cache indefinitely, only re-pull on version change ``` Supported duration formats: `"0s"`, `"30m"`, `"1h"`, `"7d"`, `"daily"`, `"weekly"`. For usage and configuration, see [Source](/vendor/component-manifest/source). ## Get Involved We'd love to hear your feedback on this feature! Please [open an issue](https://github.com/cloudposse/atmos/issues) if you have questions or suggestions. --- ## List Components with Source Configuration Atmos now includes a `source list` command to display components with source configuration. Both [`--stack`](/cli/commands/terraform/source/list#flags) and `[component]` arguments are optional, allowing flexible filtering across your infrastructure. ## What Changed Two new commands are available with flexible filtering: ```bash # List all terraform sources across all stacks atmos terraform source list # List terraform sources in a specific stack atmos terraform source list --stack dev # List sources for a specific component across all stacks atmos terraform source list vpc # List sources for a specific component in a specific stack atmos terraform source list vpc --stack dev # Unified view: list sources across all component types atmos list sources atmos list sources --stack dev atmos list sources vpc ``` ## Dynamic Output Columns The output adapts to your query: **Listing all stacks** - Shows Stack column: ``` STACK COMPONENT URI VERSION plat-dev vpc github.com/cloudposse/terraform-aws-components//modules/vpc 1.450.0 plat-prod vpc github.com/cloudposse/terraform-aws-components//modules/vpc 1.450.0 ``` **Filtering by --stack** - Omits Stack column since it's redundant: ``` COMPONENT URI VERSION eks github.com/cloudposse/terraform-aws-components//modules/eks 1.450.0 rds github.com/cloudposse/terraform-aws-components//modules/rds 1.449.0 vpc github.com/cloudposse/terraform-aws-components//modules/vpc 1.450.0 ``` **With derived components** - Shows Folder column when component names differ from their base component: ``` COMPONENT FOLDER URI VERSION vpc/production vpc github.com/cloudposse/terraform-aws-components//modules/vpc 1.450.0 vpc/staging vpc github.com/cloudposse/terraform-aws-components//modules/vpc 1.450.0 ``` The Folder column only appears when there's a difference to show, keeping output clean for simple cases. ## Multi-Type Listing The `atmos list sources` command includes a Type column since it lists sources across all component types (terraform, helmfile, packer) in a unified view: ``` STACK TYPE COMPONENT URI VERSION plat-dev helmfile nginx github.com/cloudposse/helmfile-components//charts/nginx 1.0.0 plat-dev terraform vpc github.com/cloudposse/terraform-aws-components//modules/vpc 1.450.0 plat-prod terraform vpc github.com/cloudposse/terraform-aws-components//modules/vpc 1.450.0 ``` ## Why This Matters With the source provisioner, components can declare their source inline in stack configuration for just-in-time vendoring. The `source list` command helps you: - **Audit** which components have source configured across your infrastructure - **Compare** versions across stacks (same component, different versions) - **Debug** source configuration issues - **Discover** vendorable components in unfamiliar stacks ## Output Formats Multiple output formats are supported for integration with other tools: ```bash # Table (default, human-readable) atmos list sources --format table # JSON (for scripting) atmos list sources --format json # YAML (for configuration) atmos list sources --format yaml # CSV (for spreadsheets) atmos list sources --format csv # TSV (for tab-separated pipelines) atmos list sources --format tsv ``` ## Documentation - [Source Provisioner](/cli/commands/terraform/source) - JIT vendoring overview - [`atmos list sources`](/cli/commands/list/sources) - Unified view across all component types - [`atmos terraform source list`](/cli/commands/terraform/source/list) - List Terraform sources - [`atmos helmfile source list`](/cli/commands/helmfile/source/list) - List Helmfile sources - [`atmos packer source list`](/cli/commands/packer/source/list) - List Packer sources --- ## Source Provisioner UX Improvements The source provisioner now provides better visual feedback with spinners during auto-provisioning, interactive confirmation prompts for delete operations, and interactive stack selection when `--stack` is omitted. ## What Changed Four UX improvements to the source provisioner: 1. **Auto-provisioning spinner**: When components are automatically vendored on first use, you now see a spinner with progress instead of static messages 2. **Interactive delete confirmation**: Running [`atmos terraform source delete`](/cli/commands/terraform/source/delete) without `--force` now prompts for confirmation instead of erroring 3. **Delete operation spinner**: Deletion shows a spinner during the operation for consistent feedback 4. **Interactive stack selection**: All source commands (pull, delete, describe, list) now prompt for stack selection when `--stack` is omitted ## Why This Matters These changes align the source provisioner with UX patterns used elsewhere in Atmos: ```shell # Before: Static messages during auto-provisioning ℹ Auto-provisioning source for component 'myapp' ✓ Auto-provisioned source to .workdir/terraform/dev-myapp # After: Spinner shows progress ⠋ Auto-provisioning source for 'myapp' ✓ Auto-provisioned source to .workdir/terraform/dev-myapp ``` ```shell # Before: Delete required --force or failed $ atmos terraform source delete myapp --stack dev Error: --force flag required for safety # After: Interactive confirmation when --force omitted $ atmos terraform source delete myapp --stack dev ? Delete directory: .workdir/terraform/dev-myapp? [Yes!/No.] ⠋ Deleting .workdir/terraform/dev-myapp ✓ Deleted .workdir/terraform/dev-myapp ``` ```shell # Before: Required --stack flag or error $ atmos terraform source pull myapp Error: --stack flag is required # After: Interactive stack selection when --stack omitted $ atmos terraform source pull myapp ? Choose a stack dev > prod staging ℹ Selected stack `prod` ⠋ Provisioning source for 'myapp' ✓ Provisioned source to components/terraform/myapp ``` ## How to Use It No configuration changes needed. The improvements apply automatically: - **With TTY**: Get interactive prompts and spinners - **Without TTY (CI/scripts)**: Use `--force` to skip confirmation, spinners degrade gracefully - **Force flag**: Still available for scripted workflows: `atmos terraform source delete myapp --stack dev --force` For usage and configuration, see [Source](/vendor/component-manifest/source). --- ## SSE-C Encryption Support for Remote State Lookups The [`!terraform.state`](/functions/yaml/terraform.state) YAML function now supports reading from S3 buckets encrypted with customer-provided keys (SSE-C). ## What Changed When using `!terraform.state` to read Terraform or OpenTofu state directly from S3, Atmos now supports SSE-C (Server-Side Encryption with Customer-Provided Keys). Previously, state files stored in SSE-C encrypted buckets were inaccessible through `!terraform.state`, requiring workarounds to retrieve outputs. You can provide the SSE-C key in two ways: 1. **Backend attribute** in your stack configuration: ```yaml components: terraform: my-component: backend: s3: sse_customer_key: "your-base64-encoded-key" ``` 2. **Environment variable**: ```shell export AWS_SSE_CUSTOMER_KEY="your-base64-encoded-key" ``` The backend attribute takes precedence over the environment variable, following the same convention as OpenTofu and Terraform. This also works with `remote_state_backend` overrides, so you can configure SSE-C keys per-component when referencing remote state. ## Why This Matters Organizations that use SSE-C for S3 state encryption previously couldn't use `!terraform.state` to reference outputs from those state files. This meant either switching encryption strategies or falling back to [`!terraform.output`](/functions/yaml/terraform.output) (which runs `terraform output` and requires the full Terraform/OpenTofu binary). With SSE-C support, `!terraform.state` reads the state file directly from S3 with the correct encryption headers, keeping lookups fast and dependency-free. Note that this only affects `!terraform.state` (which reads state files directly from S3). The `!terraform.output` function is unaffected since it delegates to Terraform/OpenTofu, which already handles SSE-C natively. ## Get Involved Have questions or feedback? Join us on [Slack](https://slack.cloudposse.com/) or open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Set one retry policy for a whole stack Component retry already recovers a single component from a transient error: a 502, a dropped registry connection, a flaky provider lookup. The hard part was sharing that same policy across every component in a stack. You'd copy the same `retry:` block onto each component, wire up a shared abstract base component just to hold it, or set up a mixin. Now you can define the policy once at the stack level, and every component in that stack inherits it automatically. ## The Problem Component retry matches transient failures against `conditions` regex patterns and retries with backoff. That mechanism works well for one component. Restructuring unrelated components to share an abstract base just so they inherit a policy, or setting up a mixin file with `overrides.retry` and importing it everywhere, gets you there too — but none of those match the real goal: every component in this stack should recover the same way, without extra plumbing. ## The Fix Stacks now accept a top-level `retry:` block. This works the same way as the existing top-level `vars`, `metadata`, and `hooks` blocks. Set it once at the root of a stack manifest. Every supported component in that stack then picks it up: ```yaml title="stacks/deploy/prod.yaml" retry: max_attempts: 5 backoff_strategy: exponential initial_delay: 2s max_delay: 30s conditions: - /Bad Gateway/ - /GOAWAY/ - /could not query provider registry/ components: terraform: vpc: # ... transit-gateway: # ... ``` A concrete component's `retry:` overrides stack and abstract-base values for keys it sets. Missing keys inherit from lower-precedence layers. The full precedence order, lowest to highest, is: stack-level default → abstract base component → concrete component → `overrides.retry`. ## How to Use It Add `retry:` to the root of any stack manifest, alongside `vars:` and `components:`. You need no mixin file, no shared base component, and no per-component copies: ```yaml title="stacks/deploy/prod.yaml" retry: max_attempts: 3 conditions: - /Bad Gateway/ - /connection reset/ components: terraform: vpc: vars: name: vpc rds: vars: name: rds ``` Both `vpc` and `rds` retry on the same conditions without either one declaring `retry:` itself. If one component needs a different policy, set `retry:` directly on that component. Its values override lower-precedence layers unless `overrides.retry` sets the same keys. Missing keys continue through the precedence chain. The mixin pattern from the [component retry docs](/stacks/components/terraform/retry#stack-level-defaults) is still the right tool for a policy that applies to only part of a stack. ## Get Involved Open a discussion in the [Atmos repo](https://github.com/cloudposse/atmos) or post in the SweetOps Slack. Let us know if you hit a retry scenario that the current scoping (stack, base component, component, overrides) doesn't cover well. --- ## Explicit Stack Names in Stack Manifests You can now specify an explicit `name` field in stack manifests to override the logical stack name. This is especially useful when migrating from other tools like Terragrunt, or when your infrastructure doesn't follow a strict naming convention. See it in action: [View the full example](/examples/stack-names) ## What's New Stack manifests now support a top-level `name` field that explicitly sets the logical stack name. This takes precedence over `name_template` and `name_pattern` configured in `atmos.yaml`, giving you imperative control over stack naming when you need it. ## The Problem Atmos typically derives stack names from either: - **`name_template`** - A Go template that computes the name from context variables - **`name_pattern`** - A token-based pattern like `{tenant}-{environment}-{stage}` - **Default** - The basename of the stack file (zero-config) These declarative approaches work well for greenfield projects with consistent naming conventions. However, they can be challenging when: - Migrating legacy infrastructure that predates your naming conventions - Adopting infrastructure from acquisitions with different naming schemes - Migrating from tools like Terragrunt where stack organization differs - Working with infrastructure that simply doesn't fit a pattern ## The Solution Add a `name` field to any stack manifest to explicitly set its logical name: ```yaml # stacks/legacy-prod.yaml name: "my-legacy-prod-stack" import: - catalog/base components: terraform: vpc: vars: cidr: "10.0.0.0/16" ``` With this configuration, the stack is identified as `my-legacy-prod-stack` regardless of the filename or any naming patterns configured in `atmos.yaml`. The Terraform workspace will also use this name. ## Precedence Order Stack names are now resolved in this order: 1. **`name`** (highest) - Explicit name from stack manifest 2. **`name_template`** - Go template from `atmos.yaml` 3. **`name_pattern`** - Token pattern from `atmos.yaml` 4. **Default** - Basename of the stack file This means you can still use `name_template` or `name_pattern` for most stacks while selectively overriding specific stacks that don't fit the pattern. ## Use Cases ### Migrating from Terragrunt When migrating from Terragrunt, your existing state files are tied to specific workspace names that may not match Atmos naming conventions: ```yaml # stacks/us-east-1/prod/vpc.yaml name: "prod-us-east-1-vpc" # Matches existing Terraform workspace import: - catalog/vpc components: terraform: vpc: vars: cidr: "10.0.0.0/16" ``` ### Legacy Infrastructure For infrastructure that predates your naming standards: ```yaml # stacks/old-datacenter.yaml name: "dc1-legacy-infra" # Historical name that must be preserved components: terraform: network: vars: vpc_id: "vpc-abc123" ``` ### Acquisitions When integrating acquired infrastructure: ```yaml # stacks/acme-corp-prod.yaml name: "acme-production" # Keep their original naming import: - catalog/base components: terraform: vpc: metadata: component: vpc ``` ## How It Works The `name` field is extracted during stack processing and used wherever the logical stack name is needed: - **Stack identification** - [`atmos describe stacks`](/cli/commands/describe/stacks) shows the custom name - **Stack selection** - Use `-s my-legacy-prod-stack` on the command line - **Terraform workspace** - Workspace name derives from the stack name - **Dependencies** - Reference stacks by their logical name in `depends_on` ## Comparison with Component metadata.name This feature is analogous to `metadata.name` for components, which allows renaming component instances. Just as `metadata.name` lets you control how a component is identified, the stack-level `name` field lets you control how a stack is identified. | Level | Field | Purpose | |-------|-------|---------| | Component | `metadata.name` | Override component instance name | | Stack | `name` | Override logical stack name | ## Related Documentation - [Stack Names](/stacks/name) - [Stack Configuration](/stacks) - [Terraform Workspaces](/components/terraform/workspaces) ## Get Started Add a `name` field to any stack manifest that needs an explicit name. This feature is fully backward compatible - stacks without a `name` field continue to use `name_template`, `name_pattern`, or the filename as before. --- ## Stack Name Identity and Zero-Config Defaults Atmos now enforces a single canonical identity per stack and supports zero-config stack naming using filenames. These changes make Atmos easier for newcomers while providing explicit control for advanced users. ## What Changed ### Single Identity Rule Each stack now has exactly ONE valid identifier based on this precedence: 1. **`name`** (highest) - Explicit name from stack manifest 2. **`name_template`** - Go template from `atmos.yaml` 3. **`name_pattern`** - Token pattern from `atmos.yaml` 4. **Filename** (lowest) - Basename of the stack file Previously, a stack might respond to multiple identifiers (e.g., both the filename and a generated name). Now, only the highest-priority identifier is valid. ### Zero-Config Stack Naming When no `name`, `name_template`, or `name_pattern` is configured, stacks are identified by their filename. This enables newcomers to start using Atmos immediately without any naming configuration. ## Why This Matters ### For Newcomers Getting started with Atmos is now simpler. Create a stack file and reference it by filename: ```yaml # stacks/prod.yaml components: terraform: vpc: vars: cidr: "10.0.0.0/16" ``` ```bash # Just works - no naming configuration required atmos terraform plan vpc -s prod ``` ### For Advanced Users The single identity rule prevents confusion. If you have: ```yaml # stacks/legacy-prod.yaml name: "my-legacy-prod-stack" ``` Only [`atmos terraform plan vpc -s my-legacy-prod-stack`](/cli/commands/terraform/plan) works. Using `-s legacy-prod` correctly returns an error because that's not the canonical name. ## Migration Notes If you were using both the filename and a generated name to reference the same stack, you must now use only the canonical name. Check which identifier has highest precedence: - Stack has `name` field? Use that name. - `name_template` or `name_pattern` configured? Use the generated name. - Neither? Use the filename. ## Related - [Stack Manifest Name Override](/changelog/stack-manifest-name-override) - [Stack Names Documentation](/stacks/name) --- ## Read, Write, and Delete Store Values from the CLI and Workflows A build step often creates a value that a different step needs later. Examples are an image tag, a build number, or a deployment marker. In the past, you had only bad ways to pass this value along. You could write it into Terraform state, where it does not belong. You could run a cloud CLI command by hand. You could also build a custom file-based handoff between steps. Atmos already had a fast way to read any value from a configured store. But Atmos had no supported way to write a value into a store. The only exception was one narrow hook. That hook works only with Terraform output. For every other value, you had to leave Atmos to write it. ## The Problem Atmos stores support many backends. Examples are AWS SSM, AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, GCP Secret Manager, Redis, Artifactory, 1Password, Keychain, and GitHub Actions. The [`!store`](/functions/yaml/store) and [`!store.get`](/functions/yaml/store.get) YAML functions can read any value from these stores. But Atmos gave you only two ways to write a value into a store. You could declare the value as a formal secret with [`atmos secret`](/cli/commands/secret/usage). Or you could use the one existing store hook. That hook only copies a Terraform output into a store after `apply` runs. Atmos had no supported way to write other values, such as a Docker image tag from a `container` build step, a build number, or a deployment marker created mid-workflow. To write one of these values, you had to use the AWS CLI, `curl`, or a custom shell script in the workflow. ## The Fix Atmos now has a new CLI command group named [`atmos store`](/cli/commands/store/usage). This command group gives you raw CRUD access to any configured store. Atmos also has a new workflow step named `type: store`. This step writes a value from a workflow, a custom command, or a hook. Neither the CLI nor the step requires you to declare a value first. Both work directly on any backend listed under `stores:` in `atmos.yaml`. You can scope a value to a stack and a component. Or you can omit the stack and component to make the value global. ```shell atmos store set app-metadata image_tag sha256:abc123 --stack=prod --component=ecs-service atmos store get app-metadata image_tag --stack=prod --component=ecs-service atmos store list ``` The `store` step closes the loop with the existing read functions. First, a workflow builds an image and pushes it. Next, the workflow writes the resulting tag to a store. Later, a completely separate deploy run reads the tag back with `!store` or `!store.get`. This flow needs no shared Terraform state and no custom scripts. ```yaml steps: - name: push type: container action: push with: image: myapp:{{ .env.GIT_SHA }} - name: record-tag type: store action: write with: store: app-metadata key: image_tag value: "{{ .steps.push.metadata.digest }}" stack: prod component: ecs-service ``` The `store` step is a normal registered step type. Because of this, it also runs as a hook through the existing `kind: step` bridge. You need no extra configuration to run it after `terraform apply`. Atmos allows you to write to a `secret: true` store on purpose. For example, a step can generate a password and write it straight to a secret backend. Both the CLI and the step support this case. But this write is only a shortcut. It is not a replacement for `atmos secret`. It skips declaration and scope tracking. When a value must be tracked as a formal secret, use `secrets.vars` and [`atmos secret set`](/cli/commands/secret/set) instead. ## How to Use It Set and read a value scoped to a stack and a component: ```shell atmos store set app-metadata image_tag sha256:abc123 --stack=prod --component=ecs-service atmos store get app-metadata image_tag --stack=prod --component=ecs-service --format=json ``` Delete the value. Then list the configured stores: ```shell atmos store delete app-metadata image_tag --stack=prod --component=ecs-service atmos store list ``` Write a value from a workflow step. Then read the value back in stack configuration for a completely different component: ```yaml # workflow - name: record-tag type: store action: write with: store: app-metadata key: image_tag value: "{{ .steps.push.metadata.digest }}" ``` ```yaml # stacks/.../ecs-service.yaml vars: image_tag: !store app-metadata prod ecs-service image_tag ``` Both the command group and the step type are experimental for now. We may change them as we get feedback from users. For usage and configuration, see [store](/workflows/steps/type/store). ## Get Involved Try `atmos store` and the `store` step in your own build-to-deploy pipeline. Tell us what is missing. Examples are a matching read step, bulk import and export, or another feature. You can open an issue or start a discussion at [github.com/cloudposse/atmos](https://github.com/cloudposse/atmos). --- ## Identity-Based Authentication for Stores Atmos stores now support identity-based authentication. You can configure stores to authenticate using the same named identities from [`atmos auth`](/cli/commands/auth/usage) instead of relying on default credential chains. See it in action: [View the full example](/examples/auth-stores) ## What Changed Stores ([`!store`](/functions/yaml/store) YAML function) can now reference an Atmos auth identity via a new `identity` field in the store configuration. When set, the store uses that identity's credentials instead of the default credential chain (environment variables, default AWS profiles, etc.). ```yaml stores: prod/aws-ssm: kind: aws/ssm identity: prod-admin options: region: us-east-1 ``` This works with all cloud-backed store types: - **AWS SSM Parameter Store** - loads AWS config from realm-scoped credential and config files - **Azure Key Vault** - authenticates via `DefaultAzureCredential` with tenant hint from auth context - **Google Secret Manager** - uses realm-scoped application default credentials file Redis and Artifactory stores do not support identity-based authentication since they don't map to cloud provider identity types. ## Realm Compatibility Store identities are fully compatible with Atmos auth realms. When a realm is configured, the auth system embeds the realm into credential file paths (e.g., `~/.config/atmos/{realm}/aws/{provider}/credentials`). These realm-scoped paths flow through the resolver to stores automatically -- store code never needs to know about realms. This means stores in different realms use isolated credentials, preventing cross-environment credential collisions. ## Why This Matters Previously, stores always used the default credential chain, which meant separate credential management for secrets access vs. Terraform execution. Now you can use the same identity system for both, simplifying credential management and enabling more granular access control. ## How to Use It 1. Configure an identity in your `atmos.yaml` auth section (as you normally would for `atmos auth`). 2. Add the `identity` field to your store configuration referencing that identity name. 3. The store will automatically authenticate using that identity on first access. ```yaml stores: prod/aws-ssm: kind: aws/ssm identity: prod-admin options: region: us-east-1 staging/azure-kv: kind: azure/keyvault identity: staging-azure options: vault_url: https://staging-vault.vault.azure.net prod/gsm: kind: gcp/secretmanager identity: gcp-prod options: project_id: my-gcp-project ``` Stores without the `identity` field continue to work exactly as before -- this is a fully backward-compatible change. For usage and configuration, see [Stores Configuration](/cli/configuration/stores). ## Get Involved Have questions or feedback? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Tags and Labels: a First-Class Way to Categorize and Select Anything in Atmos Your stack hierarchy picks one way to organize your infrastructure — org, account, region, component type — and it can only pick one. `vpc` in `stacks/orgs/acme/prod/network.yaml` tells you where it lives in that tree; it doesn't tell you it's tier-1, or that platform owns it, or that it's in scope for SOX. Those characteristics don't belong to one branch of the tree — they apply across many stacks and many components at once, and a single component is usually several of them simultaneously. Your AWS accounts and auth identities have the same problem: `prod-admin` doesn't tell you it's the production one, or the elevated one, until you already know that. [Tags](/stacks/components/component-metadata#tags) and [labels](/stacks/components/component-metadata#labels) give components that organizational context directly — components get both; auth identities and providers get tags — so you can select and act on infrastructure by what it is instead of by where you filed it. ## The Problem A hierarchy can only organize infrastructure one way, but you need to operate on it along lines that hierarchy doesn't capture: - **A component's path describes its place in the tree, not what it is.** It tells you the org, account, and region; it says nothing about tier, ownership, or compliance scope — and those apply across stacks and component types, not within one branch. - **That context ended up living outside the system instead of on the resource.** A `--query` expression like `.settings.tier == "network"` encoded "tier-1" for one command; a hand-maintained `--components` list encoded it for another. Neither traveled with the component, and both went stale the moment something changed. - **The same gap showed up wherever Atmos manages many things.** Components across every type (Terraform, Kubernetes, Helm, containers), plus auth identities and providers, all needed a way to carry their own organizational context instead of relying on someone remembering it. ## The Fix Atmos now carries that organizational context as metadata, in two shapes: - **Tags** — a simple list of words, for basic categorical membership. Filtering matches _any_ of the given tags. - **Labels** — a set of key-value pairs, for richer metadata. Filtering requires _all_ of the given key=value pairs, the same semantics as a Kubernetes label selector. ```yaml title="stacks/orgs/acme/prod/network.yaml" components: terraform: vpc: metadata: tags: [production, networking, tier-1] labels: cost-center: platform compliance: sox ``` ```yaml title="atmos.yaml" auth: providers: sso-prod: kind: aws/iam-identity-center start_url: https://my-org.awsapps.com/start tags: [production, aws, sso] identities: prod-admin: kind: aws/permission-set via: provider: sso-prod principal: name: AdministratorAccess account: {name: production} tags: [admin, production, elevated-access] ``` ## How to Use It **[Listing and filtering components](/cli/commands/list/components):** ```shell atmos list components --tags production,tier-1 atmos list components --labels cost-center=platform,compliance=sox ``` **[Bulk operations](/cli/commands/terraform/usage)** — [`--tags`](/stacks/components/component-metadata#tags)/[`--labels`](/stacks/components/component-metadata#labels) compose with [`--all`](/cli/commands/terraform/plan#plan-all-components)/[`--affected`](/cli/commands/terraform/plan#plan-affected-components) everywhere Atmos already supports bulk selection, across every component type: ```shell atmos terraform apply --affected --tags production atmos kubernetes apply --all --labels cost-center=platform atmos helm apply --affected --tags production atmos container up --all --tags production ``` The same selection syntax works across supported component types, so you can reuse your labels when operating on different kinds of infrastructure. **The same filtering works for [auth](/cli/commands/auth/list)**, since providers and identities are tagged the same way: ```shell atmos auth list --tags production atmos auth login --tags admin,production # auto-selects if exactly one match, else prompts atmos auth logout --tags production # logs out every matching provider ``` **Bridging into your modules** — four new YAML functions read a component's own [`metadata.tags`](/stacks/components/component-metadata#tags)/[`metadata.labels`](/stacks/components/component-metadata#labels) without the usual [`!template`](/functions/yaml/template)/`toJson` boilerplate: [`!tags`](/functions/yaml/tags) and [`!labels`](/functions/yaml/labels) return the values directly, and [`!labels.keys`](/functions/yaml/labels.keys)/[`!labels.values`](/functions/yaml/labels.values) return just the label map's keys or values. The most common use is feeding `metadata.labels` into the `var.tags` a `terraform-null-label`-style module expects (Terraform and AWS call a map "tags"; Atmos calls it a "label" — same data, different name): ```yaml title="stacks/orgs/acme/prod/network.yaml" components: terraform: vpc: metadata: labels: Namespace: eg Environment: prod vars: tags: !labels # var.tags (map) <- metadata.labels ``` To read an individual label in a template, use `{{ .metadata.labels.runner }}`. See the [template context reference](/templates) for accessing component configuration. ## Get Involved Tags and labels are supported today on components across Terraform, Kubernetes, Helm, and container components; auth identities and providers support tags. Auto-discovering tags/labels from cloud provider metadata (for example, mapping AWS SSO PermissionSet tags into Atmos labels automatically) is a natural next step — if that's something you need, open an issue and let us know your use case. --- ## Custom Commands and Workflows Are Now a Complete Task Runner Replacement If you've ever tried to move a `Taskfile.yml` over to Atmos, you've hit the gap: `deps:` had no clean equivalent in custom commands, `sources:`/`generates:` up-to-date checking didn't exist at all, and a failed lint step stopped your whole release pipeline even when you just wanted to see every check's result. So teams ended up running two tools side by side — go-task for the parts Atmos couldn't do, Atmos for everything else — instead of one. ## The Problem Atmos workflows and custom commands already covered most of what a task runner needs: steps, templating, conditionals, parallel execution. But a handful of real gaps kept people from fully retiring go-task: - **No dependency ordering between named commands or workflows.** You could make steps _within_ one command run in parallel, but you couldn't say "run `build` before `test` and `lint`, and don't run `build` twice just because two things depend on it." - **No up-to-date checking.** go-task's `sources:`/`generates:` skip a task when nothing has changed. Atmos had nothing like it — every step ran every time, even a slow compile step whose inputs hadn't changed since the last successful run. - **No continue-on-error.** A single failing step stopped everything downstream, even for steps — like a linter — where you'd rather collect every result and report at the end. - **No precondition shortcut.** Skipping an install step when a tool is already on `PATH` meant hand-rolling a shell check. - **Custom commands couldn't use `parallel`/`matrix` at all.** The migration guide's own suggested workaround — wrap dependents in a `parallel` step with `needs:` — silently failed on custom commands; it only worked in workflows. ## The Fix Custom commands and workflows now cover all of it, using the same `when:`/CEL condition engine and scheduler Atmos already had — no second, bespoke mechanism bolted on. ### Dependencies between commands and workflows ```yaml commands: - name: build steps: [...] - name: test dependencies: commands: [build] steps: [...] - name: lint dependencies: commands: [build] steps: [...] - name: release dependencies: commands: [test, lint] steps: [...] ``` `build` is declared as a dependency of both `test` and `lint`, but it only runs once — the graph dedups identical dependency invocations automatically. Dependencies run concurrently by default. Need the same command with different inputs? Parameterize it: ```yaml dependencies: commands: - name: build flags: { env: dev } - name: build flags: { env: prod } ``` Both invocations run — different parameters mean different graph nodes. Workflows get the same `dependencies.workflows`, including cross-file references via `file:`. ### Skip steps that are already up to date ```yaml steps: - name: compile inputs: sources: ["cmd/**/*.go", "go.sum"] artifacts: paths: ["bin/handler"] command: go build -o bin/handler ./cmd/handler ``` No extra configuration needed — declaring `inputs`/`artifacts` alone means "skip this step unless `sources` changed since the last successful run." Run it twice in a row and the second run skips entirely. Power users can reference the underlying facts directly (`checksum.changed`, `timestamp.changed`, or the raw per-file `sources`/`artifacts` records) for custom logic. ### Skip a step when a tool is already installed ```yaml steps: - name: install-stringer preconditions: tools: ["stringer"] command: go install golang.org/x/tools/cmd/stringer@latest ``` Resolved via Go's `exec.LookPath` — no shell, so it works identically on Linux, macOS, and Windows. ### Continue past a failing step ```yaml steps: - type: shell command: golangci-lint run ./... continue: always - type: atmos command: terraform apply vpc -auto-approve ``` `continue: always` mirrors GitHub Actions' `continue-on-error`: the step's own failure is still visible, later steps still run, and the overall exit status is unaffected. ### `parallel`/`matrix` now works in custom commands The exact recipe that used to only work in workflows now works identically in custom commands: ```yaml commands: - name: release steps: - type: parallel steps: - name: test command: go test ./... - name: lint needs: [test] command: golangci-lint run ./... ``` ## Also Shipped A handful of smaller gaps closed alongside the above: - **`platforms` via `when:`** — `when: "os == 'darwin'"` instead of a dedicated field, reusing facts already available everywhere else `when:` is. - **Native command aliases** — `aliases: [dep, d]` on a custom command, registered in-process, distinct from the top-level subprocess-redirect `aliases:` config. - **`internal: true`** — hide a command from [`atmos help`](/cli/commands/help)/[`atmos list`](/cli/commands/list/usage) while leaving it fully invocable, for commands meant only to be run as someone else's dependency. - **`values:`** on flags and arguments — restrict a flag to a fixed set of choices, with static validation and an interactive picker when a required value is missing. ## How to Use It All of this is available today in custom commands and workflows — no flags to enable, no config migration required. See [dependencies](/cli/configuration/commands/dependencies), [`inputs`](/workflows/steps/inputs), [`artifacts`](/workflows/steps/artifacts), [`preconditions`](/workflows/steps/preconditions), and [`continue`](/workflows/steps/continue) for the full field references, or the [Alternatives](/reference/alternatives) page for how Atmos compares to go-task more broadly. For usage and configuration, see [Migrating from Taskfile.yml](/migration/taskfile). ## Get Involved If you're still running go-task alongside Atmos for one of these reasons, we'd like to hear about it — open a discussion on [GitHub Discussions](https://github.com/cloudposse/atmos/discussions) and tell us what's still missing. --- ## Templated Import Paths: Pin Imports from a Single Variable Atmos now renders Go templates in stack **import paths** using `settings`, `vars`, and `env` defined by **earlier imports in the same manifest**. You can pin a remote import's Git `?ref=` — or pick a local catalog — from one variable defined once. ## What Changed Import paths (local and remote) are now templated against the configuration accumulated from imports that appear **before** them in the same `import:` list: ```yaml title="stacks/deploy/prod/_defaults.yaml" settings: context: # Defined once per stage — 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 established by the imported _defaults. - "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= ``` Previously, the component `source.version` template worked (it resolves late, at component processing), but the import `?ref=` had to be hard-coded because imports are resolved before that context exists. Now both come from the same variable. ## Why This Matters This unlocks a clean way to keep `dev` and `prod` in one repository while protecting `prod`: - **dev** stacks use local catalogs and local component sources, so changes are vetted with a normal PR. - **prod** stacks import a _versioned_ catalog and pin the component source to an immutable ref — both driven by one `deployment_repo_version`. A change to component code or catalog files affects only `dev`; `prod` moves only when the pinned ref changes (a path you can guard with `CODEOWNERS`). ## How to Use It Define the value in a `_defaults` (or any file) imported **before** the import that references it, then template the path. It works for local paths too: ```yaml import: - _defaults # sets settings.context.catalog_ref - "catalog/{{ .settings.context.catalog_ref }}/service" ``` Notes: - Requires [`templates.settings.enabled: true`](/cli/configuration/templates). - A missing referenced value is a hard error. Set `ignore_missing_template_values: true` on the import (or globally) to leave unresolved values as-is, or `skip_templates_processing: true` to keep the `{{ ... }}` literal. - Only the import **path** sees earlier imports; imported file **content** is still templated with its own `context`. See the [imports documentation](/stacks/imports#referencing-earlier-imports-in-import-paths) for details. For usage and configuration, see [Imports](/cli/configuration/imports). --- ## Pace Terminal Output for Demos and Recordings Terminal demos and recordings are hard to follow when a command dumps hundreds of lines instantly. The output may be correct, but viewers cannot read it and recording tools capture a wall of text instead of a sequence. ## The Problem Some Atmos commands produce rich, multi-line output that is useful in a real terminal but awkward in demos, docs, and recorded walkthroughs. When everything appears at once, important transitions are easy to miss. That is especially noticeable with VHS recordings and scripted demos, where readable pacing matters as much as the final output. ## The Change Atmos now supports `settings.terminal.speed`, also available as `ATMOS_TERMINAL_SPEED`, to pace terminal output by lines per second. ```yaml settings: terminal: speed: 18 ``` ```shell ATMOS_TERMINAL_SPEED=18 atmos describe stacks ``` A value of `0` keeps the existing unlimited output behavior. That is the default, so normal interactive and CI usage does not change unless you opt in. ## Why It Matters - **Better recordings.** Output appears at a readable pace for demos, tutorials, and screengrabs. - **No command rewrites.** Keep the same Atmos command and control pacing through config or an environment variable. - **Default behavior is unchanged.** Unlimited output remains the default. ## Get Involved See the [terminal settings documentation](/cli/configuration/settings/terminal) for the full terminal configuration reference. --- ## Customize Your Terminal with Atmos Themes Atmos now includes 350+ terminal themes to customize your CLI experience. Choose from popular themes like Dracula, Solarized, or GitHub Dark, or browse the complete collection to find one that matches your style. ## What Are Themes? Themes control the colors, styles, and visual presentation of Atmos terminal output. They affect markdown rendering, table formatting, syntax highlighting, and status messages to provide a consistent look across all commands. ## Browse Available Themes View the complete collection of themes in the [theme gallery](/cli/commands/theme/browse). The gallery includes a visual preview of each theme's color palette and supports search by name, type (dark/light), or author. ## Using Themes List all available themes: ```bash atmos theme list ``` Preview a specific theme: ```bash atmos theme show dracula ``` ## Configure Your Theme Set a theme in your `atmos.yaml`: ```yaml settings: terminal: theme: dracula ``` Or use an environment variable: ```bash export ATMOS_THEME=dracula atmos terraform plan ``` ## Recommended Themes The gallery highlights recommended themes that work well across different terminal environments. These themes have been tested for readability and accessibility. ## Learn More - [Browse themes](/cli/commands/theme/browse) - Visual gallery with search - [Theme commands](/cli/commands/theme/usage) - Complete command reference - [Configuration](/cli/commands/theme/usage#theme-configuration) - Theme settings and options ## Get Involved - [GitHub Pull Request](https://github.com/cloudposse/atmos/pull/1766) - Share your feedback in the [Atmos Community Slack](https://cloudposse.com/slack) --- ## Dependency-Ordered Execution for Terraform --all Flag The [`--all`](/cli/commands/terraform/plan#plan-all-components) flag now executes Terraform components in dependency order. Run [`atmos terraform apply --all -s ue2-dev`](/cli/commands/terraform/apply) and components are automatically processed from their `dependencies.components` relationships. ## What Changed The `--all` flag for terraform commands now respects component dependencies defined in your stack configurations. Components are executed in topological order, ensuring dependencies are processed before dependents. ```bash # Components execute in dependency order $ atmos terraform apply --all -s ue2-dev Executing: atmos terraform apply vpc -s ue2-dev Executing: atmos terraform apply rds -s ue2-dev # depends on vpc Executing: atmos terraform apply eks -s ue2-dev # depends on vpc Executing: atmos terraform apply app -s ue2-dev # depends on rds, eks ``` Previously, `--all` processed components in an arbitrary order, which could cause failures when a component was applied before its dependencies. ## Why This Matters ### Reliable Infrastructure Deployments When deploying a complete environment, component order matters. A database component that references a VPC's subnet IDs will fail if the VPC hasn't been created yet. Dependency ordering eliminates these race conditions. ```yaml # stacks/ue2-dev.yaml components: terraform: rds: dependencies: components: - name: vpc eks: dependencies: components: - name: vpc app: dependencies: components: - name: rds - name: eks ``` ### Destroy in Reverse Order The `destroy` command automatically reverses the dependency order, ensuring dependents are destroyed before their dependencies: ```bash $ atmos terraform destroy --all -s ue2-dev Executing: atmos terraform destroy app -s ue2-dev # destroyed first Executing: atmos terraform destroy eks -s ue2-dev Executing: atmos terraform destroy rds -s ue2-dev Executing: atmos terraform destroy vpc -s ue2-dev # destroyed last ``` ### Circular Dependency Detection The system detects circular dependencies and fails fast with a clear error message: ```bash $ atmos terraform apply --all -s ue2-dev Error: circular dependency detected: vpc -> rds -> app -> vpc ``` ## How It Works ### Dependency Graph Construction When you run a command with `--all`, Atmos: 1. **Discovers components** - Finds all Terraform components in the specified stack 2. **Builds dependency graph** - Parses `dependencies.components` to create a directed acyclic graph (DAG) 3. **Topological sort** - Orders components so dependencies come before dependents 4. **Executes in order** - Runs the terraform command for each component sequentially ### Cross-Stack Dependencies Dependencies can reference components in other stacks: ```yaml components: terraform: app: dependencies: components: - name: vpc stack: ue2-network # Different stack ``` ### Dry Run Support Preview execution order without making changes: ```bash $ atmos terraform apply --all -s ue2-dev --dry-run Would execute: atmos terraform apply vpc -s ue2-dev Would execute: atmos terraform apply rds -s ue2-dev Would execute: atmos terraform apply eks -s ue2-dev Would execute: atmos terraform apply app -s ue2-dev ``` ## Usage The `--all` flag requires a stack to be specified: ```bash # Apply all components in a stack atmos terraform apply --all -s ue2-dev # Plan all components atmos terraform plan --all -s ue2-prod # Destroy all components (reverse order) atmos terraform destroy --all -s ue2-staging ``` ### Combining with Queries Filter which components to include using the [`--query`](/cli/commands/terraform/plan#plan-components-by-query) flag: ```bash # Apply only components matching a query atmos terraform apply --all -s ue2-dev --query '.settings.team == "platform"' ``` ## Get Involved - Share your feedback in the [Atmos Community Slack](https://cloudposse.com/slack) - [Report issues](https://github.com/cloudposse/atmos/issues) on GitHub --- ## Fix: terraform plan/apply --all actually runs in dependency order [`atmos terraform plan --all`](/cli/commands/terraform/plan) and `apply --all` now execute components in dependency (topological) order, as originally documented. Until this fix, the [`--all`](/cli/commands/terraform/plan#plan-all-components) flag was processing components in a non-deterministic order — the dependency-graph implementation that landed with [PR #1516](https://github.com/cloudposse/atmos/pull/1516) was reachable from tests but never wired into the CLI dispatcher. ## What Changed The CLI dispatcher in `cmd/terraform/utils.go` routed every multi-component flag (`--all`, [`--components`](/cli/commands/terraform/plan#plan-specific-components), [`--query`](/cli/commands/terraform/plan#plan-components-by-query)) through `ExecuteTerraformQuery`, which walks components via Go map iteration. Go map iteration order is randomized — there was no topological sort on this path, and `settings.depends_on` was ignored even when defined. `--all` is now routed to `ExecuteTerraformAll`, the function that builds the dependency graph and executes components in topological order. `--components` and `--query` continue to route to `ExecuteTerraformQuery`; their behavior is unchanged. ```bash # Components execute in dependency order, every time. $ atmos terraform plan --all -s prod --dry-run INFO Processing components in dependency order count=8 INFO Processing component index=1 total=8 component=vpc stack=prod ✓ Would plan vpc in prod (dry run) INFO Processing component index=2 total=8 component=eks/cluster stack=prod ✓ Would plan eks/cluster in prod (dry run) INFO Processing component index=3 total=8 component=eks/external-dns stack=prod ✓ Would plan eks/external-dns in prod (dry run) ... ``` `destroy --all` now executes in reverse topological order — dependents are destroyed before their dependencies. Circular dependencies in `depends_on` produce a hard error with the cycle path instead of being silently traversed. ## Why This Matters Anyone who configured `settings.depends_on` and ran [`atmos terraform apply --all`](/cli/commands/terraform/apply) was relying on a feature that didn't exist at the dispatch layer. A database component referencing a VPC's subnet IDs could be applied before the VPC, depending on which order Go decided to iterate the components map that run. The failure looked like a Terraform error, not a missing-feature bug, which is why this took a while to surface. The PRD for [DAG-based concurrent execution](https://github.com/cloudposse/atmos/blob/main/docs/prd/dag-concurrent-execution.md) was authored on the assumption that this path already worked. That work can now build on a foundation that actually exists. ## Behavior Notes A few related changes ride along with the dispatch fix: - **Stack is no longer required with `--all`.** Running `atmos terraform plan --all` without `-s` now processes every stack, matching what the [terraform-apply docs](/cli/commands/terraform/apply) describe ("Apply all components in all stacks"). The internal `stack is required when using --all flag` error has been removed. - **`--all -s ` still scopes to that stack.** Cross-stack prerequisites are not pulled in by default — same scope as before, just ordered. A future opt-in flag will allow cross-stack execution. - **Dry-run output is consistent.** Both multi-component paths now emit `Would in (dry run)` for each component. - **Auth-aware YAML functions work under `--all`.** [`!terraform.state`](/functions/yaml/terraform.state), [`!store`](/functions/yaml/store), and friends now resolve credentials correctly during multi-component runs, the same as `--query` already did (fixes a latent regression of [#2081](https://github.com/cloudposse/atmos/issues/2081) for the `--all` path). ## Known Follow-ups This fix unblocks several improvements that are still pending: - The dependency parser only reads the deprecated `settings.depends_on` format. It does not yet read [`dependencies.components`](/stacks/dependencies/components), the preferred format. - The parser only recognizes `component` and `stack` keys; documented `namespace`/`tenant`/`environment`/`stage` keys are ignored. A target in another stack must use the explicit `stack:` form. - Dependency-resolution failures (e.g., typo in a `component:` reference) are still logged at `Warn` and swallowed. A typo'd dependency silently drops the edge instead of erroring. - Execution is sequential. The DAG concurrency work tracked in `docs/prd/dag-concurrent-execution.md` will add `--max-concurrency` to parallelize independent components at the same level. Tracking issue: [#2485](https://github.com/cloudposse/atmos/issues/2485). ## Get Involved - Share feedback in the [Atmos Community Slack](https://cloudposse.com/slack) - [Report issues](https://github.com/cloudposse/atmos/issues) on GitHub --- ## Fixed: Terraform CLI Flags Restored After Registry Migration Several terraform CLI flags that were not working after version 1.202.0 have been restored. These flags were inadvertently broken during the command registry migration. ## The Problem Starting in version 1.202.0, several terraform flags were not recognized or not functioning correctly: ```bash # These flags were returning "unknown flag" errors or being ignored: atmos terraform workspace mycomponent -s mystack --auto-generate-backend-file=false atmos terraform output mycomponent -s mystack --skip-init atmos terraform deploy mycomponent -s mystack --deploy-run-init=false ``` This was problematic for CI/CD pipelines and workflows that relied on these flags for controlling Atmos behavior. ## Fixed Flags The following flags are now properly registered and working: | Flag | Description | |--------------------------------|----------------------------------------------------------------------------| | [`--skip-init`](/cli/commands/terraform/usage#flags) | Skip terraform init before running command | | `--auto-generate-backend-file` | Override auto\_generate\_backend\_file setting from atmos.yaml | | `--deploy-run-init` | Override deploy\_run\_init setting from atmos.yaml | | `--init-run-reconfigure` | Override init\_run\_reconfigure setting from atmos.yaml | | `--init-pass-vars` | Pass the generated varfile to terraform init (OpenTofu feature) | | `--planfile` | Path to a terraform plan file to use | | [`--skip-planfile`](/cli/commands/terraform/usage#flags) | Skip writing the plan to a planfile | ## Flag Availability by Command The `--skip-init` and `--init-pass-vars` flags are available on **all** terraform commands. The following flags are registered only on the commands that use them: | Command | Available Flags | |-------------|---------------------------------------------------------------------------------------------| | `init` | `--auto-generate-backend-file`, `--init-run-reconfigure` | | `workspace` | `--auto-generate-backend-file`, `--init-run-reconfigure` | | `plan` | `--auto-generate-backend-file`, `--init-run-reconfigure`, `--skip-planfile` | | `apply` | `--auto-generate-backend-file`, `--init-run-reconfigure`, `--planfile` | | `deploy` | `--auto-generate-backend-file`, `--init-run-reconfigure`, `--deploy-run-init`, `--planfile` | ## Example Usage ```bash # Skip init for output commands (useful when already initialized) atmos terraform output mycomponent -s mystack --skip-init # Override backend file generation atmos terraform workspace mycomponent -s mystack --auto-generate-backend-file=false # Control init behavior during deploy atmos terraform deploy mycomponent -s mystack --deploy-run-init=false ``` ## Root Cause During the terraform command registry migration in version 1.202.0, these flags had two issues: 1. **Flag registration**: Flags were registered in the old parsing code but not in the new flag registry, causing "unknown flag" errors 2. **Flag value propagation**: Even when flags were recognized, their values weren't being properly applied to the execution context, causing them to be silently ignored The fix registers these flags on the specific commands that use them and ensures the values are properly parsed and applied during command execution. ## Upgrade Upgrade Atmos to get this fix. No configuration changes are required. --- ## Terraform Cloud Backend Support for !terraform.output The [`!terraform.output`](/functions/yaml/terraform.output) YAML function now works seamlessly with Terraform Cloud and Terraform Enterprise backends, enabling cross-component dependencies without switching backend types. ## What Changed Atmos now correctly generates `backend.tf.json` configuration for Terraform Cloud and Terraform Enterprise backends when using the `!terraform.output` YAML function. This was previously broken because Terraform Cloud requires a different backend structure than other backend types. ## Why This Matters The `!terraform.output` YAML function is powerful—it lets you reference outputs from one component in another component's configuration, creating declarative dependencies between infrastructure components. However, this feature was silently failing for teams using Terraform Cloud or Terraform Enterprise backends. When Atmos needed to fetch outputs from a dependency component, it would generate an incorrect backend configuration that Terraform would reject: ``` Error: Unsupported backend type on backend.tf.json line 4, in terraform.backend: 4: "cloud": { There is no explicit backend type named "cloud". To configure HCP Terraform, declare a 'cloud' block instead. ``` This forced teams to either: - Avoid using `!terraform.output` with Terraform Cloud - Switch to S3/Azure/GCS backends just to use this feature - Manually work around the limitation with `terraform_remote_state` data sources ## The Problem Terraform Cloud has a unique backend configuration structure. Unlike other backends (S3, Azure, GCS) that nest under `terraform.backend.`, Terraform Cloud's configuration must be placed directly under `terraform.cloud`: **Incorrect (what Atmos was generating):** ```json { "terraform": { "backend": { "cloud": { "organization": "my-org", "workspaces": { "name": "my-workspace" } } } } } ``` **Correct (what Terraform Cloud expects):** ```json { "terraform": { "cloud": { "organization": "my-org", "workspaces": { "name": "my-workspace" } } } } ``` This subtle difference caused Terraform to fail during initialization, preventing the `!terraform.output` function from fetching outputs. ## How It Works Atmos now detects when a component uses a Terraform Cloud backend and generates the correct JSON structure automatically. The fix is applied in the `generateBackendConfig` function within the `pkg/terraform/output` package. When you use `!terraform.output` to reference another component's outputs, Atmos: 1. Determines the backend type of the dependency component 2. Generates the appropriate `backend.tf.json` structure 3. For Terraform Cloud/Enterprise backends specifically, places the configuration under `terraform.cloud` instead of `terraform.backend.cloud` 4. Runs `terraform init` and `terraform output` to fetch the values 5. Returns the output values for use in your stack configuration ## Example Usage Define your EKS cluster component with a Terraform Cloud backend: ```yaml # stacks/eks-cluster.yaml components: terraform: cluster: backend_type: cloud backend: cloud: organization: "my-org" workspaces: name: "eks-cluster-prod" vars: cluster_version: "1.29" # ... other cluster configuration ``` Now reference the cluster outputs in your application component: ```yaml # stacks/my-app.yaml components: terraform: my-app: backend_type: cloud backend: cloud: organization: "my-org" workspaces: name: "my-app-prod" vars: # Reference cluster outputs using !terraform.output cluster_endpoint: !terraform.output cluster.cluster_endpoint cluster_ca_cert: !terraform.output cluster.cluster_certificate_authority_data cluster_name: !terraform.output cluster.cluster_name ``` When Atmos processes `my-app`, it will: - Detect that `cluster` uses a Terraform Cloud backend - Generate the correct backend configuration - Fetch the outputs from the `eks-cluster-prod` workspace - Make them available to your `my-app` component ## What's Next This fix ensures feature parity across all backend types. Whether you're using S3, Azure Blob Storage, GCS, or Terraform Cloud, the `!terraform.output` function now works consistently. For teams already using Terraform Cloud, this unlocks the full power of declarative component dependencies without manual workarounds. ## Get Involved Try out the enhanced `!terraform.output` function with Terraform Cloud and let us know what you think! File issues or feature requests on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Comprehensive Documentation for Terraform Commands We've added comprehensive documentation for all 35 Terraform commands in Atmos, making it easier to understand how to orchestrate Terraform with stack-based configurations. Each command now has dedicated documentation with usage examples, arguments, flags, and integration details. ## What's New Every [`atmos terraform`](/cli/commands/terraform/usage) command now has its own documentation page with: - **Command overview** - What the command does and when to use it - **Usage examples** - Real-world scenarios and patterns - **Arguments** - Required and optional command arguments - **Flags** - Available command-line flags and options - **Stack integration** - How the command works with stack configurations ## Core Commands Essential Terraform workflow commands are now fully documented: - **[terraform plan](/cli/commands/terraform/plan)** - Generate execution plans with stack context - **[terraform apply](/cli/commands/terraform/apply)** - Apply infrastructure changes - **[terraform deploy](/cli/commands/terraform/deploy)** - Combined init, plan, and apply workflow - **[terraform destroy](/cli/commands/terraform/destroy)** - Destroy infrastructure safely ## Advanced Commands Documentation for advanced Terraform operations: - **[terraform generate backend](/cli/commands/terraform/generate/backend)** - Generate backend configurations - **[terraform generate varfile](/cli/commands/terraform/generate/varfile)** - Generate variable files from stack configs - **[terraform metadata](/cli/commands/terraform/metadata)** - Extract component metadata ## State Management Complete documentation for Terraform state operations: - **[terraform state](/cli/commands/terraform/state)** - Advanced state manipulation - **[terraform import](/cli/commands/terraform/import)** - Import existing resources - **[terraform workspace](/cli/commands/terraform/workspace)** - Manage Terraform workspaces ## Development Commands Commands for Terraform module development and testing: - **[terraform fmt](/cli/commands/terraform/fmt)** - Format Terraform code - **[terraform validate](/cli/commands/terraform/validate)** - Validate configuration syntax - **[terraform test](/cli/commands/terraform/test)** - Run Terraform tests - **[terraform console](/cli/commands/terraform/console)** - Interactive console for debugging ## Browse All Commands View the complete list of Terraform commands: - **[Terraform command reference](/cli/commands/terraform/usage)** - Full command listing - **[Global flags](/cli/global-flags)** - Flags available to all commands - **[Stack configuration](/stacks)** - How stacks integrate with Terraform ## Visual Documentation Each command page includes: - **Syntax-highlighted code examples** - With your configured theme - **Terminal output screenshots** - Showing real command execution - **Table of arguments and flags** - Quick reference for all options ## Get Involved Found missing details or have suggestions for improving the documentation? - [Open an issue](https://github.com/cloudposse/atmos/issues/new) on GitHub - Share feedback in the [Atmos Community Slack](https://cloudposse.com/slack) --- ## Improved Terraform Command Architecture for Better Validation and Help Atmos terraform commands now use a modern registry pattern that improves flag validation, provides better help text, and sets the foundation for enhanced developer experience across all terraform subcommands. ## What Changed The terraform command infrastructure has been refactored to use Atmos's command registry pattern. This architectural change brings terraform commands in line with other Atmos commands, enabling better tooling and user experience improvements. ## Why This Matters ### Better Flag Validation The new architecture provides stronger type safety and validation for command flags: ```bash # Flags are validated before execution atmos terraform plan my-component -s my-stack --invalid-flag # Error: unknown flag: --invalid-flag ``` Previously, invalid flags could sometimes pass through silently or produce confusing error messages. ### Improved Help Text Help output is now more consistent and automatically respects your terminal theme: ```bash # Clear, themed help for all terraform commands atmos terraform --help atmos terraform plan --help atmos terraform generate varfile --help ``` All help text now uses proper markdown rendering with syntax highlighting, making documentation easier to read directly in your terminal. ### Foundation for Future Enhancements This refactoring enables upcoming improvements: - **Auto-completion** - Shell completion for terraform subcommands and flags - **Better error messages** - Contextual help when commands fail - **Consistent patterns** - Terraform commands work like all other Atmos commands ## What Stays the Same All existing terraform commands continue to work exactly as before: ```bash # All your existing commands work unchanged atmos terraform plan my-component -s my-stack atmos terraform apply my-component -s my-stack atmos terraform generate varfile my-component -s my-stack ``` The changes are entirely internal - your workflows, scripts, and CI/CD pipelines are unaffected. ## Technical Details The refactoring: 1. Migrates terraform commands to the `CommandProvider` interface pattern 2. Implements proper flag parsing and validation infrastructure 3. Unifies help text rendering across all terraform subcommands 4. Preserves backward compatibility with existing command behavior ## Learn More - [Terraform Commands](/cli/commands/terraform/usage) - Complete command reference - [Global Flags](/cli/global-flags) - Flags available to all commands ## Get Involved - Share your feedback in the [Atmos Community Slack](https://cloudposse.com/slack) - [Report issues](https://github.com/cloudposse/atmos/issues/new) on GitHub --- ## Component Mocks for Terraform YAML Lookups Cross-component Terraform lookups are useful precisely when an application component needs the outputs of something like a VPC, cluster, or database. They can also make a local plan or configuration review depend on deployed infrastructure, backend access, and cloud credentials that are irrelevant to the change at hand. Atmos now supports **[component mocks](/stacks/components/mocks)**: literal, component-owned Terraform outputs that you enable explicitly with `--use-mocks`. ## The problem: a small dependency can make a plan non-local Consider an `app` component that takes its VPC ID from a `vpc` component: ```yaml title="stacks/dev.yaml" components: terraform: app: vars: vpc_id: !terraform.state vpc vpc_id ``` Normally, Atmos resolves that lookup from the VPC's real Terraform state. That is the right default for regular operations, but it can be inconvenient when you are developing the application configuration before the VPC exists, reviewing a change offline, or deliberately avoiding access to a remote backend. The old choices were usually to provision the dependency first, change the consumer temporarily, or build an ad hoc fixture outside the component that owns the output. None of those makes the intended fake surface obvious. ## The fix: declare the fake surface next to the producer Declare the outputs a Terraform component can stand in for under `mocks`: ```yaml title="stacks/dev.yaml" components: terraform: vpc: mocks: vpc_id: vpc-local private_subnet_ids: [subnet-a, subnet-b] network: cidr: 10.0.0.0/16 app: vars: vpc_id: !terraform.state vpc vpc_id private_subnet_id: !terraform.output vpc '.private_subnet_ids[0]' ``` Then choose the mock path at invocation time: ```shell atmos describe component app -s dev --use-mocks atmos terraform plan app -s dev --use-mocks ``` Both `!terraform.state` and `!terraform.output` resolve from `vpc.mocks`. The familiar output and YQ-expression syntax stays the same; only the source of the value changes. This is intentionally a narrow model. There are no mock profiles, provider simulations, resource emulation, or consumer-side override DSLs. A component declares the broad fake output surface once, and the command makes an explicit decision to use it. The [advanced quick start](/quick-start/advanced/) uses this pattern for its first, state-free plan: its regional KMS, storage, and messaging components declare provider-shaped outputs, while its normal deploy continues to use real state. ## What mock mode bypasses When a Terraform lookup is mocked, Atmos loads the referenced component's merged `mocks` map and evaluates the requested expression against it. It does **not** initialize Terraform, resolve the referenced component's credentials, read a backend, or populate the real-state lookup cache. That makes mock mode useful for local plans and `describe component` output that should be independent of the remote dependency. It also means `--use-mocks` never silently falls back to real state: a missing mock map or output is an actionable error, not a surprise backend read — unless the expression itself supplies a YQ `//` default, which is honored the same way it is for real state, whether or not the referenced component declares `mocks` at all. Mock values themselves are literal. Atmos does not evaluate templates or YAML functions inside `mocks`, so a mock cannot accidentally call the real dependency it was meant to replace. ## Guardrails keep normal operations normal Mocks are opt-in. Without `--use-mocks`, existing `!terraform.state` and `!terraform.output` behavior is unchanged. The flag is supported by: - [`atmos terraform plan`](/cli/commands/terraform/plan) - [`atmos describe component`](/cli/commands/describe/component) Mutating Terraform commands—including `apply`, `deploy`, and `destroy`—and Terraform passthrough commands reject it before stack resolution. `--use-mocks` also requires YAML function processing to remain enabled. Because `mocks` follows normal component inheritance and deep-merge behavior, a base component can provide shared fixtures while a concrete component replaces only the outputs that differ. ## Try it The repository includes a provider-free example with a producer, a consumer, the normal real-state flow, and the mocked flow: ```shell cd examples/terraform-component-mocks # Resolve app's VPC ID from the vpc mock, without creating state. atmos terraform plan app -s dev --use-mocks # Inspect the final component configuration with the same mock resolution. atmos describe component app -s dev --use-mocks ``` For the normal path, create the producer's state and omit the flag: ```shell atmos terraform apply vpc -s dev atmos terraform plan app -s dev ``` Read more about [`!terraform.state`](/functions/yaml/terraform.state) and [`!terraform.output`](/functions/yaml/terraform.output), or use the example as the smallest starting point for adding mocks to your own stack. For usage and configuration, see [component output mocks](/stacks/components/mocks). --- ## Recover from transient Terraform errors automatically with component retry Provider downloads fail. Registries return 502s. State backends time out. None of that is your code's fault, but when it happens during [`atmos terraform plan`](/cli/commands/terraform/plan) in CI, the only recovery has always been a manual re-run. With this release you can configure per-component retry so transient failures recover automatically — without retrying real Terraform errors. ## What changed Components now accept a top-level `retry:` block. When the captured subprocess output matches one of your `conditions` regex patterns after a failure, Atmos retries the command with the configured backoff. Without `conditions`, no retry happens — a deliberate safety default so a typo never silently retries a real failure. ```yaml components: terraform: vpc: retry: max_attempts: 5 backoff_strategy: exponential initial_delay: 2s max_delay: 30s conditions: - /Bad Gateway/ - /5\d\d / - /connection reset/ - /TLS handshake timeout/ - /could not query provider registry/ ``` ## Why this matters This pattern hits hardest in unattended runs — GitOps pipelines, scheduled compliance scans, fleet operations across many accounts. A single 502 during `terraform init` shouldn't fail your pipeline when a 30-second retry would have succeeded. The implementation is intentionally narrow: - Retries are **pattern-driven**, not blanket. A `terraform plan` exit-code-2 caused by a real configuration error fails immediately because it does not match any pattern you listed. - Each subprocess invocation has its own retry loop. Retrying `init` does not consume the `apply` budget. - The retry block participates in component inheritance, so an abstract component can establish a default policy and concrete components extend or override it. - Output streams normally while it is captured for matching — you still see live terraform progress. ## How to use it Add a `retry:` block to any Terraform component. The minimum useful config is `conditions:` plus `max_attempts:`: ```yaml components: terraform: my-component: retry: max_attempts: 3 conditions: - /Bad Gateway/ ``` Define a default in an abstract component to apply retry across many components without copy-paste: ```yaml components: terraform: base/aws: metadata: type: abstract retry: max_attempts: 3 initial_delay: 2s backoff_strategy: exponential conditions: - /Bad Gateway/ - /5\d\d / - /TLS handshake timeout/ vpc: metadata: component: base/aws # vpc inherits the base retry policy. ``` See the [component retry docs](/stacks/components/terraform/retry) for the full configuration reference, inheritance semantics, and tradeoffs. ## Get involved We'd love to hear which transient error patterns you find yourself adding most often — that signal helps us think about a future "well-known patterns" preset. Open a discussion in the [Atmos repo](https://github.com/cloudposse/atmos) or drop a note in the SweetOps Slack. --- ## Terraform DAG Concurrency Atmos Terraform bulk commands now run through a dependency graph, with optional bounded concurrency for plans and deterministic ordering for multi-component runs. ## What Changed - Routed Terraform [`--all`](/cli/commands/terraform/plan#plan-all-components), [`--components`](/cli/commands/terraform/plan#plan-specific-components), and [`--query`](/cli/commands/terraform/plan#plan-components-by-query) through the scheduler-backed Terraform adapter. - Built Terraform dependency graphs from [`dependencies.components`](/stacks/dependencies/components) first, with legacy `settings.depends_on` as a fallback. - Preserved auth setup, store resolver behavior, YAML function processing, CI hook capture, and per-component output handling on the graph-backed path. - Reversed Terraform `destroy` graph execution so dependents are destroyed before dependencies. - Added optional Terraform plan concurrency with `--max-concurrency`, defaulting to sequential execution. - Added plan log controls: `--failure-mode`, `--log-order`, `--hide=no-changes`, and `--execution-summary-file`. ## How to Use It Run every Terraform component in dependency order: ```shell atmos terraform plan --all -s prod ``` Run a named subset while preserving dependency edges between selected components: ```shell atmos terraform plan --components vpc,eks/cluster,eks/apps -s prod ``` Run a query-selected subset: ```shell atmos terraform plan --query '.settings.tier == "network"' -s prod ``` Destroy in reverse dependency order: ```shell atmos terraform destroy --all -s prod ``` Enable bounded plan concurrency: ```shell atmos terraform plan --all -s prod --max-concurrency 4 ``` Group each component's logs after it finishes, suppress unchanged plan output, and write a machine-readable execution summary: ```shell atmos terraform plan --all -s prod \ --max-concurrency 4 \ --failure-mode keep-going \ --log-order grouped \ --hide=no-changes \ --execution-summary-file /tmp/atmos-plan-summary.json ``` ## Why This Matters - Bulk Terraform commands now use one dependency-aware execution path instead of separate routing for each selection mode. - Serial runs are deterministic because components are ordered by the graph, not by map iteration. - Large projects can speed up plan feedback by running independent components concurrently. - Concurrent output remains readable because stdout and stderr are isolated per component. - `--max-concurrency` defaults to `1`, preserving existing sequential behavior unless operators opt in. - `--failure-mode fail-fast` remains the default; `--failure-mode keep-going` lets independent graph branches continue after a failure. - Interactive identity selection is rejected when concurrency is greater than `1`. - Concurrent mutating operations require `-auto-approve` when enabled. ## Get Involved Try the new graph-backed path on a lower environment first. Feedback and testing reports are welcome, especially for large dependency graphs, query-selected runs, and CI pipelines that plan many components at once. --- ## Bulk `terraform init --all` and `--affected` Bulk commands like `terraform apply`, `plan`, and `destroy` could already run across every component in dependency order with [`--all`](/cli/commands/terraform/plan#plan-all-components), but `init` couldn't — reinitializing a whole stack meant scripting a loop over components yourself, or falling back to one-at-a-time runs. ## The Problem - Bulk execution flags `--all` and [`--affected`](/cli/commands/terraform/plan#plan-affected-components) landed on `apply`/`plan`/`destroy` when Terraform bulk execution moved onto the scheduler-backed dependency graph, but `init` was left out. - Reinitializing every component after a provider upgrade, or bootstrapping a new environment, meant looping [`atmos terraform init -s `](/cli/commands/terraform/init) by hand. - The scheduler also capped concurrency to `1` for any subcommand it didn't explicitly recognize, so there was no way to route around it either. ## The Fix - `terraform init` now accepts `--all`, `--affected`, `--max-concurrency`, `--failure-mode`, and `--log-order`, matching `destroy`. - Init keeps the natural forward dependency order — prerequisites before dependents — unlike `destroy`, which reverses the graph. - Concurrent bulk init automatically disables the shared provider plugin cache for worker subprocesses, since sharing it isn't safe across concurrent `terraform init` runs. ## How to Use It ```shell # Initialize every Terraform component, in dependency order atmos terraform init --all -s prod # Initialize only the affected components atmos terraform init --affected # Run independent components concurrently atmos terraform init --all -s prod --max-concurrency 4 # Keep going past a failed component instead of stopping atmos terraform init --all -s prod --failure-mode keep-going ``` ## Get Involved Have a bulk-execution flag you'd like to see on another command? Open an issue or join the discussion on [GitHub](https://github.com/cloudposse/atmos). --- ## Lifecycle hooks for terraform init, and --skip-hooks now works for before-* events Atmos now supports `before.terraform.init` and `after.terraform.init` lifecycle hooks, and `--skip-hooks` is finally honored for _before-_ hooks across `plan`, `apply`, and `deploy`. ## What Changed - **New init lifecycle events.** You can now run hooks around the explicit [`atmos terraform init`](/cli/commands/terraform/init) command: ```yaml terraform: hooks: pre-init: events: [before.terraform.init] kind: command command: ./scripts/check-tooling.sh post-init: events: [after.terraform.init] kind: command command: ./scripts/notify-init-done.sh ``` `after.terraform.init` is brand new, and `before.terraform.init` — previously listed in the docs but never dispatched to user hooks — now actually fires. - __`--skip-hooks` works for before-_ hooks._\* Previously `--skip-hooks` only skipped `after.*` hooks; `before.terraform.plan` / `before.terraform.apply` / `before.terraform.deploy` hooks ran anyway. Now `--skip-hooks` (skip all) and `--skip-hooks=name1,name2` (skip by name) are honored symmetrically for both before and after events. ## Why This Matters `--skip-hooks` is a global flag bound to Viper inside `RunE`, but before-\* hooks run earlier in `PreRunE` — so the skip decision never saw the CLI value and before-hooks fired regardless. The flag is now resolved directly from the parsed command (with `ATMOS_SKIP_HOOKS` / config as fallback), the same way `--ci` and `--verbose` are read, making skipping reliable everywhere hooks run. Init hooks close a gap in the lifecycle: teams that vendor sources, validate tooling, or notify systems around `terraform init` can now do it declaratively in stack config instead of wrapping the command. ## How to Use It Define hooks once at the top-level `terraform.hooks` scope (inherited by every component) or per-component, and skip them on demand: ```shell # Skip every hook for this run. atmos terraform plan vpc -s plat-ue2-prod --skip-hooks # Skip only the named hooks. atmos terraform apply vpc -s plat-ue2-prod --skip-hooks=cost,policy ``` The `before.terraform.init` / `after.terraform.init` events fire around the explicit `atmos terraform init` command — not the implicit init that `plan` and `apply` run automatically. See the [Hooks documentation](/stacks/hooks) for the full list of supported events. ## Get Involved Have a lifecycle event you wish Atmos exposed? Open an issue or join the discussion on [GitHub](https://github.com/cloudposse/atmos). --- ## Stop retyping -lock-timeout on every terraform command Terraform locks state before it writes to it, and by default it gives up the instant that lock is already held — no retry, no wait. That's fine for a single engineer running commands one at a time. It falls apart the moment two pipelines, or a pipeline and an engineer, touch the same component's state around the same moment: whichever process loses the race just fails, even though the lock would have cleared in a few seconds. ## The Problem Terraform and OpenTofu support `-lock-timeout=` to poll for a held lock instead of failing immediately, but it has to be typed on every single invocation. `TF_CLI_ARGS_` can automate it, but it is a single flat string rather than a structured, independently merged `flags:` block — so a component that wants to override one flag has to restate the rest or lose them. Teams running concurrent CI matrices, or Atmos Pro-driven deployments, would hit avoidable failures from Terraform's `0s` default when no lock timeout is configured. ## The Fix Terraform CLI execution flags — `lock_timeout`, `lock`, `parallelism`, `refresh`, and `compact_warnings` — can now be declared once under a `flags:` block: globally in `atmos.yaml`, for an entire stack, or for a single component, with each layer overriding the one before it. An `ATMOS_COMPONENTS_TERRAFORM_FLAGS_*` environment variable can override the `atmos.yaml` default too — the same environment-variable handling every other Atmos setting already gets, not a special case invented for these five flags. An explicit flag typed directly on the command line still wins over every declared default, so nothing about existing one-off usage changes. Atmos only injects a flag into commands that actually support it — for example `-refresh` isn't valid when applying a saved plan, and `terraform import` doesn't accept `-parallelism` — so you don't have to track those exceptions yourself. ## How to Use It ```yaml # atmos.yaml — fleet-wide default components: terraform: flags: lock_timeout: "5m" parallelism: 10 # stack manifest — applies to every terraform component in this stack terraform: flags: lock_timeout: "5m" components: terraform: vpc: # per-component override flags: lock_timeout: "10m" ``` ```shell # one-off override still works exactly as before, and always wins atmos terraform plan vpc -s plat-ue2-dev -- -lock-timeout=30s # environment variable overrides the atmos.yaml default — handy for a per-CI-job # tweak without touching the fleet-wide config ATMOS_COMPONENTS_TERRAFORM_FLAGS_LOCK_TIMEOUT=2m atmos terraform plan vpc -s plat-ue2-dev ``` ## Why not just use TF\_CLI\_ARGS? Yes, you could. Atmos's `env:` section also merges by stack and component. Set `env: { TF_CLI_ARGS_plan: "-lock-timeout=5m" }`, and it scopes the same way `flags:` does. But `TF_CLI_ARGS_plan` is one flat string with no separate fields. If a component overrides `-parallelism`, it must also restate `-lock-timeout`, or the component loses that value. Fields in `flags:` merge one at a time. A component can override `parallelism` alone and still inherit `lock_timeout` from the stack. Every other Atmos section merges the same way. For usage and configuration, see [atmos terraform plan](/cli/commands/terraform/plan). ## Get Involved See the [Terraform Configuration](/cli/configuration/components/terraform#flags) docs for the full field reference, defaults, and environment variable overrides. Have feedback on this feature? [Open an issue](https://github.com/cloudposse/atmos/issues) or join the conversation in the [Cloud Posse community Slack](https://cloudposse.com/slack). --- ## New --format Flag for Terraform Output The [`atmos terraform output`](/cli/commands/terraform/output) command now supports a [`--format`](/cli/commands/terraform/output#flags) flag, making it easy to export Terraform outputs in various formats for use in CI/CD workflows, scripts, and configuration files. ## The Problem Previously, extracting multiple Terraform outputs for use in GitHub Actions required repetitive, verbose commands: ```yaml - name: Get Terraform outputs run: | echo "url=$(atmos terraform output app -s preview --skip-init -- -raw url)" >> $GITHUB_OUTPUT echo "bucket=$(atmos terraform output app -s preview --skip-init -- -raw bucket)" >> $GITHUB_OUTPUT echo "api_key=$(atmos terraform output app -s preview --skip-init -- -raw api_key)" >> $GITHUB_OUTPUT ``` Each output required a separate command invocation, which was slow and cumbersome. ## The Solution With the new `--format` flag, you can export all outputs in a single command: ```yaml - name: Get Terraform outputs run: atmos terraform output app -s preview --skip-init --format=env --output-file=$GITHUB_OUTPUT ``` ## Supported Formats | Format | Output Style | Use Case | |--------|--------------|----------| | `json` | `{"key": "value"}` | Machine parsing, piping to `jq` | | `yaml` | `key: value` | Human-readable, config files | | `hcl` | `key = "value"` | Terraform locals or tfvars files | | `env` | `key=value` | GitHub Actions `$GITHUB_OUTPUT` | | `dotenv` | `key='value'` | `.env` files with quoting | | `bash` | `export key='value'` | Shell sourcing with `eval $(...)` | | `csv` | `key,value` | Spreadsheets, data processing | | `tsv` | `keyvalue` | Tab-delimited for easy parsing | ## Examples ### GitHub Actions Workflow Share outputs between steps: ```yaml jobs: deploy: steps: - name: Deploy infrastructure run: atmos terraform apply app -s preview --auto-approve - name: Export outputs id: terraform run: atmos terraform output app -s preview --skip-init --format=env --output-file=$GITHUB_OUTPUT - name: Use outputs run: | echo "Deployed to: ${{ steps.terraform.outputs.url }}" curl ${{ steps.terraform.outputs.url }}/health ``` ### Shell Script Integration Source outputs directly in bash: ```bash # Export outputs as environment variables eval $(atmos terraform output vpc -s prod --format=bash) # Now use them echo "VPC ID: $vpc_id" aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpc_id" # Use --uppercase for standard ENV_VAR naming convention eval $(atmos terraform output vpc -s prod --format=bash --uppercase) echo "VPC ID: $VPC_ID" ``` ### Flatten Nested Outputs Use [`--flatten`](/cli/commands/terraform/output#flags) to expand nested maps and arrays into individual key/value pairs: ```bash # Without --flatten: {"config": {"host": "localhost", "port": 3000}} # Output: config={"host":"localhost","port":3000} # With --flatten: expands nested maps atmos terraform output app -s prod --format=env --flatten # Output: config_host=localhost # config_port=3000 # Arrays are flattened with numeric indices # Input: {"subnets": [{"id": "subnet-1"}, {"id": "subnet-2"}]} # Output: subnets_0_id=subnet-1 # subnets_1_id=subnet-2 # Combine --flatten with --uppercase for standard ENV_VAR naming eval $(atmos terraform output app -s prod --format=bash --flatten --uppercase) echo "Config host: $CONFIG_HOST" echo "Config port: $CONFIG_PORT" ``` ### Write to File Save outputs to a file for later use: ```bash # Save as .env file atmos terraform output app -s staging --format=dotenv --output-file=.env # Save as JSON for processing atmos terraform output app -s staging --format=json --output-file=outputs.json # Save as HCL for Terraform locals atmos terraform output vpc -s prod --format=hcl --output-file=vpc_outputs.auto.tfvars ``` ## Backward Compatibility When `--format` is not specified, the command passes through to native Terraform/OpenTofu, preserving existing behavior. The `-json` and `-raw` flags continue to work as before. ## Get Started The `--format` flag is available now. See the [terraform output documentation](/cli/commands/terraform/output) for more details. --- ## Zero-Config Terraform Provider Caching Atmos now automatically caches Terraform providers across all components, dramatically reducing `terraform init` times and network bandwidth. This feature is enabled by default with zero configuration required. ## Why Provider Caching Matters In large Atmos projects with many components, each `terraform init` downloads the same providers repeatedly. For the AWS provider alone, this can mean downloading 300+ MB per component. With provider caching, Atmos downloads each provider version once and reuses it across all components. ## How It Works When you run any Terraform command, Atmos automatically: 1. Sets `TF_PLUGIN_CACHE_DIR` to `~/.cache/atmos/terraform/plugins` 2. Sets `TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE=true` (required by Terraform) Terraform then downloads providers to this shared cache and creates symlinks in each component's `.terraform` directory. ## Zero Configuration Provider caching works out of the box. Just upgrade Atmos and run your commands as usual: ```bash atmos terraform init mycomponent -s dev ``` The first init downloads providers to the cache. Subsequent inits for other components reuse the cached providers instantly. ## Configuration Options While caching works with no configuration, you can customize its behavior in `atmos.yaml`: ```yaml components: terraform: # Disable automatic caching (default: true) plugin_cache: false # Use a custom cache directory (default: ~/.cache/atmos/terraform/plugins) plugin_cache_dir: /shared/terraform/plugin-cache ``` Or via environment variables: ```bash export ATMOS_COMPONENTS_TERRAFORM_PLUGIN_CACHE=false export ATMOS_COMPONENTS_TERRAFORM_PLUGIN_CACHE_DIR=/custom/path ``` ## Respecting User Overrides If you already have `TF_PLUGIN_CACHE_DIR` set in your environment or in the `env:` section of `atmos.yaml`, Atmos respects your configuration and does not override it. ## Cleaning the Cache To free disk space or force providers to be re-downloaded, use the new `--cache` flag: ```bash # Clean the shared plugin cache atmos terraform clean --cache # Clean with force (no confirmation prompt) atmos terraform clean --cache --force ``` ## Performance Impact In our testing with projects containing 50+ components: - **First init**: Same as before (downloads all providers) - **Subsequent inits**: 10-50x faster (symlinks from cache) - **Disk savings**: Significant reduction in duplicate provider binaries ## Related Documentation - [Terraform Configuration](/cli/configuration/components/terraform) - Full configuration reference - [terraform clean](/cli/commands/terraform/clean) - Cache cleanup options --- ## Testing Custom Terraform Providers with Atmos Components If you develop Terraform providers, you can now test them locally with Atmos-managed components using Terraform's development overrides feature. This enables rapid iteration without publishing development versions to a registry. ## Overview When developing Terraform providers, you need to test them with real infrastructure code. Terraform's development overrides let you point to locally-built provider binaries instead of downloading from a registry. This works with Atmos components. ## How It Works Development overrides use two separate mechanisms that work together: ### 1. Provider Binary Location (Terraform CLI Config) Create a `.terraformrc` file in your component directory: ```hcl provider_installation { dev_overrides { "registry.terraform.io/myorg/myprovider" = "/absolute/path/to/provider/bin" } direct {} } ``` This tells Terraform where to find your locally-built provider binary instead of downloading it from a registry. ### 2. Provider Configuration (Atmos Stack Manifests) Configure provider behavior using the `providers` section in your stack manifests as usual: ```yaml components: terraform: myapp: providers: myprovider: endpoint: "https://api.example.com" api_token: "{{ .settings.api_token }}" env: TF_CLI_CONFIG_FILE: ".terraformrc" ``` Atmos continues to generate `providers_override.tf.json` for provider configuration, while Terraform uses your local binary via `dev_overrides`. ## Setup **1. Create `.terraformrc` in your component directory** Place the file in your component folder (e.g., `components/terraform/myapp/.terraformrc`): ```hcl provider_installation { dev_overrides { "registry.terraform.io/myorg/myprovider" = "/absolute/path/to/provider/bin" } direct {} } ``` Atmos executes Terraform from the component directory, so the file must be there. **2. Configure the environment variable** In your stack manifest: ```yaml components: terraform: myapp: env: TF_CLI_CONFIG_FILE: ".terraformrc" ``` **3. Build and test your provider** ```bash # Build provider cd providers/terraform-provider-myprovider go build -o bin/terraform-provider-myprovider # Test with Atmos cd ../../ atmos terraform plan myapp -s dev ``` Terraform uses your local binary. Atmos handles configuration. ## Workflow ```bash # Edit provider code vim providers/terraform-provider-myprovider/provider.go # Rebuild cd providers/terraform-provider-myprovider go build -o bin/terraform-provider-myprovider # Test with Atmos cd ../../ atmos terraform plan myapp -s dev ``` Changes are available immediately without publishing. ## Key Points **File location**: The `.terraformrc` file goes in the component directory (e.g., `components/terraform/myapp/.terraformrc`), not the repository root. Add it to your component's `.gitignore`. **Template for teams**: Provide `.terraformrc.example` with placeholder paths that team members update for their environment. **CLI configuration only**: The `provider_installation` block must be in a Terraform CLI config file, not in `.tf` files. **Absolute paths**: Use absolute paths to the directory containing the provider binary. **Warning message**: Terraform shows "Warning: Provider development overrides are in effect" when using local providers. ## Documentation Full documentation includes setup instructions, team collaboration patterns, and troubleshooting: - [Provider Configuration Guide](/components/terraform/providers) - [Terraform Development Overrides](https://developer.hashicorp.com/terraform/cli/config/config-file#development-overrides-for-provider-developers) ## Related - [GitHub Issue #1726](https://github.com/cloudposse/atmos/issues/1726) - [Documentation PR #1730](https://github.com/cloudposse/atmos/pull/1730) --- ## Manage the Terraform CLI Config from atmos.yaml Atmos can now manage the **Terraform/OpenTofu runtime configuration (RC)** for you. Declare it once under `components.terraform.rc`, and Atmos writes a temporary CLI config file and points the subprocess at it via `TF_CLI_CONFIG_FILE` and `TOFU_CLI_CONFIG_FILE` — no hand-managed dotfiles, no manual env exports. ## What is the "RC" file? "RC" is **runtime configuration** — the same convention as `.bashrc` or `.npmrc`. Terraform and OpenTofu read a CLI configuration file (named [`.terraformrc` / `terraform.rc`](https://developer.hashicorp.com/terraform/cli/config/config-file) for Terraform and [`.tofurc` / `.terraformrc`](https://opentofu.org/docs/cli/config/config-file/) for OpenTofu) that controls _how the CLI itself behaves_ — distinct from your `.tf` code, which describes infrastructure. It is where you configure things like: - **`provider_installation`** — where providers come from: network mirrors, filesystem mirrors, or going `direct` to registries (and include/exclude rules for supply-chain control). - **`host`** — service-discovery overrides, e.g. redirecting a registry's `modules.v1` endpoint. - **`credentials`** — API tokens for private registries. - **`plugin_cache_dir`** — a shared provider plugin cache. This file is normally a hand-edited dotfile in your home directory, pointed to by the `TF_CLI_CONFIG_FILE` environment variable. Atmos now lets you declare it per stack/component instead. ## What changed Until now there was no way to declare this runtime configuration in Atmos — you had to hand-author a `.terraformrc` and export `TF_CLI_CONFIG_FILE` yourself. Now you declare it in your stack configuration: ```yaml components: terraform: rc: enabled: true # Rendered verbatim into Terraform's native CLI configuration. provider_installation: - network_mirror: url: "https://terraform-mirror.example.com/" - direct: exclude: - "registry.terraform.io/hashicorp/*" host: "registry.terraform.io": services: "modules.v1": "https://modules.example.com/v1/modules/" ``` Atmos renders that into the native HCL CLI-config grammar and exposes it to `terraform`/`tofu` automatically. No Terraform code, provider declarations, or module sources change. ## How it works When `components.terraform.rc.enabled` is set, then for each `terraform`/`tofu` invocation Atmos: 1. **Renders** the `rc:` section into Terraform's native CLI-config (HCL) grammar — preserving block semantics like the ordered `provider_installation` methods and labeled `host`/`credentials` blocks. 2. **Writes** it to a temporary file with an atomic write (so a partial file is never observed). 3. **Exposes** it to the subprocess by setting both `TF_CLI_CONFIG_FILE` and `TOFU_CLI_CONFIG_FILE` to that file. 4. **Cleans up** the temp file after the whole pipeline finishes — it survives `init`, `workspace`, and `plan`/`apply` within a single invocation. If you already manage your own CLI config via either env var (or the legacy `TERRAFORM_CONFIG`), Atmos detects it and renders nothing, deferring to you. ## A passthrough, not a new abstraction The `rc:` section is **near-opaque**: keys map directly to Terraform CLI-config directives (`provider_installation`, `host`, `credentials`, `plugin_cache_dir`, …). New directives work immediately without waiting for an Atmos release, because Atmos renders the section rather than modeling it. ## Terraform and OpenTofu, no guessing Atmos sets **both** `TF_CLI_CONFIG_FILE` and `TOFU_CLI_CONFIG_FILE` to the generated file, so your config is honored whether the resolved binary is `terraform` or `tofu` — no heuristic about which tool you're running. If you already manage your own CLI config (via either env var or the legacy `TERRAFORM_CONFIG`), Atmos detects it and defers to you. ## The foundation for the registry cache RC management is useful on its own, and it's also the injection point the new [Terraform Registry Cache](/changelog/terraform-registry-cache) builds on — the cache contributes its `network_mirror` and module `host` directives into this same generated CLI config. Enable the cache and the RC plumbing is driven for you. ## Get involved This feature is **experimental**. Try declaring a `components.terraform.rc` block in a stack and inspect the generated config Terraform receives. Feedback welcome. For usage and configuration, see [Terraform Configuration](/cli/configuration/components/terraform). --- ## Terraform Registry Cache: Reproducible Infrastructure Builds Atmos can now transparently **cache Terraform and OpenTofu providers and modules** behind a single feature flag. Turn it on and repeated runs — local or CI — stop re-downloading the same artifacts, keep working when upstream registries are slow or down, and capture the exact versions a deployment used so builds stay reproducible. No changes to your Terraform code, provider declarations, or module sources. See it in action: [View the full example](/examples/caching) ## Your dependencies are artifacts — treat them that way Mature software projects treat their dependencies as **versioned, cached, reproducible artifacts**: pulled once, stored close by, and replayed for builds you can reproduce months or years later. Infrastructure code has had no equivalent. Providers and modules are re-fetched from upstream on every run, on every machine, in every CI job — and if a version moves or a registry has a bad day, your "reproducible" infrastructure isn't. This release brings that artifact model to your infrastructure. **You own your infrastructure and its dependencies** — Atmos just facilitates capturing, storing, and reproducing them. Caching is simply the mechanism that makes **reproducible infrastructure** practical, and it's built on a shared artifact-store + caching foundation that Atmos also uses for CI artifacts and (soon) bundles. ## Terraform has no caching story for modules Terraform ships a shared **provider** plugin cache (`TF_PLUGIN_CACHE_DIR`), and providers are individually large, so that helps. But there is **no equivalent for modules** — and there's a structural reason why. Terraform vendors modules into each root module's own data directory (`.terraform/modules/`), and **two root modules can't share a `.terraform` data directory without conflict**. So there's nowhere to put a shared module cache: every root module downloads its own private copy. Across a real infrastructure that adds up fast. Any one component might pull only a handful of modules, but multiply that by **many instances of the same root modules**, modules **reused across many roots**, and every run on every machine, and the same sources get re-resolved and re-cloned **hundreds of times in aggregate**. There is no sharing across roots, no sharing across runs, and no sharing across machines. Atmos sidesteps the data-directory problem by caching one layer up — at the **registry and download** layer, before modules ever land in `.terraform/` — so every root module still gets its own private copy, but the bytes come from a shared local cache instead of the network. The cost isn't just bytes — it's **sheer request volume**. Every module is a registry round-trip (version listing, download resolution) before anything is fetched. The more modules your infrastructure pulls, the longer `init` takes and the more chances there are for a **transient error** — a slow registry, a rate limit, a flaky network — to fail the whole run. Terraform's answer to that is to make you re-run. What Atmos implements is an elegant, reusable cache that fills this gap for **both providers and modules**, and does so safely under **concurrent access across multiple Atmos and Terraform processes** — many readers, one downloader per artifact — so parallel components and CI jobs share one warm cache instead of stampeding the registry. Caching is one move under a larger umbrella: making Terraform runs **reproducible, fast, resilient, and secure by depth**. Capturing the exact artifacts a deployment used (reproducibility), cutting redundant downloads and request volume (performance), surviving registry outages and transient errors (resiliency), and funneling provider/module fetches through a single auditable egress you can restrict (defense in depth) are not separate features — they're incremental improvements that compound into stable infrastructure. The registry cache advances all of them at once. ## What you get That framing is where the concrete wins come from: - **Speed & bandwidth** — repeated and CI runs reuse previously downloaded providers and modules instead of pulling them again. - **Reliability** — runs keep working during registry outages, and when a version disappears upstream you still have it. - **Reproducibility** — the exact providers and modules a deployment used are preserved, so it can be replayed later. - **Toward air-gapped** — a warm cache is the on-disk closure a future "atmos bundle" is built from. ## Turn it on ```yaml components: terraform: cache: enabled: true ``` That's it. Atmos resolves a cache location, starts an ephemeral local network-mirror proxy on `127.0.0.1`, generates the Terraform CLI config that routes through it, populates the cache on demand, and tears the proxy down on exit. The cache persists on disk for the next run. When bytes are served from cache, Atmos prints a one-line **savings report** before exiting. You can tune the location and freshness: ```yaml components: terraform: cache: enabled: true location: ~/.cache/atmos # defaults to the XDG cache dir metadata_ttl: 24h # how long registry metadata stays fresh stale_while_revalidate: 168h # serve stale metadata while revalidating ``` ## How it works Behind the scenes, Atmos starts a small HTTP proxy on localhost that acts as both a **provider network mirror** and a **module registry**. It manages Terraform's CLI configuration (the RC file) for you and points the tool at that local proxy — so every provider and module request quietly flows through it and checks the cache first. There's no daemon and nothing to run: the proxy spins up when a run starts and goes away when it ends, leaving the warm cache on disk. Hits are served straight from disk; misses are fetched once and stored. Registry metadata honors **TTLs and stale-while-revalidate**, so listings stay fresh without paying the round-trip every time, and responses stay snappy. And because parallel components and CI jobs hit the same cache, Atmos handles **file locking across processes and operating systems** — one downloader, many readers, no corrupted half-downloads. Because it speaks Terraform's own mirror protocols and stores providers in the standard mirror layout, the cache plays nicely with `terraform providers mirror` and offline `filesystem_mirror` too. ## What's cached - **Providers** — fully cached via the Provider Network Mirror Protocol, stored in the canonical `filesystem_mirror` layout so `terraform providers mirror` and offline `filesystem_mirror` interchange with the proxy. - **Modules** — registry version listings and download resolution are cached, and HTTP-archive modules are cached in full. Module sources that resolve to `git::` (the common case for the public registry and mono-repos) pass through unchanged today — a dedicated **git mirror** will complete that picture in a follow-up. ## Works with Terraform and OpenTofu — including private registries Both tools speak the same protocols and headers, and the mirrors are host-keyed, so `registry.terraform.io`, `registry.opentofu.org`, and private registry hosts all just work. The proxy forwards your request headers and credentials to upstream (honoring `TF_TOKEN_` and `TOFU_TOKEN_`), and forwards Terraform's own `User-Agent` verbatim so registries still see exactly who's calling. ## Manage the cache ```shell atmos terraform cache list # list cached providers and modules atmos terraform cache stats # size, object count, provider/module breakdown atmos terraform cache prune # drop stale metadata (keeps immutable artifacts) atmos terraform cache delete ``` `stats` reports what the filesystem can tell us — size, counts, breakdown — and intentionally not a hit rate: a hit is a per-run event, surfaced by the savings report, not stored state. ## Get involved This feature is **experimental** while we expand it (object-storage backends for shared CI/team caches, `cache warm`/`export`/`import`, and the git mirror are next). Try `components.terraform.cache.enabled: true` on a repo with a warm cache and watch the savings report. Feedback and issues are very welcome. For usage and configuration, see [atmos terraform cache](/cli/commands/terraform/cache). --- ## Pin Terraform and Provider Versions in Stack Configuration You can now pin Terraform and provider versions directly in your stack configuration. Atmos generates `terraform_override.tf.json` files with `required_version` and `required_providers` blocks, giving you centralized control over infrastructure versioning. ## What Changed Previously, pinning Terraform and provider versions required modifying component source code or maintaining separate override files. Now you can declare versions in your stack YAML: ```yaml components: terraform: vpc: metadata: component: vpc vars: name: main-vpc terraform: required_version: ">= 1.10.1" required_providers: aws: source: "hashicorp/aws" version: "~> 5.0" random: source: "hashicorp/random" version: ">= 3.0" ``` ## How It Works When you run [`atmos terraform generate required-providers`](/cli/commands/terraform/generate), Atmos creates a `terraform_override.tf.json` file in your component directory: ```json { "terraform": { "required_version": ">= 1.10.1", "required_providers": { "aws": { "source": "hashicorp/aws", "version": "~> 5.0" }, "random": { "source": "hashicorp/random", "version": ">= 3.0" } } } } ``` This override file merges with your component's existing Terraform configuration, allowing you to pin versions without modifying component source code. ## Stack Inheritance Version constraints follow Atmos's standard inheritance model. Define base versions in a catalog and override per-environment: ```yaml # stacks/catalog/terraform.yaml terraform: required_version: ">= 1.9.0" required_providers: aws: source: "hashicorp/aws" version: "~> 5.0" # stacks/deploy/prod.yaml import: - catalog/terraform terraform: required_version: ">= 1.10.1" # Override for prod ``` ## CLI Usage Generate the override file for a specific component and stack: ```bash atmos terraform generate required-providers vpc --stack dev-us-east-1 ``` Or specify a custom output path: ```bash atmos terraform generate required-providers vpc --stack dev-us-east-1 \ --file /custom/path/terraform_override.tf.json ``` ## Why This Matters - **Centralized version management** - Control versions across all components from stack configuration - **Environment-specific versions** - Pin stricter versions in production while allowing flexibility in development - **No component modification** - Override files work without changing component source code - **Inheritance support** - Define base versions in catalogs and override where needed For usage and configuration, see [Provider Generation](/components/terraform/providers). --- ## Just-in-Time Component Vendoring with source Atmos now supports just-in-time (JIT) vendoring of components directly from stack configuration using the top-level `source` field. This works for **Terraform**, **Helmfile**, and **Packer** components. Declare component sources inline without requiring separate `component.yaml` files—components are automatically downloaded on first use. See it in action: [View the full example](/examples/source-provisioning) ## What Changed We've added automatic JIT source provisioning and new `source` command groups for terraform, helmfile, and packer. When you run any command, Atmos automatically provisions the component source if the directory doesn't exist: ```yaml components: terraform: vpc: source: uri: github.com/cloudposse/terraform-aws-components//modules/vpc version: 1.450.0 included_paths: - "*.tf" excluded_paths: - "*.md" - "tests/**" helmfile: nginx: source: uri: github.com/cloudposse-archives/helmfiles//releases/nginx-ingress version: 0.126.0 included_paths: - "*.yaml" packer: eks-ami: source: uri: github.com/aws-samples/amazon-eks-custom-amis version: main included_paths: - "*.pkr.hcl" ``` The new commands are available for each component type: **Terraform:** - [`atmos terraform source pull`](/cli/commands/terraform/source/pull) - Vendor a component from source configuration (use `--force` to re-vendor) - [`atmos terraform source list`](/cli/commands/terraform/source/list) - List components with source configured - [`atmos terraform source describe`](/cli/commands/terraform/source/describe) - View source configuration - [`atmos terraform source delete`](/cli/commands/terraform/source/delete) - Remove vendored source directory **Helmfile:** - [`atmos helmfile source pull`](/cli/commands/helmfile/source/pull) - [`atmos helmfile source list`](/cli/commands/helmfile/source/list) - [`atmos helmfile source describe`](/cli/commands/helmfile/source/describe) - [`atmos helmfile source delete`](/cli/commands/helmfile/source/delete) **Packer:** - [`atmos packer source pull`](/cli/commands/packer/source/pull) - [`atmos packer source list`](/cli/commands/packer/source/list) - [`atmos packer source describe`](/cli/commands/packer/source/describe) - [`atmos packer source delete`](/cli/commands/packer/source/delete) ## Why This Matters Previously, vendoring components required maintaining separate `component.yaml` files alongside your stack configuration. This created maintenance overhead and made it harder to: 1. **Version per environment** - Different environments often need different component versions 2. **Keep configuration colocated** - Source information was separated from component configuration 3. **Vendor on demand** - Components had to be pre-vendored before use With `source`, you can now: - **Declare sources inline** in your stack manifests - **Override versions per environment** using stack inheritance - **Vendor just-in-time** when you need the component - **Filter files** with `included_paths` and `excluded_paths` ## No Vendored Components Committed to Git With source-based versioning, you no longer need to commit vendored component code to your Git repository if that's not adding value for your workflow. Components are downloaded just-in-time during execution instead of being pre-vendored and checked in. This means: **Benefits:** - **Smaller repositories** - No vendored Terraform code bloating your git history - **Faster clones** - Particularly helpful for large component libraries - **Simplified PRs** - Version changes are single-line config updates, not large diffs **Tradeoffs:** - **No local immutable copy** - Components aren't in your repo, so you lose the audit trail in Git - **No pre-deploy diff review** - Can't review component code changes before deployment - **AI coding assistants lack context** - Tools like Claude Code, Cursor, and GitHub Copilot work significantly better with vendored code—they have full context of your components for navigation, understanding dependencies, and accurate suggestions - **Network dependency** - Requires network access during execution For teams that value having vendored code committed for audit trails, code review, AI-assisted development, or offline deployments, traditional [vendoring](/vendor) remains the right choice. Source-based versioning is ideal when the operational overhead of vendoring outweighs its benefits. ## How to Use It ### Automatic Provisioning (Default) Add `source` to your component configuration: ```yaml # stacks/dev.yaml components: terraform: vpc: source: uri: github.com/cloudposse/terraform-aws-components//modules/vpc version: 1.450.0 vars: cidr_block: "10.0.0.0/16" ``` Then just run terraform—the source is provisioned automatically: ```shell atmos terraform plan vpc --stack dev # → Auto-provisions source for component 'vpc' # → Successfully auto-provisioned source to components/terraform/vpc # → Terraform runs ``` ### Explicit CLI Commands For fine-grained control, use the source CLI commands: ```shell atmos terraform source pull vpc --stack dev ``` ### Version Per Environment Use stack inheritance to set different versions per environment: ```yaml # stacks/catalog/vpc/defaults.yaml components: terraform: vpc/defaults: source: uri: github.com/cloudposse/terraform-aws-components//modules/vpc version: 1.450.0 # stacks/dev.yaml - use latest components: terraform: vpc: metadata: inherits: [vpc/defaults] source: version: 1.451.0 # Override for dev # stacks/prod.yaml - pin to stable components: terraform: vpc: metadata: inherits: [vpc/defaults] # Uses inherited version 1.450.0 ``` ### Supported Source Types The source provisioner uses go-getter and supports: - Git repositories (GitHub, GitLab, Bitbucket) - S3 buckets - GCS buckets - HTTP/HTTPS URLs - OCI registries ## Combining with Workdir Isolation The `source` provisioner works seamlessly with the [workdir provisioner](/changelog/component-workdir-isolation) for concurrent operations across all component types (terraform, helmfile, and packer). When both are enabled, you get the best of both worlds: 1. **Source provisioner** vendors the component from a remote location 2. **Workdir provisioner** creates an isolated execution directory ### Why Combine Them? When running `terraform plan` on the same component across multiple stacks concurrently, you need isolation to prevent conflicts in `.terraform/` directories and state files. The workdir provisioner provides this isolation. With remote sources, different stacks might use different versions of the same component. The combination ensures: - Each stack gets the correct component version. - Each execution runs in its own isolated directory. - No conflicts between concurrent operations. ### Configuration Example Enable both `source` and `provision.workdir` for a component: ```yaml components: terraform: vpc: source: uri: github.com/cloudposse/terraform-aws-components//modules/vpc version: 1.450.0 provision: workdir: enabled: true vars: cidr_block: "10.0.0.0/16" ``` ### Workflow With both source and workdir enabled, everything happens automatically: ```shell # Just run terraform - both source and workdir are provisioned automatically atmos terraform plan vpc -s dev # → Source provisioner: downloads to .workdir/terraform/dev-vpc-bb03116d/ (directly to workdir) # → Terraform runs in isolated workdir ``` When source and workdir are both configured, the source provisioner downloads directly to the workdir location (`.workdir/terraform/--/`), skipping the intermediate component directory. For explicit control, you can still use the CLI: ```shell # Explicit source pull (without workdir, goes to components/terraform/vpc/) atmos terraform source pull vpc --stack dev ``` This means you can safely run multiple stacks in parallel: ```shell # These can run concurrently without conflicts atmos terraform plan vpc -s dev & atmos terraform plan vpc -s staging & atmos terraform plan vpc -s prod & wait ``` ### Directory Structure Putting it all together, a project mixing both patterns—some components vendored without workdir isolation, others using the combined source + workdir flow—looks like this: ``` project/ ├── components/ │ ├── terraform/ │ │ └── vpc/ # Vendored by source pull │ ├── helmfile/ │ │ └── nginx/ # Vendored by source pull │ └── packer/ │ └── eks-ami/ # Vendored by source pull ├── .workdir/ # Created when workdir enabled │ ├── terraform/ │ │ ├── dev-vpc-bb03116d/ # Isolated for dev stack │ │ ├── staging-vpc-8ce5e903/ # Isolated for staging stack │ │ └── prod-vpc-0e327247/ # Isolated for prod stack │ ├── helmfile/ │ │ └── dev-nginx-cfcfbd1f/ # Isolated for dev stack │ └── packer/ │ └── dev-eks-ami-62c3c999/ # Isolated for dev stack └── stacks/ ├── dev.yaml ├── staging.yaml └── prod.yaml ``` For usage and configuration, see [Source](/vendor/component-manifest/source). ## Get Involved We'd love to hear your feedback on this feature! Please [open an issue](https://github.com/cloudposse/atmos/issues) if you have questions or suggestions. For more details, see the source documentation for [terraform](/cli/commands/terraform/source), [helmfile](/cli/commands/helmfile/source), and [packer](/cli/commands/packer/source), as well as the [Source-Based Version Pinning](/design-patterns/version-management/source-based-versioning) design pattern. --- ## Fixed: !terraform.state with Disabled Workspaces The [`!terraform.state`](/functions/yaml/terraform.state) YAML function now correctly reads Terraform state when workspaces are disabled (`components.terraform.workspaces_enabled: false` in `atmos.yaml`). Previously, Atmos looked for state files in the wrong location, causing the function to fail. ## The Problem When `workspaces_enabled: false` is set in `atmos.yaml`, Atmos sets the workspace name to `default`. However, Terraform stores state differently for the default workspace compared to named workspaces: | Backend | Default Workspace | Named Workspace | |-----------|---------------------|-----------------------------------------------------| | **S3** | `` | `//` | | **Local** | `terraform.tfstate` | `terraform.tfstate.d//terraform.tfstate` | | **Azure** | `` | `env:` | Atmos was incorrectly looking for state at the named workspace path even when using the default workspace. For example: ```yaml # atmos.yaml components: terraform: workspaces_enabled: false # Uses "default" workspace ``` ```yaml # Stack manifest components: terraform: my-component: vars: vpc_id: !terraform.state vpc output_id # Failed to find state! ``` The `!terraform.state` function would look for state at `workspace_key_prefix/default/key` (S3) or `terraform.tfstate.d/default/terraform.tfstate` (local) instead of the correct location. ## The Fix Atmos now correctly handles the default workspace for all backend types: - **S3 backend**: Uses `` directly instead of `/default/` - **Local backend**: Uses `terraform.tfstate` instead of `terraform.tfstate.d/default/terraform.tfstate` - **Azure backend**: Already worked correctly ## Example With workspaces disabled, the `!terraform.state` function now works as expected: ```yaml # atmos.yaml components: terraform: workspaces_enabled: false # Stack manifest components: terraform: networking: metadata: component: vpc vars: cidr: "10.0.0.0/16" application: metadata: component: app vars: # Now correctly reads from terraform.tfstate (not terraform.tfstate.d/default/terraform.tfstate) vpc_id: !terraform.state networking vpc_id subnet_ids: !terraform.state networking private_subnet_ids ``` ## Upgrade Upgrade Atmos to get this fix. No configuration changes are required. The `!terraform.state` function will automatically use the correct state file paths based on your workspace configuration. ## References - [GitHub Issue #1920](https://github.com/cloudposse/atmos/issues/1920) - [Terraform S3 Backend Documentation](https://developer.hashicorp.com/terraform/language/backend/s3) - [Terraform Local Backend Documentation](https://developer.hashicorp.com/terraform/language/backend/local) - [Terraform Azure Backend Documentation](https://developer.hashicorp.com/terraform/language/backend/azurerm) - [Terraform GCS Backend Documentation](https://developer.hashicorp.com/terraform/language/backend/gcs) For usage and configuration, see [Workspaces](/components/terraform/workspaces). --- ## Introducing Streaming UI: Real-Time Progress for Terraform Commands Say goodbye to overwhelming Terraform output. The new [streaming UI](/cli/configuration/components/terraform) mode transforms verbose terraform output into a clean, Docker-build-style progress display with interactive confirmations, resource dependency tree visualization, and attribute-level change details. ## What Changed We've added an optional streaming UI mode for Terraform commands (`plan`, `apply`, `deploy`, `init`, `destroy`) that displays real-time resource status with visual indicators, progress tracking, interactive confirmations, and condensed completion summaries. ### Real-Time Progress Display **During execution:** ``` ⠋ apply plat-ue2-dev/vpc Creating aws_security_group.default (5.2s) ████████░░░░ 2/5 ✓ Created aws_vpc.main (2.1s) ✓ Created aws_subnet.public[0] (1.3s) ``` The inline progress bar shows the current activity, elapsed time, and completion status on a single line. **On completion:** ``` ✓ Apply plat-ue2-dev/vpc completed (15.2s) ``` **On error:** ``` ✗ Apply plat-ue2-dev/vpc failed: 1 error (12.1s) Error: aws_instance.web[0]: InvalidAMIID.NotFound ``` ### Dependency Tree with Attribute Changes The streaming UI shows a visual dependency tree of resources being changed, with color-coded indicators: ``` plat-ue2-dev/myapp ● ├── aws_s3_object.file │ content_type "text/html" → "text/plain" │ source "hello.html" → "hello.txt" ● └── aws_instance.new ami (none) → "ami-12345" instance_type (none) → "t3.micro" ``` Resources are marked with colored dots (●): green for create, yellow for update, red for delete. Attribute changes show old → new values in a two-column layout. For multi-line values (like file content), each line is shown with add/remove indicators: ``` ● └── aws_s3_object.weather content - Current weather: Sunny, 72°F - Humidity: 45% + Current weather: Cloudy, 65°F + Humidity: 80% + Wind: 15 mph NW ``` ### Interactive Confirmation Before applying changes, the UI displays a confirmation prompt: ``` Do you want to apply these changes? > Yes No ``` The confirmation is styled with the Atmos theme and includes proper margins for visual clarity. ## Why This Matters Terraform's default output is overwhelming, especially for new users. A simple infrastructure change can produce hundreds of lines containing: - Verbose state refresh messages for every resource - Provider initialization logs - JSON-like attribute diffs that obscure actual changes - Technical identifiers and ARNs This creates several problems: 1. **Information overload** - Important changes are buried in noise 2. **Progress blindness** - No clear indication of completion percentage during long operations 3. **Error obscurity** - Errors get buried in the output stream 4. **Context switching** - Mental overhead parsing Terraform syntax The streaming UI solves these by showing only what matters: which resources are changing, what attributes are affected, and their current status. ## How to Use It ### Enable via Flag ```shell # Enable for a single command atmos terraform plan vpc -s dev --ui # Disable explicitly when enabled by config atmos terraform apply vpc -s prod --ui=false ``` ### Enable via Configuration Add to your `atmos.yaml`: ```yaml components: terraform: ui: enabled: true ``` Or set it directly from the CLI without hand-editing YAML: ```shell atmos config set components.terraform.ui.enabled true ``` With this on, the UI turns on automatically whenever a real terminal is detected — no `--ui` flag needed — and still auto-disables in the same piped/CI cases described below. Use `--ui=false` per-command to override it off. ### Enable via Environment Variable ```shell export ATMOS_TERRAFORM_UI=true atmos terraform plan vpc -s dev ``` ## Smart Auto-Disable The streaming UI automatically falls back to standard output when: - **No TTY attached** - Output is piped or redirected - **CI environment detected** - `CI=true` environment variable is set - **Unsupported commands** - Commands that don't support JSON streaming This means your existing scripts and CI pipelines continue to work without modification. ## Supported Commands | Command | Streaming UI | |---------|--------------| | `plan` | ✓ | | `apply` | ✓ | | `deploy` | ✓ | | `init` | ✓ | | `destroy` | ✓ | | `refresh` | ✗ (no `-json` streaming support in Terraform's `refresh`) | ## Technical Details The implementation uses Terraform's machine-readable JSON output format (`-json` flag): 1. Atmos automatically adds `-json` to terraform commands when UI mode is enabled 2. Parses plan files to build a dependency tree with attribute-level changes 3. Renders a Bubbletea-based TUI with spinners, progress bars, and status indicators 4. Displays interactive confirmation prompts using the `huh` library 5. Preserves exit codes (critical for `plan -detailed-exitcode`) This is a pure Go implementation using charmbracelet/bubbletea and huh, requiring no external binaries or CGO. ## Learn More - [Terraform Configuration Reference](/cli/configuration/components/terraform) - [atmos terraform plan](/cli/commands/terraform/plan) - [atmos terraform apply](/cli/commands/terraform/apply) ## Get Involved We'd love your feedback on the streaming UI. Try it out and let us know how it works for your workflow. Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues) or join us in [Slack](https://slack.cloudposse.com). --- ## Terraform state migrations with tfmigrate Refactoring Terraform code leaves state behind. Rename a resource, or move it between root modules, and every plan shows a destroy-and-recreate for infrastructure that never changed. You can fix this by hand with `terraform state mv`, but that command only fixes one workspace at a time. It is easy to get wrong, and no one can review it before it runs. [`tfmigrate`](https://github.com/minamijoyo/tfmigrate) turns these state changes into migration files that you store under version control. Atmos runs these files for you — manually from the CLI, or automatically from Terraform lifecycle hooks — in the same component context as [`atmos terraform plan`](/cli/commands/terraform/plan) and [`apply`](/cli/commands/terraform/apply). ```shell atmos terraform migrate plan s3-bucket -s plat-ue2-dev atmos terraform migrate apply s3-bucket -s plat-ue2-dev ``` ## What Changed The new `atmos terraform migrate` command family adds: - [`atmos terraform migrate plan`](/cli/commands/terraform/migrate-plan) to preview a migration. - [`atmos terraform migrate apply`](/cli/commands/terraform/migrate-apply) to apply a migration. - [`atmos terraform migrate list`](/cli/commands/terraform/migrate-list) to inspect per-component hook and history context. - `kind: tfmigrate` hooks for running migrations from Terraform lifecycle events. - Zero-config history storage that reuses the component's Terraform backend. Before `tfmigrate` runs, Atmos performs the normal Terraform component setup. This includes auth identity resolution, source provisioning, workdir provisioning, generated backend and varfiles, Terraform init, and workspace selection. ## Dynamic Hooks `kind: tfmigrate` hooks default to `mode: dynamic`, so automation follows the Terraform operation: ```yaml hooks: state-migration: events: - before.terraform.plan - before.terraform.apply kind: tfmigrate mode: dynamic ``` `before.terraform.plan` runs `tfmigrate plan`. `before.terraform.apply` and `before.terraform.deploy` run `tfmigrate apply`. Use static `mode: plan` or `mode: apply` when a hook must always run one action. Hooks run through Atmos, so they use the same identity as the Terraform operation. If the Terraform command or component selects an Atmos auth identity, the migration gets that same authenticated environment. ## Recovering From Skipped Releases Provider removals are where this bites hardest. When a component release drops a provider configuration, every workspace that skips the intermediate release gets stuck. The state still holds resources from the removed provider, and Terraform refuses to plan: ```text Error: Provider configuration not present To work with random_pet.legacy (orphan) its original provider configuration at provider["registry.opentofu.org/hashicorp/random"].legacy is required, but it has been removed. ``` Until now, the only fix was manual, and per workspace: restore a temporary provider override, apply, then delete the override again. Ship a migration alongside the release instead. The hook prunes the stale state entries before Terraform loads provider configurations, so workspaces can jump straight to the newest release: ```hcl migration "state" "drop_legacy_provider_state" { actions = [ "rm random_pet.legacy", ] } ``` Note: `state rm` abandons the remote object. It does not destroy it. This is usually what you want for provider-cleanup migrations. If you need to destroy the object, destroy it before you upgrade, or temporarily restore the provider configuration. ## History Mode For idempotent migrations in automation, use `tfmigrate` history mode: ```shell atmos terraform migrate apply s3-bucket -s plat-ue2-dev ``` History mode needs no configuration by default. Atmos looks for a custom `tfmigrate` config in four places: the hook's `config` field, the `--tfmigrate-config` / `ATMOS_TFMIGRATE_CONFIG` flag or environment variable, the `TFMIGRATE_CONFIG` environment variable, and a `.tfmigrate.hcl` file in the component. If none of these exist, Atmos generates a config on the fly. The generated config stores migration history in the component's own Terraform backend. For an S3 or GCS backend, Atmos reuses the same bucket as the state. It stores history under a key namespaced by stack, component, and workspace, and it inherits the region, role ARN, and endpoint. For a local backend, Atmos stores the history file beside the state file. Atmos records every applied migration and never reruns it. You don't need to set anything up. To take control, provide your own config. Drop a `.tfmigrate.hcl` file in the component, set the hook's `config` field, or pass `--tfmigrate-config` / `ATMOS_TFMIGRATE_CONFIG`. Atmos exports stack, component, and workspace-scoped history variables, and copies the supported Terraform backend settings. Your custom config can then reuse the same bucket and identity setup: ```hcl tfmigrate { migration_dir = "./tfmigrate" history { storage "s3" { bucket = env.ATMOS_TFMIGRATE_HISTORY_BUCKET key = env.ATMOS_TFMIGRATE_HISTORY_KEY region = env.ATMOS_TFMIGRATE_HISTORY_REGION role_arn = env.ATMOS_TFMIGRATE_HISTORY_ROLE_ARN } } } ``` The default history key is: ```text tfmigrate////history.json ``` That keeps multiple Atmos component instances from colliding when they share a Terraform backend bucket. ## Important Limitation Single-file `tfmigrate apply path.hcl` is not idempotent by itself. A rerun can fail if a state address already moved or was removed. Prefer history mode instead. When the component's backend is S3 or GCS, the generated default gives you durable storage automatically. With a purely local backend, make sure the local history file survives between runs — for example, have your CI workflow persist it. ## Learn More - [`atmos terraform migrate`](/cli/commands/terraform/migrate) - [Hooks](/stacks/hooks) - [Terraform Component Configuration](/cli/configuration/components/terraform) For usage and configuration, see [Atmos component migration in YAML config](/tutorials/atmos-component-migrations-in-yaml). --- ## Enforce Terraform Conventions with the tflint Hook Kind Consistent, idiomatic Terraform doesn't happen by accident — it takes a linter enforcing the same conventions on every component, every run. Atmos already runs security and cost scanners as component hooks; now it runs a **linter** the same way, with a built-in `tflint` hook kind. ## The Problem `terraform validate` only proves your configuration parses and is internally consistent. It says nothing about the things that keep a growing codebase healthy: - **Code consistency & conventions** — naming, formatting, and required version constraints, applied uniformly across every component and every contributor. - **Hygiene** — unused variables, outputs, and locals; deprecated interpolation syntax; provider settings that will bite you later. - **Correctness `validate` misses** — invalid instance types, unsupported arguments, and other provider-specific mistakes. [tflint](https://github.com/terraform-linters/tflint) checks all of that. But without a first-class integration, enforcing it meant a hand-rolled `kind: command` hook in every stack — and figuring out how to get its output to render. Worse, standards that aren't automated quietly become optional: they hold only as long as a reviewer remembers to look. There was also a mechanical snag. The security scanners write their SARIF report to a file (`--output $ATMOS_OUTPUT_FILE`), which is how Atmos picks it up. tflint has **no file-output flag** — `tflint --format=sarif` writes to **stdout**. So a first-class tflint kind needed the hook engine to learn one new trick: capture a tool's stdout. ## The Solution A built-in `tflint` hook kind — zero configuration. Wire it on `before.terraform.init` so a lint failure stops you _before_ any init/plan work, the way a fast-failing linter should: ```yaml components: terraform: vpc: hooks: lint: events: [before.terraform.init] kind: tflint ``` For CI workflows that only run [`atmos terraform plan`](/cli/commands/terraform/plan), `apply`, or `deploy` and do not call [`atmos terraform init`](/cli/commands/terraform/init) explicitly, use that command's before event instead (for example `before.terraform.plan`) so the lint summary is written in the same job. The kind ships sane defaults (`tflint --chdir=$ATMOS_COMPONENT_PATH --format=sarif`, `on_failure: warn`) and runs against the same directory Terraform does — including the provisioned workdir when that feature is enabled. Under the hood, hook kinds can now opt into **stdout capture**: Atmos redirects the tool's stdout into its structured-output side channel, so a tool that only prints to stdout is handled identically to one that writes a file. From there, tflint rides the exact same path as the other scanners — a single markdown summary in your terminal and on the Atmos Pro run page. And because it produces SARIF through the shared handler, tflint automatically inherits everything the [scanner CI integration](/stacks/hooks) added: a job **step summary**, inline **PR annotations** on the diff, and a **GitHub Code Scanning** upload — all gated by your `ci:` config, no extra wiring. (See [Scanner Findings as Inline PR Annotations and Code Scanning Alerts](/changelog/scanner-annotations-and-code-scanning).) ## How to Use It 1. Make sure `tflint` is on `PATH` (`brew install tflint`), or pin it for auto-install: ```yaml dependencies: tools: tflint: "0.59.1" ``` 2. Add the hook to a component and run any Terraform command: ```bash atmos terraform plan vpc -s test ``` tflint runs first — before init — and renders its findings. The builtin `terraform` ruleset enforces core conventions with no setup; add a `.tflint.hcl` and run `tflint --init` once for provider rulesets (aws/google/azurerm) that catch cloud-specific mistakes (a `kind: command` hook works for that). A working, credential-free setup lives in [`examples/hooks-tflint`](https://github.com/cloudposse/atmos/tree/main/examples/hooks-tflint). Findings default to `on_failure: warn`, so conventions surface as guidance first — flip to `fail` when you're ready to enforce them as a gate. --- ## Help Text Now Respects Your Terminal Theme Atmos help text now automatically adapts to your configured theme, providing a consistent and visually cohesive experience across all commands. Whether you're using a dark theme like Dracula or a light theme like GitHub, help output will match your terminal's color scheme. ## What Changed Previously, help text used a fixed color scheme that didn't respect your terminal theme settings. Now, when you run any `--help` command, the output automatically uses colors from your configured theme. ## How It Works The theme system applies to all help output: ```bash # Help text uses your configured theme atmos terraform plan --help atmos describe stacks --help atmos --help ``` If you have a theme configured in `atmos.yaml` or via the `ATMOS_THEME` environment variable, all help text will automatically use those colors for syntax highlighting, headings, and examples. ## Force Color Output For screenshot generation or CI/CD environments, you can force colored output even when not connected to a TTY: ```bash # Force color output for screenshots atmos --help --force-color # Or use environment variable ATMOS_FORCE_COLOR=true atmos terraform plan --help ``` This ensures consistent, colorful output regardless of the execution environment. ## Configuration Set your theme in `atmos.yaml`: ```yaml settings: terminal: theme: dracula ``` Or use an environment variable: ```bash export ATMOS_THEME=solarized-dark ``` ## Learn More - [Terminal themes](/cli/commands/theme/usage) - Complete theme documentation - [Browse themes](/cli/commands/theme/browse) - Visual gallery of 350+ themes - [Force color flag](/cli/global-flags) - Command-line options ## Get Involved - Share your feedback in the [Atmos Community Slack](https://cloudposse.com/slack) --- ## Theme Commands Migrated to StandardFlagParser The theme commands have been migrated to use the modern `StandardFlagParser` infrastructure, bringing them in line with other Atmos commands. ## For Atmos Contributors This change has **zero user impact** but improves internal code consistency. The theme commands now use the same flag parsing pattern as `terraform`, `helmfile`, and `packer` commands. ## What Changed The theme commands have been migrated to use the `StandardFlagParser` pattern, enabling environment variable support and consistent flag precedence across all Atmos commands. ## Usage ```bash # Use environment variable export ATMOS_THEME_RECOMMENDED=true atmos theme list # CLI flag overrides environment variable atmos theme list --recommended=false ``` ## Benefits - **Environment variable support**: Set `ATMOS_THEME_RECOMMENDED=true` to filter recommended themes - **Flag precedence**: CLI flags > environment variables > config files > defaults - **Consistency**: Same pattern as `terraform`, `helmfile`, and `packer` commands - **Type safety**: Removed global variables in favor of type-safe options structs For usage and configuration, see [atmos theme](/cli/commands/theme/usage). --- ## Toolchain registry adds github_archive and github_content support Atmos's Aqua-compatible toolchain registry now understands two more package types: `github_archive` and `github_content`. Tools that ship as a source tarball (like `adr-tools` and `tfenv`) or as a single raw file in a repo (like `kubens` and `kubectx`) can now be installed through [`atmos toolchain install`](/cli/commands/toolchain/install) without any registry workarounds. ## What Changed Aqua's registry defines several package types for downloading tools. Atmos previously supported only two: - `github_release` — assets attached to a GitHub Release - `http` — arbitrary HTTP(S) URLs Two more are now supported: - `github_archive` — the auto-generated source tarball produced by GitHub for any tag, downloaded from `github.com/{owner}/{repo}/archive/refs/tags/{version}.tar.gz`. Always `.tar.gz`, regardless of the `format` field. - `github_content` — a single file from a GitHub repo at a tag, downloaded from `raw.githubusercontent.com/{owner}/{repo}/{version}/{path}`. The required `path` field points to the file inside the repo. Both implementations match upstream `aquaproj/aqua` exactly. For `github_archive`, the `asset`, `url`, `format`, and `format_overrides` fields are intentionally ignored (Aqua hardcodes `tar.gz` and the URL pattern). For `github_content`, the same fields are ignored — only `repo_owner`, `repo_name`, and `path` are used. Before this release, any Aqua registry entry using either type failed with `unsupported tool type: ...`. Those entries now resolve correctly. ## How to Use It ### github\_archive For a tool that ships its binary as a script inside its source tree — such as `adr-tools`: ```yaml packages: - type: github_archive repo_owner: npryce repo_name: adr-tools files: - name: adr src: adr-tools-{{trimV .Version}}/src/adr ``` The `{{trimV .Version}}` template expands to match GitHub's archive root directory (e.g., `adr-tools-3.0.0/` for version `v3.0.0`), so `files[].src` points to the file inside the extracted archive. ### github\_content For a tool that ships as a single raw file in a repo — such as `kubens` from `ahmetb/kubectx`: ```yaml packages: - type: github_content repo_owner: ahmetb repo_name: kubectx path: kubens ``` The download URL becomes `https://raw.githubusercontent.com/ahmetb/kubectx/{version}/kubens`. No archive extraction is involved — the file is downloaded directly. ## Why This Matters Aqua's upstream registry has hundreds of entries that use these two types — `tfenv`, `tgswitch`, `adr-tools`, `kubectx`, `kubens`, and many one-binary shell-script projects. Until now, those entries were dead in Atmos. Adding these two package types unblocks all of them without registry-level changes: pull the Aqua registry entry as-is and it just works. The Aqua-compatible build/install types (`go_install`, `go_build_install`, `cargo`) remain unsupported — they require invoking a language toolchain at install time rather than downloading an artifact, which is a different installation model. A follow-up issue tracks that work. For usage and configuration, see [atmos toolchain](/cli/commands/toolchain/usage). ## Get Involved Atmos is open source on [GitHub](https://github.com/cloudposse/atmos). File issues or open PRs if you hit any tools the registry can't resolve. --- ## Script-Friendly Output for atmos toolchain get Pulling a tool's version into a script usually means scraping decorated terminal output. A checkmark here, a color code there, maybe a table row — and now the one-liner that used to grab a version string needs a regex, a `head -1`, and a `2>&1` to work around output that was never meant to be parsed. ## The Problem A CI job that needs a tool's configured version — to pass to another action, or write to `GITHUB_OUTPUT` — had to reach for something like: ```shell version=$(atmos toolchain get vale-cli/vale 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) echo "version=$version" >> "$GITHUB_OUTPUT" ``` That's a lot of shell just to answer "what version is configured?" — a regex to strip a checkmark and color codes, `head -1` because the human-readable view can list more than one line, and `2>&1` because the output goes to stderr, not stdout. ## The Fix [`atmos toolchain get`](/cli/commands/toolchain/get) now supports a [`--format`](/cli/commands/toolchain/get#flags) flag with two script-friendly output modes alongside the existing human-readable table: `plain` prints just the bare version string, and `json` prints structured output including whether that version is installed. ## How to Use It Grab a version with nothing to parse: ```shell $ atmos toolchain get vale-cli/vale --format=plain 2.20.0 ``` Which collapses the CI snippet above to: ```shell version=$(atmos toolchain get vale-cli/vale --format=plain) echo "version=$version" >> "$GITHUB_OUTPUT" ``` Ask for more than the version string with `--format=json`: ```shell $ atmos toolchain get terraform --format=json ``` ```json { "tool": "hashicorp/terraform", "version": "1.9.8", "installed": false } ``` `--format=plain` only makes sense for a single resolved version, so it's rejected when combined with [`--all`](/cli/commands/toolchain/get#flags) (which lists every available version) — use `--format=json` there instead, which returns the full list with each entry's installed and default status. ## Get Involved Try `--format=plain` or `--format=json` the next time you're piping a tool version into a script. If you run into a `toolchain` command that still only prints decorated, hard-to-parse output, please [open an issue](https://github.com/cloudposse/atmos/issues) so we can add the same script-friendly modes there too. --- ## Toolchain Installs Are Reproducible by Default Now Atmos's toolchain has had a lockfile for a while. The lockfile records the exact resolved artifact and checksum for each platform. A package manager's lockfile pins a dependency tree the same way. But the lockfile was opt-in. The setting was not documented anywhere a user could find it. Almost nobody turned it on. Almost nobody's installs were actually reproducible. The installs only looked reproducible, because the version string matched. ## The Problem A version in `.tool-versions` pins what you asked for. It does not pin what Atmos actually installed. Every [`atmos toolchain install`](/cli/commands/toolchain/install) command resolves that version against the live registry again. The exact download URL, checksum, and platform artifact are not fixed anywhere. Only the version number is fixed. ## The Fix Toolchain installs now write a lockfile by default. You do not need to configure anything. Run `atmos toolchain install`. Atmos records the exact resolved version, download URL, checksum, and size for your platform. Installs skip a tool that already exists on disk, so an already-installed tool will not get a lockfile entry until you run [`atmos toolchain lock`](/cli/commands/toolchain/lock) (or reinstall it with `atmos toolchain install --reinstall`). Once every tool has an entry, the next install resolves against the lockfile instead of asking the registry again, on your machine, a teammate's, or in CI. Reproducibility holds per platform: everyone on an operating system and architecture with a matching lock entry gets the same artifact, byte for byte. If a project's `atmos.yaml` pins an edition dated before this change, the project keeps the old opt-in behavior. Nothing changes for a project that relied on the previous default. New and unpinned projects get the lockfile from the start. ## How to Use It You do not need to opt in. Install as usual: ```shell $ atmos toolchain install ``` Commit the resulting `toolchain.lock.yaml` file next to `.tool-versions`. If some of those tools were already installed before you ran this, run `atmos toolchain lock` too -- `atmos toolchain install` won't touch a tool it finds already on disk, so it can leave that tool's entry missing. Once every tool has an entry, every install resolves the exact pinned artifact for that platform. This applies on your machine, a teammate's machine, and a CI runner. Atmos does not resolve the version against the registry again. The [`atmos toolchain`](/cli/commands/toolchain/usage) command is still experimental. Its interface may change. The reproducibility does not depend on that. ## Get Involved If you already use the toolchain, check whether `toolchain.lock.yaml` has an entry for every tool in `.tool-versions`. Run `atmos toolchain lock` to fill in any `atmos toolchain install` skipped because they were already installed, then commit the lockfile. If your installs still do not reproduce the same way across machines with matching lock entries, [open an issue](https://github.com/cloudposse/atmos/issues). That is exactly the gap this fix closes. --- ## Native Toolchain Management with Aqua Registry Integration Atmos now includes native toolchain management that seamlessly integrates with the Aqua registry ecosystem — giving you access to hundreds of pre-configured CLI tools without the overhead of external tool managers. ## What's New Atmos now includes **built-in toolchain management** commands that let you install, manage, and version control CLI tools directly within your infrastructure projects. This feature integrates natively with the [Aqua registry](https://aquaproj.github.io/), leveraging its extensive ecosystem of package definitions while providing deep integration with Atmos workflows and components. ### Why This Matters Managing tool versions across infrastructure teams has always been a challenge. Different developers use different versions of terraform, kubectl, helm, and dozens of other CLIs — leading to "works on my machine" problems and deployment inconsistencies. Traditional solutions require: - Installing separate tool managers (asdf, aqua, tfenv, etc.) - Maintaining separate configuration files - Context switching between your infrastructure tool and your tool manager - No integration with your infrastructure automation workflows **Now you can manage all your CLI tools directly from Atmos** — with zero external dependencies and seamless integration with your infrastructure workflows. ## How It Works ### Getting Started When you run [`atmos toolchain list`](/cli/commands/toolchain/list) without a `.tool-versions` file, Atmos provides helpful guidance: ``` # No Configuration Found No .tool-versions file found at: .tool-versions ## To get started: Add a tool to automatically create your configuration: atmos toolchain add terraform@1.6.0 Then list your tools: atmos toolchain list ``` ### Installation Install tools with simple commands: ```bash # Install specific versions using aliases atmos toolchain install terraform@1.9.8 atmos toolchain install opentofu@1.10.3 atmos toolchain install kubectl@1.28.0 atmos toolchain install k9s@0.32.7 # Install using canonical registry paths atmos toolchain install hashicorp/terraform@1.9.8 atmos toolchain install opentofu/opentofu@1.10.3 atmos toolchain install derailed/k9s@0.32.7 # Install all tools from .tool-versions file atmos toolchain install ``` ### Version Management Manage tool versions with `.tool-versions` files (asdf-compatible): ``` terraform 1.9.8 opentofu 1.10.3 kubectl 1.28.0 helm 3.13.0 k9s 0.32.7 ``` ```bash # Add tools to .tool-versions atmos toolchain add terraform@1.9.8 atmos toolchain add k9s@0.32.7 # Remove a tool from .tool-versions atmos toolchain remove terraform # Set default version when multiple are installed atmos toolchain set terraform 1.9.8 # List installed tools with status atmos toolchain list ``` The `list` command displays a comprehensive table showing: - Tool aliases and registry paths - Installed versions and their status (✓ or ✗) - Installation dates and binary sizes - Multiple versions when configured ### Execution Run tools directly through Atmos: ```bash # Execute a specific version atmos toolchain exec terraform@1.9.8 -- plan # Use the version from .tool-versions atmos toolchain exec terraform -- plan # Execute k9s for Kubernetes cluster management atmos toolchain exec k9s -- version atmos toolchain exec k9s@0.32.7 -- info # Get tool paths for integration atmos toolchain path atmos toolchain which terraform atmos toolchain which k9s ``` ### Registry Discovery Browse available tools and search the registry: ```bash # List all available tools in the Aqua registry atmos toolchain registry list aqua # Search for specific tools atmos toolchain registry search terraform atmos toolchain registry search kubectl atmos toolchain registry search k9s # Get detailed information about a tool atmos toolchain info terraform atmos toolchain info k9s # Get available versions for a tool atmos toolchain get k9s atmos toolchain get terraform --all --limit 10 ``` ## Aqua Registry Integration The power of Atmos toolchain comes from its integration with the [Aqua registry](https://github.com/aquaproj/aqua-registry)—a community-maintained collection of over 1,000 CLI tool definitions. ### Benefits of Aqua Registry - **Extensive Coverage**: Pre-configured definitions for terraform, kubectl, helm, k9s, aws-cli, and hundreds more - **Community Maintained**: Regular updates and new tools added by the community - **Proven Format**: Battle-tested YAML format used by thousands of teams - **No Vendor Lock-in**: Compatible with asdf `.tool-versions` files ### How Registry Resolution Works Atmos automatically resolves tool names using a smart resolution system: 1. **Exact matches**: `hashicorp/terraform` → `hashicorp/terraform` 2. **Alias resolution**: `terraform` → `hashicorp/terraform` 3. **Registry lookup**: Searches Aqua registry for canonical paths This means you can use short, friendly names like `terraform`, `kubectl`, or `k9s` without needing to remember full registry paths like `derailed/k9s`. ## Component Dependencies Atmos toolchain integrates seamlessly with stack configuration. Declare tool dependencies at multiple levels with proper inheritance: ```yaml # Global dependencies (applies to all components) dependencies: tools: aws-cli: "2.0.0" jq: "latest" k9s: "0.32.7" # Component type dependencies (applies to all terraform components) terraform: dependencies: tools: terraform: "1.9.8" tflint: "0.54.0" # Component instance dependencies (specific component) components: terraform: vpc: dependencies: tools: terraform: "1.9.8" checkov: "latest" eks-cluster: dependencies: tools: terraform: "1.9.8" kubectl: "1.28.0" k9s: "0.32.7" ``` When you provision a component, Atmos automatically: 1. Resolves tool dependencies from stack configuration (with inheritance) 2. Installs missing tools 3. Updates PATH to include installed tools 4. Executes the component with the correct tool versions This ensures every component runs with exactly the tools it needs, eliminating version conflicts. Dependencies also work for workflows and custom commands! ## Configuration Flexibility Atmos toolchain supports multiple configuration sources with proper precedence: **Environment Variables:** ```bash export ATMOS_TOOL_VERSIONS=/path/to/.tool-versions export ATMOS_TOOLS_DIR=/custom/tools/dir export ATMOS_TOOLCHAIN_GET_ALL=true export ATMOS_TOOLCHAIN_GET_LIMIT=50 ``` **Command Line Flags:** ```bash atmos toolchain list --tool-versions /path/to/.tool-versions atmos toolchain get terraform --all --limit 20 ``` **Configuration Files:** Configure default behavior in `atmos.yaml`: ```yaml toolchain: versions_file: .tool-versions install_path: .tools ``` Precedence: **flags > environment variables > config files > defaults** ## XDG Base Directory Support Atmos toolchain follows the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) for cache storage: - **Cache**: `$XDG_CACHE_HOME/atmos/toolchain` (or `~/.cache/atmos/toolchain`) - **Fallback**: Falls back to `~/.cache/tools-cache` if XDG directories are unavailable - **Override**: Set `XDG_CACHE_HOME` or `ATMOS_XDG_CACHE_HOME` to customize This ensures proper integration with system conventions and user preferences. ## Safety and Reliability The toolchain includes safety features for robust operation: - **Version pinning**: `.tool-versions` ensures consistent tool versions across environments - **Error handling**: Graceful fallbacks for missing files or network issues - **Helpful messages**: Clear guidance when configuration is missing or tools aren't found - **Clean uninstall**: [`atmos toolchain clean`](/cli/commands/toolchain/clean) removes unused tool versions - **Cross-platform**: Works consistently on Linux, macOS, and Windows ## Benefits Over Traditional Tool Managers ### For Developers - **Single Command**: No need to install or learn asdf, aqua, tfenv, etc. - **Zero Setup**: Works immediately with Atmos—no plugins or configuration - **Helpful Guidance**: Clear error messages guide you to the correct workflow - **Consistent Interface**: Same command structure as other Atmos operations ### For Teams - **Version Consistency**: `.tool-versions` ensures everyone uses the same versions - **CI/CD Integration**: Works seamlessly in automated pipelines - **Component Isolation**: Different components can use different tool versions - **Audit Trail**: Lockfiles provide cryptographic proof of tool versions ### For Infrastructure - **Dependency Declaration**: Tools specified alongside infrastructure code - **Automatic Installation**: Components install their own dependencies - **PATH Management**: Automatic PATH configuration for spawned processes - **Cross-Platform**: Works on Linux, macOS, and Windows ## Migration from Other Tool Managers ### From asdf Atmos toolchain is compatible with asdf `.tool-versions` files—no changes needed: ```bash # Your existing .tool-versions file works as-is terraform 1.9.8 kubectl 1.28.0 helm 3.13.0 # Just use Atmos toolchain commands instead atmos toolchain install atmos toolchain list ``` ### From Aqua If you're using Aqua directly, migration is straightforward: ```bash # Convert aqua.yaml to .tool-versions grep -E '^\s+name:' aqua.yaml | \ sed 's/name://' | \ xargs -I {} atmos toolchain add {} # Then use Atmos toolchain atmos toolchain install ``` ### From tfenv/rbenv/pyenv Create a `.tool-versions` file with your current versions: ```bash # Check current versions terraform version kubectl version --client # Add to .tool-versions echo "terraform $(terraform version | head -1 | awk '{print $2}' | tr -d 'v')" > .tool-versions echo "kubectl $(kubectl version --client -o json | jq -r '.clientVersion.gitVersion' | tr -d 'v')" >> .tool-versions # Install via Atmos atmos toolchain install ``` ## Implementation Details ### User Interface The toolchain uses Atmos's modern UI system with: - **Markdown rendering**: Helpful messages with formatted code blocks - **Status indicators**: Automatic checkmarks (✓) for success, warnings (⚠), and errors (✗) - **Table display**: Clean, formatted tables with proper column sizing - **Color degradation**: Automatic color support detection and graceful fallback - **Consistent formatting**: All output follows Atmos UI conventions ### Technical Architecture Key architectural decisions: - **Registry pattern**: Extensible design supports multiple registries - **Interface-driven**: All components use interfaces for testability - **Options pattern**: Configuration via functional options, not parameter drilling - **Context propagation**: Proper cancellation and timeout support - **XDG compliance**: Follows system conventions for cache directories - **Error wrapping**: Sentinel errors with context chains ## Getting Started Try it now: ```bash # Add tools to your project atmos toolchain add terraform@1.9.8 atmos toolchain add kubectl@1.28.0 atmos toolchain add k9s@0.32.7 # List your tools atmos toolchain list # Install everything atmos toolchain install # Use tools directly atmos toolchain exec terraform -- version atmos toolchain exec k9s -- version # Or add tools to your PATH export PATH="$(atmos toolchain path):$PATH" terraform version k9s version ``` No configuration required—just start using it! ## Backward Compatibility Atmos toolchain is fully backward compatible: - Existing `.tool-versions` files (asdf format) work without modification - No breaking changes to component or stack configuration - All existing commands continue to work as before - Toolchain commands are completely additive ## Future Enhancements We're continually improving the toolchain experience. Planned enhancements include: - **Lockfile support**: Reproducible builds with checksums and platform-specific binaries - **Custom registries**: Support for private tool registries - **Version constraints**: Semantic version ranges (e.g., `^1.9.0`, `~> 1.10.0`) - **Performance optimizations**: Parallel downloads and improved caching We'd love your feedback! Try the toolchain commands and let us know what you think. For usage and configuration, see [atmos toolchain](/cli/commands/toolchain/usage). --- ## Toolchain now installs aws-cli, Node, and other multi-file tools correctly You pinned `aws/aws-cli` in your toolchain, ran [`atmos toolchain install`](/cli/commands/toolchain/install), and saw a green checkmark — then `aws --version` died with `Failed to load Python shared library`. Or you pinned `nodejs/node` and the install never finished at all, warning about "unknown type" and a file it couldn't find. The tools were right there in the registry, `aqua` installed them fine, but Atmos couldn't. That's fixed. Multi-file tools now install completely and actually run. ## The Problem Atmos installed tools by copying just the entrypoints named in the registry's `files:` list out of the downloaded archive — and throwing the rest away. That works for a single self-contained binary like `jq` or `terraform`. It breaks for "onedir" bundles that ship a binary _plus_ the files it needs at runtime: - **`aws/aws-cli`** bundles its own Python runtime. Atmos kept `aws` and `aws_completer` and dropped the ~8,600 supporting files, including `libpython`. The install reported success, but the binary was dead on arrival ([#2743](https://github.com/cloudposse/atmos/issues/2743)). - **`nodejs/node`** exposes `npm`, `npx`, and `corepack` as symlinks into a sibling `lib/` tree, and publishes under a `v`-prefixed path. Atmos skipped the symlinks as an "unknown type" and looked for `node-24.18.0-...` instead of `node-v24.18.0-...`, so the install failed outright ([#2744](https://github.com/cloudposse/atmos/issues/2744)). ## The Fix Atmos now mirrors how the upstream `aqua` CLI installs these packages: - **Preserves the whole archive** for multi-file tools under a `.pkg` directory — so every executable stays next to the runtime files it needs — and records each entrypoint's real path in a small sidecar manifest, without creating any symlinks of its own (a design choice used consistently across Atmos). - **Keeps the archive's own symlink entrypoints** (like `npm` → `../lib/node_modules/...`) instead of dropping them. - **Resolves versions consistently**, so a pinned `24.18.0` correctly finds the `v24.18.0` archive that actually downloads. - **Fails honestly**: a broken or incomplete extraction no longer reports success or leaves an orphaned binary behind. Single-binary tools are completely unaffected — they keep the exact same flat layout as before. ## How to Use It Nothing new to learn. Pin the tools and install: ```shell atmos toolchain install aws/aws-cli@2.35.15 atmos toolchain install nodejs/node@24.18.0 ``` ```shell $ aws --version aws-cli/2.35.15 Python/3.14.5 Linux/... exe/x86_64 $ node --version v24.18.0 $ npm --version 11.16.0 ``` See [How tools are installed on disk](/cli/commands/toolchain/install#how-tools-are-installed-on-disk) for details on the layout. ## Get Involved Toolchain is an [experimental feature](https://atmos.tools/experimental) and we're actively hardening it. If a tool doesn't install cleanly, please [open an issue](https://github.com/cloudposse/atmos/issues) with the `owner/repo` and version — those reports are exactly what drove this fix. --- ## Atmos Toolchain Now Verifies Package Checksums and Signatures Atmos toolchain installs now verify downloaded packages before extraction when registry metadata provides checksums, signatures, or attestations. ## What Changed Atmos now preserves and evaluates Aqua-compatible verification metadata during toolchain installs: - Checksum files from GitHub releases and HTTP sources are downloaded and parsed before extraction. - `raw` and `regexp` checksum formats are supported with templated asset names such as `.Asset` and `.AssetWithoutExt`. - `sha256`, `sha512`, `sha1`, and `md5` digests are supported. - Signature and attestation metadata can invoke `cosign verify-blob`, `slsa-verifier verify-artifact`, `gh attestation verify`, and `minisign -Vm`. - Verified package URL, checksum, size, and verification methods are recorded in the toolchain lockfile with backward-compatible optional fields. By default, Atmos verifies checksums and signatures when metadata exists, while still allowing packages without verification metadata to install. ## Why This Matters Toolchain packages are often fetched directly from release assets, so download integrity matters as much as version pinning. With supply-chain attacks and compromised package artifacts becoming routine operational risks, verifying that a downloaded archive is the artifact the publisher intended is now part of the trust boundary for every install. This release lets Atmos use the same verification metadata already maintained by Aqua package definitions, reducing exposure to tampered archives, corrupted caches, and accidental asset mismatches. Cached assets are no longer trusted blindly. If verification fails, Atmos rejects the cached asset and prevents extraction instead of installing a package that does not match the registry metadata. ## How to Use It The default configuration verifies available metadata without requiring every package to publish checksums or signatures: ```yaml toolchain: verification: checksums: when_available signatures: when_available verifier_install: auto ``` For stricter environments, require verification metadata: ```yaml toolchain: verification: checksums: required signatures: required ``` Set `verifier_install: path_only` when CI images already provide `cosign`, `slsa-verifier`, `gh`, or `minisign` and Atmos should not install verifier CLIs automatically. For usage and configuration, see [Toolchain Verification](/cli/configuration/toolchain/verification). ## Get Involved See the [toolchain configuration reference](/cli/configuration/toolchain#package-verification) for the verification policy settings and supported verifier commands. --- ## Toolchain proxies install only the commands you use Installing every tool a repository might eventually need makes project setup slow and wasteful. Atmos toolchain proxies provide on-demand, just-in-time installation instead: invoking a configured command resolves its pinned version and installs that binary only when it is first needed. The first invocation pays the download-and-prepare cost; later invocations reuse the installed release. Proxies also make subcommands first-class executable names. A command-named link points back to Atmos; when it is invoked, Atmos reads the executed name, finds its proxy configuration, and runs the configured tool with its prefix arguments and the caller’s arguments. A multicall tool such as uutils/coreutils can therefore expose `coreutils ls` as the normal `ls` command—without shell aliases, copied shims, or a different invocation in every repository. ## Install on Demand, Keep Versions Pinned An alias resolves the package name, and a proxy maps the familiar command to the package and any prefix arguments it needs: ```yaml title="atmos.yaml" toolchain: aliases: coreutils: uutils/coreutils proxies: ls: tool: coreutils args: [ls] ``` ```text title=".tool-versions" coreutils 0.9.0 ``` ## Get Started Activate every configured proxy in the current Bash or Zsh session with one command: ```sh eval "$(atmos toolchain env)" ``` The normal `ls` command now invokes the `ls` proxy, which resolves to the pinned `coreutils ls` implementation. On its first invocation, the proxy installs that version if it is not already available, then runs it with the supplied arguments. Later invocations reuse the installed binary. For an intentionally eager setup—for example, a CI image or an offline preparation step—install every pinned tool in `.tool-versions` before activating the environment: ```sh atmos toolchain install eval "$(atmos toolchain env)" ``` ## Promote a Subcommand to a Command The same on-demand mechanism is useful when a package exposes many operations through one binary. The `args` list supplies the subcommand or default flags, while the proxy name becomes the command that developers and automation invoke. This turns a useful part of a larger tool into a normal, version-pinned command without a wrapper script. ## Useful Beyond an Interactive Shell Atmos prepares the proxy environment for built-in command runners, workflows, hooks, components, and custom commands. A project can therefore use the familiar command name in its automation while retaining the tool version and source in version control. Developers can opt into the same proxy directory in their terminal without modifying the system command globally. The boundary is deliberate: a proxy is available only to Atmos child processes or to a shell that has explicitly activated the toolchain environment. It never replaces the system `ls` command for the rest of the machine. ## Portable by Design Atmos creates symbolic links on Unix-like systems and executable hard links on Windows, avoiding a Windows symlink-privilege requirement. Proxy activation also carries the configuration context across directory changes, so the selected toolchain remains associated with the project that declared it. See [Toolchain Proxies](/cli/configuration/toolchain/proxies) for the configuration reference, platform details, and troubleshooting guidance. --- ## atmos toolchain update: Move a Pinned Tool Forward Safely Pinning a CLI tool to an exact version is good practice — until it's time to move forward. Then it means opening `.tool-versions` by hand, going to check the tool's release page, picking a version, and editing the line yourself. Get it wrong and you're stuck rerunning install commands to find out. ## The Problem A tool declared in `.tool-versions` stays exactly where you pinned it, on purpose — that's what makes it reproducible. But "pinned" shouldn't mean "stuck." When a new release ships, moving a tool forward has always meant editing the file by hand: look up the tool's latest release yourself, write the new version string into `.tool-versions`, then reinstall and hope you copied it correctly. There was no single command that answered "what's newer, and can I have it?" ## The Fix [`atmos toolchain update`](/cli/commands/toolchain/update) resolves each tool's newest available version and moves the pin forward for you, then installs it. Run it with no arguments to update everything in `.tool-versions`, or name specific tools to update just those. Tools pinned to a PR, commit SHA, or git ref are left alone — those pins are meant to stay exact, so `update` skips them with an explanation instead of silently doing nothing. ## How to Use It Update everything: ```shell $ atmos toolchain update ✓ terraform: 1.9.8 -> 1.11.4 ✓ jq: up to date (1.8.2) Updated 1 tool(s), 1 up to date ``` Update one tool, or preview first: ```shell $ atmos toolchain update terraform --dry-run terraform: 1.9.8 -> 1.11.4 (dry-run) Would update 1 tool(s) ``` Tools are updated concurrently, with the same [`--max-concurrency`](/cli/commands/toolchain/update#flags) control [`atmos toolchain install`](/cli/commands/toolchain/install) already supports. ## Get Involved Try `atmos toolchain update` the next time a pinned tool falls behind. If you run into a tool whose update doesn't behave the way you'd expect, please [open an issue](https://github.com/cloudposse/atmos/issues). --- ## Topic-Specific CLI Help Atmos help showed too much at once. A simple command like [`atmos terraform plan --help`](/cli/commands/terraform/plan) explained the command, its examples, its own flags, compatibility flags, and every inherited global flag, which made the options you actually needed harder to find. Topic-specific help fixes that by making default help focused, while keeping usage examples, command flags, and the full reference one flag away. ## What Changed Default `--help` now shows the command description, usage, examples, subcommands, and flags for that command. Atmos no longer dumps every inherited global flag by default, so command-specific options stay visible. When you need a narrower or broader view, use a help topic: ```shell atmos terraform plan --help=usage atmos terraform plan --help=flags atmos terraform plan --help=all ``` `--help=usage` shows just usage and examples. `--help=flags` shows command-specific flags, plus compatibility flags when Atmos forwards options to a native tool. `--help=all` restores the full reference view, including global flags. Every default help page includes a short reminder: ```text Use --help=usage for examples or --help=all for all flags and full help. ``` ## Why It Matters - **Less noise by default.** Atmos still exposes global flags, but they no longer bury the flags that actually change the command you are reading about. - **Examples are one command away.** Atmos already maintains usage snippets in markdown help files, and those snippets now have their own focused view. - **Full reference stays available.** `--help=all` keeps the exhaustive output available for scripting, documentation, and deep inspection. ## Try It Compare the focused and full views: ```shell atmos terraform plan --help atmos terraform plan --help=all ``` Use `--help=usage` when you only need examples, and `--help=flags` when you are checking command-specific options. --- ## Terminal Steps: tty, interactive, and exec for Custom Commands and Workflows Custom command and workflow shell steps now support docker-style `tty` and `interactive` fields — plus a new `exec` step type that replaces the Atmos process entirely — so commands like `aws ssm start-session`, `ssh`, `psql`, and `vim` can take over the terminal properly, with Ctrl-C going to the session instead of killing Atmos. ## The Problem Shell steps run with Atmos managing their output: secrets are masked, output can be captured for later steps, and everything flows through pipes. That's exactly what you want for `terraform plan` — and exactly wrong for an interactive session: ```yaml commands: - name: ssh steps: - type: shell command: "aws ssm start-session --target {{ .Arguments.instance_id }}" ``` The SSM session ran as a piped subprocess, so full-screen rendering broke. Worse, pressing Ctrl-C inside the session interrupted _Atmos itself_ — Atmos exited, the pipe closed, and the orphaned session died with SIGPIPE. There was no way to say "give this step the terminal." ## The Solution Steps now accept `tty` and `interactive`, modeled on `docker run -it`: ```yaml commands: - name: ssh description: Open an SSM session to an instance arguments: - name: instance_id description: EC2 instance ID required: true steps: - type: shell tty: true interactive: true command: "aws ssm start-session --target {{ .Arguments.instance_id }}" ``` Now `atmos ssh i-1234567890` behaves like a native terminal session: - **`tty: true`** allocates a pseudo-terminal (like `docker -t`). The command sees a real TTY, so full-screen programs render correctly. Secret masking is still applied to the session output. - **`interactive: true`** attaches your stdin and lets the step own Ctrl-C (like `docker -i`). Atmos suspends its own interrupt handling while the step runs, so Ctrl-C interrupts the command in the session — not Atmos. - Together, keystrokes flow straight to the session and the session's exit code becomes the Atmos exit code. The same fields work in workflow shell steps: ```yaml workflows: debug-db: steps: - type: shell tty: true interactive: true command: "psql $DATABASE_URL" ``` You can also use `interactive: true` on its own for prompt-style commands that read stdin but don't need a full TTY, while Ctrl-C is handled by the command. ## Going Further: `type: exec` Sometimes Atmos should be purely a launcher. A step of `type: exec` **replaces the Atmos process entirely** — shell `exec` semantics, a true `execve` on macOS and Linux: ```yaml commands: - name: ssh steps: - type: exec command: "aws ssm start-session --target {{ .Arguments.instance_id }}" ``` The command inherits your terminal, environment, and working directory natively. Job control (Ctrl-Z) works exactly as if you'd run the command yourself, there's zero proxy overhead, and the command's exit code becomes the Atmos exit code. The trade-offs are inherent to process replacement: the exec step must be the final step (validated), no secret masking applies, and nothing runs after it. Rule of thumb: use `tty`/`interactive` when you want Atmos supervising (masking, multiple steps, retries); use `type: exec` when the command _is_ the destination — SSM sessions, SSH, consoles. ## How It Works With `tty: true`, Atmos runs the step under a pseudo-terminal (the same mechanism [`atmos devcontainer attach`](/cli/commands/devcontainer/attach) uses). With `interactive: true`, your terminal switches to raw mode for the duration of the step, so the Ctrl-C byte travels through the PTY to the child process — Atmos never even sees the signal. Secret masking is applied to the PTY output stream, so credentials in session output are still redacted. Pseudo-terminals are supported on macOS and Linux. On Windows, `tty: true` falls back to attaching the real console streams directly; sessions still work and Ctrl-C still goes to the step, but secret masking is unavailable in that mode (Atmos warns when masking is enabled). ## How to Use It See the [Interactive and TTY Steps](/cli/configuration/commands/steps#interactive-and-tty-steps) documentation for the full reference, including semantics of each flag and platform notes. For usage and configuration, see [interactive](/workflows/steps/interactive). ## Get Involved Have a use case that still doesn't fit — port-forwarding helpers, REPLs, TUIs? [Open an issue](https://github.com/cloudposse/atmos/issues) or join us in the [Atmos community](https://cloudposse.com/slack). --- ## Unified Task Runner for Custom Commands Custom commands now support structured task syntax with per-step configuration including timeouts, retry logic, working directories, and authentication identities. ## What Changed We've introduced a new `pkg/runner` package that provides a unified task execution layer for custom commands. This enables structured syntax alongside the existing simple string syntax: **Simple syntax (still works):** ```yaml commands: - name: hello steps: - "echo Hello world!" - "echo Goodbye!" ``` **New structured syntax:** ```yaml commands: - name: deploy steps: - name: validate command: terraform validate timeout: 30s - name: apply command: terraform apply -auto-approve timeout: 10m working_directory: /app/infra ``` **Mixed syntax:** ```yaml commands: - name: build steps: - "echo Starting build..." - name: compile command: make build timeout: 5m - "echo Build complete!" ``` **With retry for flaky operations:** ```yaml commands: - name: sync steps: - name: upload command: aws s3 sync ./dist s3://mybucket retry: max_attempts: 3 initial_delay: 1s max_delay: 30s ``` **Using atmos commands with identity:** ```yaml commands: - name: deploy-all steps: - name: deploy-vpc command: terraform apply vpc type: atmos stack: prod-us-east-1 identity: production-deployer timeout: 15m ``` ## Why This Matters This change lays the groundwork for unifying custom commands and workflows under a shared execution model. The new `Task` type supports: - **Timeouts** - Prevent runaway commands with per-step time limits - **Working directories** - Execute steps in specific directories - **Task types** - `shell` (default) for shell commands, `atmos` for atmos CLI commands with stack support - **Retry configuration** - Built-in retry support with configurable attempts and backoff delays - **Identity** - Per-step authentication identity for multi-account deployments - **Stack** - Specify which stack to use for `atmos` type commands ## For Contributors The new `pkg/runner` package provides: - `Task` and `Tasks` types with flexible YAML unmarshaling - `CommandRunner` interface for testable command execution - `Run()` and `RunAll()` functions with context-based timeout enforcement - Full mapstructure decode hook support for Viper integration This is an internal architectural improvement with full backward compatibility for existing custom command configurations. For more details, see the [Custom Commands documentation](/cli/configuration/commands). For usage and configuration, see [steps](/cli/configuration/commands/steps). --- ## New !unset YAML Function to Delete Inherited Keys Atmos now supports the [`!unset`](/functions/yaml/unset) YAML function, which **removes a key entirely** from a stack configuration during inheritance and merging. It's the clean way to drop a value that a parent stack or import defined, without resorting to workarounds. ## The Problem Atmos configuration is built from layered imports and inheritance. That's a strength — until you need to _remove_ something a base layer set. Setting a key to `null` doesn't remove it; it keeps the key with a null value, which downstream merges (and Terraform) still see: ```yaml import: - base # defines vpc.vars.enable_vpn_gateway: true components: terraform: vpc: vars: # Keeps the key, now as null — not the same as "not set". enable_vpn_gateway: null ``` There was no first-class way to say "pretend the parent never set this." ## The Solution The `!unset` function marks a key for deletion. After processing, the key is gone — `IsSet` returns false and it never reaches the merged config: ```yaml import: - base components: terraform: vpc: vars: # Remove the inherited value entirely. enable_vpn_gateway: !unset # Override a sibling as usual. enable_nat_gateway: false ``` The `dev` stack ends up with `enable_nat_gateway: false`, **no** `enable_vpn_gateway` key, and everything else inherited from `base`. ## Use Cases ### Clean overrides by exception Keep a rich base catalog and remove only what a specific environment shouldn't have: ```yaml components: terraform: app: vars: config: database: # Drop the inherited backup config for dev. backup_enabled: !unset host: "dev.db.example.com" ``` ### Removing whole sections `!unset` works at any depth, including entire mapping sections: ```yaml settings: # Remove an inherited integration block wholesale. spacelift: !unset ``` ### Trimming inherited lists Use it to drop a list item that inheritance brought in, alongside the values you keep. ## How It Works `!unset` is handled during YAML preprocessing and stack merging. The key is truly removed from the underlying configuration store rather than being set to null, so it does not appear in `AllSettings()` and won't be re-introduced by later merges. Sibling and ancestor keys are preserved. ## Get Started The `!unset` function is available now. Check out the [documentation](/functions/yaml/unset) for more examples and details, and browse the full set of [Atmos YAML functions](/functions/yaml). --- ## Unsupported YAML Function Validation Atmos now detects unsupported and misspelled YAML function tags before configuration processing continues. Invalid tags return a clear error with the supported Atmos YAML functions, so typos fail fast instead of being silently treated as ordinary YAML. ## What Changed Atmos validates explicit custom YAML tags during `atmos.yaml` and stack manifest processing. If a tag is not one of the supported Atmos YAML functions, Atmos returns an unsupported tag error that includes the valid options. ```yaml vars: home: !envv HOME ``` Instead of quietly accepting the misspelled `!envv` tag, Atmos reports that the tag is unsupported and points users toward valid functions like [`!env`](/functions/yaml/env), [`!exec`](/functions/yaml/exec), [`!include`](/functions/yaml/include), and the other supported YAML tags. ## Exact Matching Validation uses exact tag matching. A misspelled function such as `!envv` no longer matches the supported `!env` prefix, which prevents typos from slipping through parser or resolver paths that previously checked only the beginning of a tag. Standard YAML tags such as `!!str` continue to work normally. ## Why It Matters YAML functions are often used for dynamic values: environment variables, Terraform outputs, secrets, Git metadata, and included files. A misspelled tag can otherwise hide until much later in a deployment workflow, where the failure is harder to connect to the original configuration. Failing at parse or processing time makes the error immediate and actionable. ## Get Started No configuration is required. Use the documented [Atmos YAML functions](/functions/yaml) as usual, and Atmos will report unsupported tags when it finds them. --- ## Install Atmos from a Branch or Tag with ref: `--use-version` now accepts a `ref:` prefix, so you can run the latest build of any branch or tag — like `ref:main` — without looking up a commit SHA. ## What's New [`--use-version`](/cli/configuration/version/use) already let you install Atmos from a pull request (`pr:2040`) or a specific commit (`sha:ceb7526`). The new `ref:` prefix adds branches and tags: ```bash # Latest build of main atmos --use-version=ref:main version # Latest build on a release branch atmos --use-version=ref:release/v1.199 terraform plan -s mystack # A tag's commit build atmos --use-version=ref:v1.199.0 version # Disambiguate a branch vs a tag of the same name atmos --use-version=ref:heads/main version atmos --use-version=ref:tags/v1.199.0 version ``` It works everywhere `--use-version` does, including `version.use` in `atmos.yaml` and the `ATMOS_USE_VERSION` environment variable: ```yaml title="atmos.yaml" version: use: "ref:main" ``` ## How It Works When you pass a `ref:`, Atmos: 1. **Resolves the ref** — asks GitHub for the ref's current commit SHA (one lightweight API call). 2. **Reuses the SHA install path** — downloads that commit's CI build artifact for your platform, caches it, and re-executes — exactly like `sha:`. Because the ref is resolved on every run, a mutable branch like `main` always tracks the latest build. The resolved commit is cached by SHA, so if the branch hasn't moved, there's no reinstall — and when it does move, the new build installs automatically. This also means `ref:` is the most reliable way to pin a "moving target": you write `ref:main` once instead of chasing a new `sha:` after every merge. ## When to Use Which - **`ref:main`** — always run the newest build of a branch (great for trying unreleased fixes on `main`). - **`pr:2040`** — test the exact changes in an open pull request. - **`sha:ceb7526`** — pin to one immutable commit. - **`1.199.0`** — install a released, versioned binary. Like `pr:` and `sha:`, the `ref:` form installs **CI build artifacts** (retained for 90 days), not released binaries. For an older tag whose artifacts have expired, install the release with the plain semver form (`1.199.0`) instead. ## Get Involved Try it with `atmos --use-version=ref:main version` and let us know how it goes. As always, [join us in the Cloud Posse community](https://cloudposse.com/slack) if you have questions or feedback. --- ## Manage Vendored Dependencies Locally Package managers normally let you discover available updates, review their impact, and apply them locally. Vendored Atmos components did not: the only updater was a GitHub Action, so there was no native way to run that workflow from your workstation before opening a pull request. ## The Problem Vendored components pin an upstream version in `vendor.yaml` or `component.yaml`. Before this release, Atmos could pull that pinned version, but it could not tell you whether a newer version was available, show you what would change, or update the pin locally. Those tasks required the legacy Component Updater GitHub Action or a manual sequence of checking upstream tags, cloning a repository to compare versions, and editing YAML by hand. That is a poor fit for a dependency-management workflow. You should be able to inspect an update where you work, decide whether to accept it, and make the change without sending a trial pull request through CI. ## The Fix Atmos now provides that package-management workflow directly in the CLI. - **`atmos vendor update`** finds newer versions for Git-backed component sources, honors each source's version constraints, and updates the pinned version in place while preserving comments, anchors, templates, and formatting. Use [`--check`](/cli/commands/vendor/vendor-update#flags) to preview available updates without writing files, or [`--pull`](/cli/commands/vendor/vendor-update#flags) to pull the updated sources immediately afterward. - **`atmos vendor diff`** compares a component's current pinned version with another tag, branch, or commit so you can review the upstream changes before updating. Both commands work from your local workspace. They support sources declared in a repository-level `vendor.yaml` or in per-component `component.yaml` files, including repositories that use both styles. They also respect `vendor.base_path`, `ATMOS_VENDOR_BASE_PATH`, and `--chdir`, so the commands use the same vendoring layout as [`atmos vendor pull`](/cli/commands/vendor/pull). ### Migrating from the legacy GitHub Action The `cloudposse/github-action-atmos-component-updater` GitHub Action is deprecated. Use the native package-management commands, [`atmos vendor update`](/cli/commands/vendor/vendor-update) and [`atmos vendor diff`](/cli/commands/vendor/vendor-diff), instead. The legacy Action documentation remains available as a [deprecated reference](/deprecated/github-actions/component-updater). ## How to Use It ```shell # See which components have an eligible newer version. atmos vendor update --check # Review the current pinned version against the latest tag. atmos vendor diff --component vpc # Update one component, or every component tagged "networking". atmos vendor update --component vpc atmos vendor update --tags networking # Update pins and then pull the selected component sources. atmos vendor update --pull # Compare two explicit versions and limit the diff to one file. atmos vendor diff --component vpc --from 1.0.0 --to 2.0.0 --diff-file variables.tf ``` ## Get Involved Read the [Vendoring](/vendor/) overview, [`vendor diff`](/cli/commands/vendor/vendor-diff), and [`vendor update`](/cli/commands/vendor/vendor-update) documentation for the full reference. To share feedback or request an improvement, [open an issue](https://github.com/cloudposse/atmos/issues). --- ## Verify Vendored Files Never Silently Drift Vendoring pulls external code into your own repository so it's reviewable, diffable, and not subject to an upstream registry going away. But once those files land on disk, nothing has watched them since. A teammate edits a vendored file directly to work around a bug. A pull can interrupt partway through. CI reuses a runner's disk across jobs. Every one of these leaves your checkout silently out of sync with what Atmos actually vendored. The first sign of trouble is usually a broken `terraform plan` weeks later — not the moment the drift happened. ## The Problem The [`atmos vendor pull`](/cli/commands/vendor/pull) command always re-fetches everything, every time, whether or not anything actually changed. That makes it slow to lean on as a drift check, so most teams just don't run it that way — they trust the checkout and find out otherwise the hard way. There was also no way to answer "does what's on disk still match what was vendored" without a network round trip, and no way to choose how loudly a stale checkout should complain before Atmos quietly re-fetches it. ## The Fix Every `atmos vendor pull` now records a `vendor.lock.yaml` receipt for each vendored source: its declared origin, a resolved identity, and a checksum for every file it wrote. That receipt is what [`atmos vendor verify`](/cli/commands/vendor/vendor-verify) checks against, with zero network access: ```shell atmos vendor verify ``` ```text COMPONENT PATH REASON vpc components/terraform/vpc/main.tf checksum mismatch ``` It exits non-zero the moment anything doesn't match — a missing file or a modified one — so it drops straight into a CI gate. Add `--component ` to scope the check, or `--format json` for machine-readable output. You also get to choose how a drifted checkout behaves on the next `atmos vendor pull`, instead of always silently re-fetching: ```yaml vendor: lock: enforcement: warn # silent | warn | strict ``` - The `silent` mode re-fetches with no reporting — the behavior every `vendor pull` had before this existed. - The `warn` mode (the default) re-fetches and prints one line naming what drifted and why. - The `strict` mode refuses to run at all until you pass `--refresh-lock`, so an unreviewed local edit can never get silently overwritten — or silently kept — without someone noticing. ## How to Use It Gate CI on drift the same way you'd gate on any other check: ```shell atmos vendor verify || exit 1 ``` Override enforcement for a single invocation without touching `atmos.yaml`: ```shell atmos vendor pull --lock-enforcement=strict ``` And when a source genuinely needs to move — not just recover from drift — `version:` can now be a semver range instead of only an exact pin: ```yaml sources: - component: vpc source: github.com/cloudposse/terraform-aws-vpc.git version: "^1.0.0" ``` The first `atmos vendor pull` resolves that range to a concrete tag and locks it there — every later pull reuses the locked version with no network call at all, until an explicit [`atmos vendor update`](/cli/commands/vendor/vendor-update) or `--refresh-lock` re-resolves it. An exact pin like `version: v1.5.0` behaves exactly as it always has: the manifest itself remains the single source of truth for what gets fetched. ## Get Involved Questions about lock enforcement, `vendor verify`, or version ranges are welcome in the [Atmos GitHub repository](https://github.com/cloudposse/atmos) and the community Slack. --- ## Select Vendored Components by Stack, Label, or Tag A CI job deploying the dev stack shouldn't need to vendor every component in the repository -- just the ones dev actually uses. Production deploys have the same problem in reverse: pulling in components that belong to other environments wastes time and widens what that job can touch. Selecting the right subset meant either hand-listing every component with repeated [`--component`](/cli/commands/vendor/pull#flags) flags or reaching for [`--everything`](/cli/commands/vendor/pull#flags) and pulling in components the job has nothing to do with. ## The Problem Scoping a vendor pull to "just what this stack needs" is a selection CI reaches for on every deploy, not an occasional convenience. Without it, the choice was between hand-listing every component or vendoring the entire repository. Neither scales, and neither composes: picking components by tag, by name, and by the stack they belong to used to be three separate, mutually exclusive modes rather than filters that could work together. ## The Fix [`atmos vendor pull`](/cli/commands/vendor/pull) now accepts [`--stack`](/cli/commands/vendor/pull#flags) and [`--labels`](/cli/commands/vendor/pull#flags) selectors, and [`--tags`](/cli/commands/vendor/pull#flags) composes with either of them (or with `--component`) as an independent, narrowing filter -- across `pull`, `diff`, `clean`, `update`, and `verify` alike: - `--stack` vendors every component declared in that stack that has its own `component.yaml`. - `--labels` filters that eligible stack component set by each component's stack [`metadata.labels`](/stacks/components/component-metadata#labels). - `--tags` narrows whichever set `--stack`/`--labels` (or `--component`) resolved, down to components whose declared source tags match. Combining selectors that don't overlap now fails with a clear error instead of silently matching nothing or falling back to a different set than you asked for. ## How to Use It ```shell # Vendor each stack component that has a component.yaml atmos vendor pull --stack plat-ue2-dev # Select by stack metadata.labels instead of stack name atmos vendor pull --labels tier=1,cost-center:platform # Narrow a stack selection further by declared tag atmos vendor pull --stack plat-ue2-dev --tags networking # Preview instead of pulling atmos vendor pull --stack plat-ue2-dev --dry-run ``` The same selectors work on the other vendor subcommands: ```shell atmos vendor diff --stack plat-ue2-dev --tags networking atmos vendor update --labels tier=1 --check atmos vendor clean --stack plat-ue2-dev atmos vendor verify --labels tier=1 ``` For `atmos vendor pull`, `--stack`/`--labels` install each matched component from its own `component.yaml`, bypassing `vendor.yaml` entirely -- components without one are skipped, and if every matched component lacks a `component.yaml` the command succeeds having pulled nothing. It only fails with the shared selector's "no components matched" error when `--stack`/`--labels` itself resolves to no stack, or when `--tags` narrows an already non-empty selection down to zero. `--component` still selects a single, explicitly named component and isn't combined with `--stack`/`--labels`, but composes with `--tags` the same way they do. `diff`, `update`, `clean`, and `verify` use the same selectors to resolve component names, but retain their own manifest-resolution rules (`vendor.yaml` first, falling back to `component.yaml`). ## Get Involved Have feedback or ideas for vendor improvements? Join our [Slack community](https://slack.cloudposse.com) or open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Per-Target Version Overrides in Vendor Manifests Vendor targets now accept optional version overrides, enabling multiple versions of the same component from a single source entry. ## What Changed The `targets` field in vendor manifests now supports both the original string syntax and a new map syntax with `path` and `version` keys. When a target specifies its own `version`, the source URL template is re-resolved with that version. **Before** (still works): ```yaml targets: - "components/terraform/vpc" ``` **New map syntax with per-target version override:** ```yaml targets: - path: "components/terraform/vpc/{{.Version}}" version: "2.1.0" - path: "components/terraform/vpc/{{.Version}}" version: "3.0.0" ``` Both syntaxes can be mixed freely within the same targets list. ## Why This Matters Previously, vendoring multiple versions of the same component required duplicating the entire source entry for each version. With per-target version overrides, a single source entry can vendor the same component to multiple paths with different versions: ```yaml spec: sources: - component: vpc source: "github.com/cloudposse/terraform-aws-vpc.git///?ref={{.Version}}" version: "2.1.0" targets: - "components/terraform/vpc" - path: "components/terraform/vpc/{{.Version}}" version: "3.0.0" ``` This vendors version 2.1.0 to `components/terraform/vpc` and version 3.0.0 to `components/terraform/vpc/3.0.0`, all from a single source definition. ## How to Use It Update your `vendor.yaml` to use the map syntax for any targets that need a different version than the source-level default. The `path` field is required; the `version` field is optional. When `version` is omitted, the target inherits the source-level version as before. See the [vendoring documentation](/design-patterns/version-management/vendoring-components) for more details and examples. For usage and configuration, see [Vendor Configuration](/cli/configuration/vendor). --- ## Command Aliases for Vendor and Workflow List Atmos now supports [`atmos vendor list`](/cli/commands/vendor/usage) and [`atmos workflow list`](/cli/commands/workflow) as aliases for their [`atmos list vendor`](/cli/commands/list/list-vendor) and [`atmos list workflows`](/cli/commands/list/list-workflows) counterparts. ## What Changed Two new command aliases make discoverability more intuitive: - `atmos vendor list` is now an alias for `atmos list vendor` - `atmos workflow list` is now an alias for `atmos list workflows` Both command forms are equivalent and share the same flags, completions, and output. ## Why This Matters Users naturally expect list commands under the parent command (e.g., `atmos vendor list`), while Atmos organizes all list commands under [`atmos list`](/cli/commands/list/usage). This bidirectional aliasing follows the same pattern as [`atmos list themes`](/cli/commands/list/themes) and [`atmos theme list`](/cli/commands/theme/list). ## How to Use It Both forms work identically: ```bash # These are equivalent atmos vendor list atmos list vendor # These are equivalent atmos workflow list atmos list workflows # All flags work the same atmos vendor list --format json atmos workflow list --format csv ``` --- ## Version-Aware JIT Source Provisioning with TTL Atmos now supports intelligent version-aware JIT (Just-In-Time) source provisioning with automatic re-provisioning on version changes and TTL-based cleanup for stale workdirs. ## What Changed The workdir provisioning system has been enhanced with version tracking and intelligent re-provisioning: - **Version-aware re-provisioning** - When a remote source version changes in your configuration, Atmos automatically re-provisions the workdir with the new version - **URI change detection** - Changing the source URI triggers re-provisioning to ensure you always have the correct source - **Incremental local sync** - Local component changes are synced using per-file checksums, copying only modified files - **TTL-based cleanup** - Stale workdirs can be cleaned up automatically based on last-accessed time - **Enhanced metadata** - Workdirs now track `source_uri`, `source_version`, and `last_accessed` timestamps ## Why This Matters Previously, if you updated a component's source version in your stack configuration, you had to manually clean the workdir before the change would take effect. Now Atmos handles this automatically: - **Seamless version upgrades** - Change `version: "0.24.0"` to `version: "0.25.0"` and run `terraform plan` - Atmos detects the mismatch and re-provisions automatically - **Reduced disk usage** - The `--expired` cleanup option removes workdirs that haven't been accessed within a configurable TTL - **Faster local iteration** - Only modified files are synced to workdirs, making local development faster - **Full visibility** - The `workdir list` and `workdir show` commands now display version and access information ## How to Use It ### Version-Aware Provisioning Configure a component with a source version: ```yaml components: terraform: vpc: source: uri: "github.com/cloudposse/terraform-aws-vpc//src" version: "0.25.0" provision: workdir: enabled: true ``` When you change the version, Atmos automatically re-provisions: ```bash $ atmos terraform plan vpc -s dev # Output: "Source version changed (0.24.0 → 0.25.0)" # Workdir is re-provisioned with the new version ``` ### TTL-Based Cleanup Clean workdirs that haven't been accessed in a week: ```bash $ atmos terraform workdir clean --expired --ttl=7d Cleaning 2 expired workdir(s) (TTL: 7d)... ✓ Removed dev-nginx-cfcfbd1f (last accessed 14d 2h ago) ✓ Removed staging-api-c28f3444 (last accessed 10d 5h ago) Cleaned 2 expired workdir(s) ``` Preview what would be cleaned with dry-run: ```bash $ atmos terraform workdir clean --expired --ttl=7d --dry-run Dry run: would clean 2 expired workdir(s) (TTL: 7d): dev-nginx-cfcfbd1f (last accessed 14d 2h ago) staging-api-c28f3444 (last accessed 10d 5h ago) ``` ### Enhanced Workdir Information View detailed workdir information: ```bash $ atmos terraform workdir show vpc --stack dev ✓ Workdir Status Name dev-vpc-bb03116d Component vpc Stack dev Source Type remote Source URI github.com/cloudposse/terraform-aws-vpc//src Source Version 0.25.0 Path .workdir/terraform/dev-vpc-bb03116d Created 2026-01-20 10:00:00 UTC Updated 2026-01-22 14:30:00 UTC Last Accessed 2026-01-22 14:30:00 UTC ``` List workdirs with version and access information: ```bash $ atmos terraform workdir list COMPONENT STACK TYPE VERSION LAST_ACCESSED PATH vpc dev remote 0.25.0 2026-01-22 14:30 .workdir/terraform/dev-vpc-bb03116d my-local dev local - 2026-01-22 10:15 .workdir/terraform/dev-my-local-f5cd33a0 ``` ## TTL Format The `--ttl` flag supports various duration formats: - **Time units**: `30m`, `2h`, `7d` (minutes, hours, days) - **Keywords**: `hourly`, `daily`, `weekly`, `monthly` - **Seconds**: `3600` (plain integers are interpreted as seconds) For usage and configuration, see [Source](/vendor/component-manifest/source). ## Get Involved Have feedback on the version-aware provisioning feature? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues) or join the discussion in our [Slack community](https://slack.cloudposse.com). --- ## Enforce Atmos Version Requirements with Version Constraints Atmos now supports version constraint validation, allowing you to specify required Atmos version ranges in your `atmos.yaml` configuration. When your configuration requires specific features or behaviors, you can ensure all team members and CI/CD pipelines use compatible Atmos versions. ## The Problem Teams using Atmos often face version consistency challenges: - **Feature availability** - Newer configurations may use features that don't exist in older Atmos versions - **Breaking changes** - Running an incompatible version can cause confusing errors - **Environment drift** - CI pipelines, local development, and containers may run different versions - **No clear signal** - Users don't know when upgrading is required vs. recommended ## The Solution Add a `version.constraint` section to your `atmos.yaml`: ```yaml version: constraint: require: ">=1.100.0, <2.0.0" enforcement: "fatal" message: "Please upgrade Atmos to continue." ``` Atmos validates the constraint at startup. If the current version doesn't satisfy the requirement: - **`fatal`** (default) - Exit with a helpful error message - **`warn`** - Show a warning and continue - **`silent`** - Skip validation (for debugging) ## Constraint Syntax Uses the same syntax as Terraform version constraints: | Constraint | Meaning | |------------|---------| | `>=1.100.0` | Minimum version | | `<2.0.0` | Maximum version (exclusive) | | `>=1.100.0, <2.0.0` | Version range | | `~>1.100` | Pessimistic (>=1.100.0, \<2.0.0) | | `!=1.150.0` | Exclude specific version | ## Override for Debugging Use the environment variable to temporarily bypass constraints: ```bash ATMOS_VERSION_ENFORCEMENT=warn atmos terraform plan ``` ## Get Started Add version constraints to your `atmos.yaml` today to ensure consistent Atmos versions across your team and infrastructure. See the [Version Constraints documentation](/cli/configuration/version/constraint) for complete details. --- ## Browse and Explore Atmos Releases from Your Terminal We're introducing two new commands for exploring Atmos releases: [`atmos version list`](/cli/commands/version/list) and [`atmos version show`](/cli/commands/version/show). Browse release history with date filtering, inspect artifacts, and keep your infrastructure tooling up-to-date—all from your terminal with beautiful formatted output. ## What's New ### `atmos version list`: Browse All Releases The new `atmos version list` command displays recent Atmos releases in a clean, formatted table: ```bash $ atmos version list ``` **Features:** - 📋 **Clean table view** - Borderless table with header separator - 📖 **Markdown-rendered titles** - Release titles displayed with proper formatting and colors - 📅 **Date filtering** - Filter releases with [`--since`](/cli/commands/version/list#flags) (ISO 8601 dates) - 📄 **Pagination support** - Browse through extensive release history with [`--limit`](/cli/commands/version/list#flags) and [`--offset`](/cli/commands/version/list#flags) - ✨ **Current version indicator** - Green bullet (●) marks your installed version - 🔄 **Spinner feedback** - Visual feedback during GitHub API calls - 📱 **Terminal width detection** - Automatically adapts to your terminal size ### `atmos version show`: Dive into Release Details Want to see what's in a specific release? Use `atmos version show`: ```bash $ atmos version show v1.95.0 ``` This displays: - **Full release notes** rendered in Markdown with colors preserved - **Release metadata** (version, publication date, title) - **Platform-specific artifacts** - Only shows assets matching your OS and architecture - **File sizes and download URLs** - Styled links for easy access ```bash # View the latest release $ atmos version show # View a specific version $ atmos version show v1.95.0 # Works without 'v' prefix too $ atmos version show 1.95.0 ``` ## Why This Matters ### For Platform Engineers Before, discovering Atmos releases meant context-switching to GitHub: ```bash # Old workflow $ atmos version 👽 Atmos 1.94.0 on darwin/arm64 # Now open browser, navigate to GitHub releases... # Scroll through releases, click around... # Copy version number... ``` Now, everything stays in your terminal: ```bash # New workflow $ atmos version list # View releases in a formatted table # Read release notes with 'atmos version show' # All without leaving your terminal ``` ### For Infrastructure Teams **Release Auditing:** ```bash # Export release data for compliance atmos version list --format json > releases.json # Script version discovery in CI/CD VERSION=$(atmos version list --format json | jq -r '.releases[0].version') ``` **Changelog Review:** ```bash # Quickly review recent changes before upgrading atmos version list --limit 5 # Compare current version to latest atmos version show latest ``` ### For Contributors **Verify Releases:** ```bash # Check that your release published correctly atmos version show v1.95.0 # Inspect release artifacts and download URLs atmos version show v1.95.0 ``` ## How to Use It ### Basic Usage List the last 10 releases (default): ```bash $ atmos version list ``` List more releases with pagination: ```bash # Show 20 releases $ atmos version list --limit 20 # Skip first 10, show next 10 $ atmos version list --limit 10 --offset 10 ``` Filter by date: ```bash # Show releases since specific date (ISO 8601 format) $ atmos version list --since 2025-01-01 ``` Include prerelease versions (beta, alpha, rc): ```bash # By default, only stable releases are shown # Use this flag to include prereleases $ atmos version list --include-prereleases ``` ### Machine-Readable Output Perfect for scripting: ```bash # JSON output $ atmos version list --format json # YAML output $ atmos version list --format yaml ``` **Example JSON output:** ```json { "releases": [ { "version": "v1.95.0", "title": "Enhanced Vendoring and Bug Fixes", "published_at": "2025-04-15T10:30:00Z", "url": "https://github.com/cloudposse/atmos/releases/tag/v1.95.0", "prerelease": false, "current": true } ] } ``` ## Performance & Rate Limits ### GitHub API Rate Limits **Without authentication:** 60 requests/hour **With authentication:** 5,000 requests/hour To increase your rate limit, set a GitHub token: ```bash # Get your token from GitHub CLI export ATMOS_GITHUB_TOKEN=$(gh auth token) # Or set GITHUB_TOKEN directly export GITHUB_TOKEN="ghp_xxxxxxxxxxxx" ``` No special scopes are needed for public repositories. ## Examples ### Find Recent Features ```bash # List last 5 releases $ atmos version list --limit 5 # View details on the latest $ atmos version show ``` ### Audit Release History for Compliance ```bash # Export all releases to JSON $ atmos version list --limit 100 --format json > all-releases.json # Parse with jq for specific info $ atmos version list --format json | \ jq -r '.releases[] | "\(.version) - \(.published_at)"' ``` ### Check Release Artifacts Before Downloading ```bash # View release details with platform-specific artifacts $ atmos version show v1.95.0 # See artifact sizes and download URLs ``` ## Technical Details ### Implementation Highlights - **Formatted table output** using [Charmbracelet lipgloss/table](https://github.com/charmbracelet/lipgloss) with automatic word wrapping - **Markdown rendering** powered by [Glamour](https://github.com/charmbracelet/glamour) preserving ANSI colors - **Loading spinner** built with [Charmbracelet Bubbletea](https://github.com/charmbracelet/bubbletea) for TTY detection - **GitHub API integration** using [go-github](https://github.com/google/go-github) with OAuth2 authentication - **Platform-specific filtering** matches assets to runtime.GOOS and runtime.GOARCH - **Terminal width detection** using Atmos's existing utilities - **Command registry pattern** for modular organization ### For Developers If you're interested in the implementation details, check out: - **[PRD: Version List Command](https://github.com/cloudposse/atmos/blob/main/docs/prd/version-list-command.md)** - Complete design document - **[CLI Documentation](https://atmos.tools/cli/commands/version/list)** - Usage reference ## Try It Now Upgrade to the latest Atmos release and try it yourself: ```bash # Check your current version atmos version # Browse available releases atmos version list # View details on latest atmos version show ``` ## Get Involved We're building Atmos in the open and welcome your feedback: - 💬 **Discuss** - Share thoughts in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions) - 🐛 **Report Issues** - Found a bug? [Open an issue](https://github.com/cloudposse/atmos/issues) - 🚀 **Contribute** - Want to add features? Review our [contribution guide](https://atmos.tools/community/contributing). --- **Want to learn more?** Read the full [Version List Command PRD](https://github.com/cloudposse/atmos/blob/main/docs/prd/version-list-command.md) for detailed technical information. --- ## Keep version fields in JSON files in sync without a template Plain JSON has no comment syntax, so there's nowhere to put an annotation telling a tool which field carries a managed version. Rewriting the whole file from a parsed structure works, but it reflows formatting, reorders keys, and turns a one-line diff into a noisy one. Neither option was a good fit for keeping a `version` field in a `package.json`, plugin manifest, or marketplace listing in sync with a locked dependency. ## The Problem The [Version Tracker](/cli/configuration/version/files)'s `marker` manager rewrites version tokens on lines annotated with an `atmos:version` comment — but JSON has no comment syntax, so there's no line to annotate. The `template` manager covers formats like this by rendering a `*.tmpl` source to a sibling output file, but that means maintaining a template and a generated file as two files that have to be kept in sync by hand every time an unrelated field in the JSON document changes. ## The Fix A new `json` file manager writes locked values directly into JSON files at configured field paths. It patches only the targeted field and leaves everything else in the document — key order, spacing, unrelated fields — exactly as it was, so [`atmos version track apply`](/cli/commands/version/track/apply) produces a minimal, single-field diff instead of a fully reformatted file. ## How to Use It ```yaml version: dependencies: atmos: ecosystem: github/actions datasource: github-releases provider: github package: cloudposse/atmos desired: "~1.160" files: - manager: json paths: - package.json options: set: - path: version from: atmos ``` A single rule can target more than one file, and a single file can carry more than one managed field — each just needs its own `path`/`from` entry under `set`: ```yaml files: - manager: json paths: - package.json options: set: - path: version from: cli - path: engines.node from: node ``` `atmos version track apply` rewrites every configured field from the lock; `--check` fails and lists any file that's out of date, so CI can catch drift before it merges. ## Get Involved See the [Version Files](/cli/configuration/version/files#updating-json-files) docs for the full `path`/`from` syntax, including array indexing and escaping keys that contain literal dots. Have feedback on this feature? [Open an issue](https://github.com/cloudposse/atmos/issues) or join the conversation in the [Cloud Posse community Slack](https://cloudposse.com/slack). --- ## Keep version fields in YAML files in sync without losing comments A Helm values file, a docker-compose override, or any other hand-maintained YAML config often carries one field that tracks an external dependency's version, alongside comments explaining what the surrounding settings do and anchors sharing config between sections. Keeping that one field in sync usually means either editing it by hand on every release, or running it through a full parse-and- regenerate step that throws the comments and anchors away. ## The Problem The [Version Tracker](/cli/configuration/version/files)'s `template` manager covers YAML by treating a `*.tmpl` source as the source of truth and rendering it to a sibling output file — a good fit when the whole file is generated, but overkill for a hand-maintained file that only has one or two managed fields. The `marker` manager can annotate a line with a comment, but its rewrite is a plain token substitution, not an assignment into structured YAML. Neither manager patches a single field in an existing, hand-authored YAML document while leaving everything else — comments, anchors, key order — untouched. ## The Fix A new `yaml` file manager writes locked values directly into YAML files at configured field paths. It reuses Atmos's own format-preserving YAML editor — the same engine behind [`atmos config set`](/cli/commands/config/config-set) and [`atmos stack set`](/cli/commands/stack/stack-set) — so comments, anchors and aliases, and key order on untouched fields all survive the edit. ## How to Use It ```yaml version: dependencies: cli: ecosystem: github/releases datasource: github-releases provider: github package: cli/cli desired: "~2" files: - manager: yaml paths: - charts/*/values.yaml options: set: - path: version from: cli format: '{{ trimPrefix "v" .Version }}' ``` [`atmos version track apply`](/cli/commands/version/track/apply) rewrites the configured field from the lock, stripping the `v` prefix GitHub tags carry via the optional `format` template; `--check` fails and lists any file that's out of date, so CI can catch drift before it merges. ## Get Involved See the [Version Files](/cli/configuration/version/files#updating-yaml-files) docs for the full path syntax and the `format` field. Have feedback on this feature? [Open an issue](https://github.com/cloudposse/atmos/issues) or join the conversation in the [Cloud Posse community Slack](https://cloudposse.com/slack). --- ## Pin your manifest schema to an Atmos release, not a moving target Upgrading the `atmos` binary has always meant upgrading your schema validation too, whether you wanted to or not. The JSON Schema published at `atmos.tools/schemas/atmos/atmos-manifest/1.0/...` is a single, mutable file — every merge to `main` updates it in place. Bump the binary without touching your stack YAML, and the schema underneath your `atmos.yaml` pin can still change: fields you haven't reviewed yet suddenly validate, or a schema you were relying on to _reject_ unreleased fields quietly starts accepting them. ## The Problem Atmos has no concept of a versioned schema. There's one URL, and it always reflects whatever shipped most recently to `main`. Teams that pin `schemas.atmos.manifest` to that URL for CI-deterministic validation get a moving target: the schema they tested against last month isn't the schema running today. And there was no way to ask for "the schema as of Atmos 1.219.0" — only "the schema right now." Compounding the problem, the published schema and the schema actually compiled into the `atmos` binary (the one [`atmos validate stacks`](/cli/commands/validate/stacks) uses by default) were two separately hand-maintained files that had quietly drifted apart — the binary's copy was missing whole sections (`dependencies`, `generate`, `provision`, `source`, and dozens of newer workflow step fields) that the website copy had, and vice versa. ## The Fix Two changes land together: - **One schema, not two.** The schema embedded in the `atmos` binary (`pkg/datafetcher/schema/atmos/manifest/1.0.json`) is now the single source of truth. The copy published to `atmos.tools` is generated from it at build time — never hand-edited, never committed separately, never able to drift again. - **Per-release pinned snapshots.** Every Atmos release now publishes an immutable schema snapshot alongside the floating one: `atmos.tools/schemas/atmos/atmos-manifest//atmos-manifest.json`. Pin to it, and upgrading the binary later won't change what your stack YAML validates against until you deliberately bump the pin. ## How to Use It ```yaml title="atmos.yaml" schemas: atmos: # Floating — always the latest schema (the default if you don't set this). # manifest: "https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json" # Pinned — frozen to exactly what shipped with this Atmos release. manifest: "https://atmos.tools/schemas/atmos/atmos-manifest/1.219.0/atmos-manifest.json" ``` Most users don't need to change anything — if you don't set `schemas.atmos.manifest` at all, `atmos validate stacks` already defaults to the schema embedded in whatever binary you're running, which is exactly what you'd expect. The pinned URL is there for teams that explicitly want to decouple "upgrade the binary" from "adopt new manifest fields." A pinned URL only exists for releases going forward from this change — earlier releases only have the floating `1.0` path. For usage and configuration, see [atmos validate schema](/cli/commands/validate/schema). ## Get Involved See [Floating vs. Pinned Schema URLs](/cli/configuration/schemas#floating-vs-pinned-schema-urls) for the full picture, or open an issue if you hit a validation gap this doesn't cover. --- ## Welcome to the Atmos Changelog We're excited to launch the [Atmos](/intro) Changelog—your go-to source for feature announcements, technical deep dives, and best practices for managing cloud infrastructure at scale. ## What to Expect This changelog will be your window into the latest developments in Atmos. Here's what you'll find: - **Feature Announcements**: Get the first look at new capabilities as they're released, with practical examples showing how to use them - **Technical Deep Dives**: Understand how Atmos works under the hood and learn advanced patterns for complex scenarios - **Best Practices**: Discover proven approaches for organizing stacks, managing components, and scaling your infrastructure - **Tips & Tricks**: Learn shortcuts and techniques to make your Atmos workflows more efficient ## Stay Connected Atmos is constantly evolving based on feedback from teams managing real-world infrastructure. This changelog helps bridge the gap between releases and documentation, giving you context about why features exist and how they solve practical problems. For detailed documentation, visit [atmos.tools](https://atmos.tools). To discuss features or share your use cases, join us in [GitHub Discussions](https://github.com/orgs/cloudposse/discussions). Welcome aboard! --- ## Workflow Environment Variables Workflows now support environment variables at both workflow and step levels with hierarchical merging. ## What Changed You can now define environment variables in workflow YAML files at two levels: - **Workflow-level**: Applied to all steps in the workflow - **Step-level**: Applied to a specific step, overriding workflow-level variables with the same key Environment variables are merged hierarchically, with step-level values taking precedence over workflow-level values for the same keys. ## Example ```yaml workflows: deploy: description: Deploy with custom environment env: FOO: bar BAZ: workflow-value steps: - command: echo "$FOO - $BAZ" type: shell env: BAZ: step-value ``` Running this workflow outputs `bar - step-value` because: - `FOO` is inherited from the workflow level (`bar`) - `BAZ` is overridden by the step level (`step-value`) ## Why This Matters This feature enables several use cases: - **Consistent environment setup**: Define common variables once at the workflow level - **Step-specific overrides**: Override variables for specific steps without duplicating configuration - **Integration with external tools**: Pass environment variables to shell commands and Terraform ## Environment Variable Precedence The full precedence order (lowest to highest priority): 1. System environment variables 2. Global env from `atmos.yaml` 3. Workflow-level `env` 4. Step-level `env` 5. Auth identity env vars (if `identity` is specified) For usage and configuration, see [env](/workflows/env). ## Get Involved Have questions or feedback? Join us on [Slack](https://cloudposse.com/slack) or open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Workflow File Auto-Discovery: Run Workflows Without Specifying Files The [`atmos workflow`](/cli/commands/workflow) command now automatically discovers workflow files, eliminating the need to specify `--file` for uniquely named workflows. This developer experience improvement makes running workflows faster and more intuitive. ## What's New Previously, running a workflow required explicitly specifying the workflow file: ```bash # Before: Always needed --file flag atmos workflow deploy --file workflows/deploy.yaml ``` Now, if your workflow name is unique across all workflow files, Atmos automatically finds it: ```bash # After: Just specify the workflow name atmos workflow deploy ``` ## Why This Matters ### Faster Workflow Execution **Before:** You had to remember which file contained each workflow: ```bash # Which file was it in again? atmos workflow deploy --file workflows/deploy.yaml atmos workflow test --file workflows/ci.yaml atmos workflow cleanup --file workflows/cleanup.yaml ``` **After:** Just run the workflow by name: ```bash # Atmos finds it automatically atmos workflow deploy atmos workflow test atmos workflow cleanup ``` ### Better Developer Experience The `--file` flag is now **optional** for most workflows. You only need it when: - Multiple workflow files contain a workflow with the same name - You want to explicitly specify which file to use ### Consistent with Other Commands This brings workflow execution in line with other Atmos commands that auto-discover resources: ```bash # Components auto-discovery (existing) atmos terraform plan vpc -s prod # Workflows auto-discovery (new) atmos workflow deploy ``` ## How It Works When you run `atmos workflow ` without `--file`: 1. **Scans workflow directory** - Atmos searches all YAML files in your configured workflows path 2. **Finds matching workflows** - Looks for workflows with the specified name 3. **Auto-selects if unique** - If only one file contains that workflow name, it runs automatically 4. **Prompts if multiple** - If multiple files have the same workflow name, shows an interactive selector ### Interactive Selection for Duplicates If multiple workflow files contain the same workflow name, Atmos presents an interactive selector: ```text Multiple workflows found with name 'deploy'. Please choose: > production.yaml - Deploy to production environment staging.yaml - Deploy to staging environment development.yaml - Deploy to development environment Press ctrl+c or esc to exit ``` You can still use `--file` to skip the prompt: ```shell atmos workflow deploy --file workflows/production.yaml ``` ## Backward Compatibility All existing workflows continue to work exactly as before: ```bash # Explicit --file flag still works atmos workflow deploy --file workflows/deploy.yaml # Auto-discovery is purely additive atmos workflow deploy ``` ## Examples ### Basic Usage Run a workflow by name (auto-discovers the file): ```bash $ atmos workflow deploy ``` ### With Additional Flags All workflow flags work with auto-discovery: ```bash # Run with dry-run $ atmos workflow deploy --dry-run # Run for specific stack $ atmos workflow deploy --stack prod # Resume from a specific step $ atmos workflow deploy --from-step validate # Specify identity for authentication $ atmos workflow deploy --identity prod-admin ``` ### Explicit File Selection When you need precise control: ```bash # Explicitly specify which file $ atmos workflow deploy --file workflows/production.yaml # Useful in scripts or CI/CD $ atmos workflow deploy \ --file workflows/production.yaml \ --stack prod \ --identity prod-deployer ``` ## Interactive TUI Still Available Running `atmos workflow` without any arguments still launches the interactive TUI: ```bash # Interactive workflow browser $ atmos workflow ``` This shows all available workflows across all files, allowing you to browse and select interactively. For usage and configuration, see [Workflows](/cli/configuration/workflows). ## Get Involved We're building Atmos in the open and welcome your feedback: - 💬 **Discuss** - Share thoughts in [GitHub Discussions](https://github.com/cloudposse/atmos/discussions) - 🐛 **Report Issues** - Found a bug? [Open an issue](https://github.com/cloudposse/atmos/issues) - 🚀 **Contribute** - Want to add features? Review our [contribution guide](https://atmos.tools/community/contributing) --- **Related Documentation:** - [Workflows Guide](https://atmos.tools/core-concepts/workflows) - [Workflow Command Reference](https://atmos.tools/cli/commands/workflow) --- ## Use Tags and Labels with Workflows Atmos workflows already supported [`--stack`](/cli/commands/workflow#flags). They now also support [`--tags`](/cli/commands/workflow#flags) and [`--labels`](/cli/commands/workflow#flags), forwarding those selectors to every nested `type: atmos` step. For an introduction to defining and selecting [tags and labels](/changelog/tags-and-labels), see the original feature announcement. ## How to Use It Use tags and labels on their own: ```shell atmos workflow deploy --tags networking --labels deployment:dev ``` `--stack`, `--tags`, and `--labels` are all optional selectors. Combine the ones that define the scope you need: ```shell atmos workflow deploy \ --stack tenant1-ue2-dev \ --tags networking \ --labels deployment:dev ``` Use the same selectors you already use with normal Atmos commands, while keeping the workflow's ordering, dependencies, parallel steps, and matrix steps intact. ## Get Involved Try tags and labels with the workflows you already use for targeted deployments. If a workflow pattern needs a different selection model, open an issue with the command and component scope you want to express. --- ## Working Directory Support for Commands and Workflows Custom commands and workflow steps can now specify a `working_directory` to control where they execute. ## Working Directory for Commands and Workflows Custom commands and workflow steps can now specify a `working_directory` to control where they execute: ```yaml # .atmos.d/commands.yaml commands: - name: localstack description: Start LocalStack for local development working_directory: docker/localstack steps: - docker compose up -d ``` ```yaml # stacks/workflows/deploy.yaml workflows: build-and-deploy: steps: - command: make build working_directory: !repo-root type: shell - command: docker compose up -d working_directory: docker/app type: shell ``` - **Absolute paths** are used as-is - **Relative paths** resolve against `base_path` - **Step-level** overrides workflow-level settings ## Documentation - [Custom Commands](/cli/configuration/commands/working-directory) - [Workflows](/workflows/working-directory) --- ## Preserve Directory Hierarchy in Terraform State Buckets You can now preserve `/` in component names when Atmos auto-generates backend key prefixes — keeping your state bucket organized to match your component directory structure. ## What Changed A new `components.terraform.workspace.prefix_separator` setting in `atmos.yaml` controls how Atmos handles `/` characters in component names when auto-generating backend key prefixes (`workspace_key_prefix` for S3, `prefix` for GCS, `key` for Azure). ```yaml components: terraform: workspace: prefix_separator: "/" # Preserve directory hierarchy ``` Previously, Atmos always replaced `/` with `-`, flattening hierarchical component names: | Component | Before (default) | After (`prefix_separator: "/"`) | |---------------------------|-------------------------------------------------------|-------------------------------------------------------| | `services/consul` | `services-consul/workspace/terraform.tfstate` | `services/consul/workspace/terraform.tfstate` | | `platform/eks` | `platform-eks/workspace/terraform.tfstate` | `platform/eks/workspace/terraform.tfstate` | | `platform/services/vault` | `platform-services-vault/workspace/terraform.tfstate` | `platform/services/vault/workspace/terraform.tfstate` | ## Why This Matters Teams with large component libraries (hundreds of components) organize them in directory hierarchies like `services/consul`, `platform/eks`, `data/rds`. With the default `-` separator, the state bucket becomes a flat listing of hundreds of dash-separated prefixes — making it difficult to navigate. With `prefix_separator: "/"`, the state bucket mirrors the component directory structure, giving you the same hierarchy in both your source tree and your state storage. ## How to Use It Add the setting to your `atmos.yaml`: ```yaml components: terraform: workspace: prefix_separator: "/" ``` The setting applies to all three supported backends: - **S3** — `workspace_key_prefix` - **GCS** — `prefix` - **Azure** — the component portion of `key` Explicitly configured backend keys (e.g. `workspace_key_prefix` set directly in your stack config) are never modified — the separator only affects auto-generated values. :::warning State Migration Required Changing this setting on an existing project changes the backend key paths. Terraform will not find existing state files at the old paths. Before switching: 1. Identify affected components 2. Rename state paths in your backend storage (S3 bucket, GCS bucket, Azure storage) 3. Update `atmos.yaml` with the new separator 4. Run [`atmos terraform plan`](/cli/commands/terraform/plan) to verify no unexpected changes ::: For usage and configuration, see [Workspaces](/components/terraform/workspaces). ## Get Involved Found an issue or have a feature request? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues). --- ## Edit Atmos config, stacks, and vendor manifests without breaking YAML Atmos can now read and edit your YAML — `atmos.yaml`, stack manifests, and `vendor.yaml` — through first-class commands that **preserve comments, anchors, YAML functions, and Go templates**. No more `sed` or `yq` one-liners that strip your comments and reformat the file. ## What Changed Three new sets of dot-notation get/set/delete commands, all built on a shared, format-preserving YAML engine: - **[`atmos config get|set|delete `](/cli/commands/config/usage)** — edit the active `atmos.yaml`. - **[`atmos stack get|set|delete -s -c `](/cli/commands/stack/usage)** — edit a component's value in a stack. Atmos uses **provenance** to find the manifest that actually defines the effective (post-merge) value and edits that file. - **[`atmos vendor get|set [version]`](/cli/commands/vendor/usage)** — read or pin a vendored component's version, matched by name. ```shell atmos config set logs.level Debug atmos stack set vars.region us-west-2 -s plat-ue2-prod -c vpc atmos vendor set vpc v1.5.0 ``` ## Why This Matters Atmos YAML is human-authored: comments explain intent, anchors keep it DRY, and [`!terraform.output`](/functions/yaml/terraform.output) functions and `{{ … }}` templates carry real behavior. A naive "parse → re-serialize" edit destroys all of that. These commands operate at the node level (via the `yq` engine Atmos already ships) and are verified to preserve comments, anchors/aliases, Atmos YAML functions, and templates. A **strict guard** even refuses edits that would silently mutate a value shared through a YAML anchor. ## How to Use It Paths are dot-notation by default (`vars.region`, `sources[0].version`), and [`--type`](/cli/commands/stack/config/set#flags) lets you write typed values (`--type=bool`, `int`, `float`, `null`, or a raw `yaml` literal). For stacks, `get` reports where a value resolves from, and [`--file`](/cli/commands/stack/config/set#flags) lets you target a manifest explicitly. See the [`config`](/cli/commands/config/usage), [`stack`](/cli/commands/stack/usage), and [`vendor`](/cli/commands/vendor/usage) command docs. ## Get Involved This is the foundation for scripted, comment-safe configuration changes across Atmos. A normalize (`fmt`) command builds on the same engine and is on the way. --- ## Atmos Now Detects Circular Dependencies in YAML Functions Atmos now detects circular dependencies in YAML function calls and provides a clear call stack showing exactly where the cycle occurs. ## What Changed Previously, circular dependencies in YAML functions like [`!terraform.state`](/functions/yaml/terraform.state) and [`!terraform.output`](/functions/yaml/terraform.output) would cause stack overflow panics with cryptic error messages. Now Atmos detects these cycles before they cause problems and shows you exactly where the circular dependency exists. ## Why This Matters Circular dependencies can easily occur when components reference each other: ```yaml # Component A references Component B vars: vpc_id: !terraform.state vpc core vpc_id # Component B references Component A vars: transit_gateway_id: !terraform.state transit-gateway core tgw_id ``` ## How It Works When Atmos encounters a circular dependency, it now provides a detailed error message with the full dependency chain: ``` circular dependency detected Dependency chain: 1. Component 'vpc' in stack 'core' → !terraform.state transit-gateway core transit_gateway_id 2. Component 'transit-gateway' in stack 'core' → !terraform.state vpc core vpc_id 3. Component 'vpc' in stack 'core' (cycle detected) → !terraform.state transit-gateway core transit_gateway_id To fix this issue: - Review your component dependencies and break the circular reference - Consider using Terraform data sources or direct remote state instead - Ensure dependencies flow in one direction only ``` ## Performance Impact The cycle detection adds negligible overhead (less than 0.001% of execution time) and uses goroutine-local storage to ensure thread safety. For usage and configuration, see [Atmos YAML Functions](/functions/yaml). ## Get Involved - [GitHub Pull Request](https://github.com/cloudposse/atmos/pull/1708) - Share your feedback in the [Atmos Community Slack](https://cloudposse.com/slack) --- ## YAML Key Delimiter for Dot Notation in Stack Files Atmos now supports expanding dotted YAML keys into nested maps in stack configuration files. Enable the `key_delimiter` setting in `atmos.yaml` to use concise dot notation like `metadata.component: vpc-base` instead of deeply nested YAML structures. ## What Changed Stack YAML files now support **key delimiter expansion** — a configurable setting that transforms dotted keys into nested maps during YAML parsing. This brings stack files in line with how `atmos.yaml` already handles dotted keys via Viper. When enabled, unquoted keys containing the delimiter are automatically expanded: ```yaml # Before: deeply nested YAML components: terraform: vpc: metadata: component: vpc-base settings: spacelift: workspace_enabled: true # After: concise dot notation components: terraform: vpc: metadata.component: vpc-base settings.spacelift.workspace_enabled: true ``` ## Why This Matters Infrastructure configurations grow deep fast. Setting a single value like `settings.spacelift.workspace_enabled` previously required creating the full nested hierarchy — four levels of indentation for one boolean. Dot notation eliminates that ceremony while keeping the configuration readable. The feature is **opt-in**, **backwards compatible**, and marked as **experimental**. Existing configurations work exactly as before. Enable it only when you want it. The `settings.experimental` mode controls the notification behavior — see [Experimental Features](/cli/configuration/settings/experimental) for details. ## How to Use It Add the `key_delimiter` setting to your `atmos.yaml`: ```yaml settings: yaml: key_delimiter: "." ``` ### Quoting as Escape Quoted keys are never expanded, so you can mix dot notation with literal dotted key names: ```yaml # Expanded: becomes metadata: { component: vpc-base } metadata.component: vpc-base # Literal: stays as "output.json" "output.json": true ``` ### Custom Delimiters Use any string as the delimiter: ```yaml settings: yaml: key_delimiter: "::" ``` ```yaml metadata::component: vpc-base # Expanded output.json: true # Literal (dots are not the delimiter) ``` ## External Tooling Dot notation is an Atmos-specific extension to YAML. External tools like IDE linters, `yamllint`, and YAML language servers don't know about key expansion — they see the raw dotted keys and may flag them as schema violations. Atmos validates stack files _after_ expansion, so its own validation works correctly. But if your workflow depends on external YAML validation, keep this tradeoff in mind. For usage and configuration, see [key\_delimiter](/cli/configuration/settings#yaml-settings). ## Get Involved Have feedback on this feature? [Open an issue](https://github.com/cloudposse/atmos/issues) or join the conversation in the [Atmos Slack community](https://slack.cloudposse.com). --- ## YQ Default Values Now Work Reliably in YAML Functions We've fixed an issue where YQ default values (using the `//` fallback operator) in [`!terraform.state`](/functions/yaml/terraform.state) and [`!terraform.output`](/functions/yaml/terraform.output) YAML functions were not being evaluated when components weren't provisioned or outputs didn't exist. ## What Changed YQ expressions with default values now work correctly in all scenarios: ```yaml # This now works reliably when vpc component isn't provisioned vars: vpc_id: !terraform.output vpc {{ .stack }} .vpc_id // "default-vpc" subnets: !terraform.state vpc {{ .stack }} .subnets // ["subnet-1", "subnet-2"] ``` ## The Problem Previously, when a terraform component wasn't provisioned or an output didn't exist, the YAML function wrappers would return `nil` or exit before the YQ pipeline could evaluate default expressions. This caused sporadic failures where users expected the YQ `//` operator to provide fallback values. The issue manifested as: - Stack configurations failing when referencing unprovisioned components - Inconsistent behavior depending on component state - No way to gracefully handle missing outputs with defaults ## The Solution We refactored the YAML function processing to: 1. **Properly classify errors**: Distinguish between recoverable errors (component not provisioned, output missing) and non-recoverable API errors (S3 timeouts, network failures) 2. **Evaluate YQ defaults for recoverable errors**: When a component isn't provisioned but the expression includes a default (`//`), evaluate the YQ expression against an empty map to extract the default value 3. **Propagate API errors**: Infrastructure failures like S3 timeouts correctly propagate as errors rather than silently using defaults ## Examples ### String Defaults ```yaml vars: bucket_name: !terraform.output s3 {{ .stack }} .bucket_name // "default-bucket" ``` If the `s3` component isn't provisioned, `bucket_name` will be `"default-bucket"`. ### List Defaults ```yaml vars: subnets: !terraform.state vpc {{ .stack }} .private_subnets // ["subnet-a", "subnet-b"] ``` If the `vpc` component isn't provisioned, `subnets` will be `["subnet-a", "subnet-b"]`. ### Map Defaults ```yaml vars: tags: !terraform.output 'common {{ .stack }} .tags // {"env": "dev", "team": "platform"}' ``` ### No Default (Error on Missing) ```yaml vars: # This will error if vpc component isn't provisioned (expected behavior) vpc_id: !terraform.output vpc {{ .stack }} .vpc_id ``` ## Error Handling Behavior | Scenario | Has Default (`//`) | Result | |----------|-------------------|--------| | Component not provisioned | Yes | Uses default value | | Component not provisioned | No | Returns error | | Output missing | Yes | Uses default value | | Output missing | No | Returns `nil` | | API error (S3 timeout) | Yes/No | Returns error | ## Migration No migration is required. Existing configurations that use YQ defaults will now work as expected. Configurations without defaults maintain their existing behavior. ## Related Links - [PR #1836: Fix YQ defaults for terraform.state/output YAML functions](https://github.com/cloudposse/atmos/pull/1836) - [YAML Functions Documentation](/functions/yaml) - [Terraform Output Function](/functions/yaml/terraform.output) - [Terraform State Function](/functions/yaml/terraform.state) --- ## Zero-Configuration Terminal Output: Write Once, Works Everywhere Atmos now features intelligent terminal output that adapts to any environment automatically. Developers can write code assuming a full-featured terminal, and Atmos handles the rest - capability detection, color adaptation, and secret masking happen transparently. No more capability checking, manual color detection, or masking code. Just write clean, simple output code and it works everywhere. ## The Problem with Traditional CLI Output Most CLI tools force developers to make painful choices: ```go // Traditional approach - painful! if isatty.IsTerminal(os.Stdout.Fd()) { // Using Charm Bracelet's lipgloss for styling successStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("10")) fmt.Println(successStyle.Render("Success!")) } else { fmt.Println("Success!") // Plain for pipes } // And don't forget to mask secrets! if containsSecret(output) { output = maskSecrets(output) } fmt.Println(output) ``` ### What Existing Solutions Don't Solve While [Charm Bracelet's Lip Gloss](https://github.com/charmbracelet/lipgloss) and similar libraries handle **rendering** beautifully (styled components, layouts, colors), they don't solve critical infrastructure CLI challenges: - **Secret Masking**: No automatic redaction of sensitive data across all output channels - **Centralized I/O Control**: Output scattered across stdout/stderr without unified masking - **Security-First Design**: Secrets can leak through unmasked channels or error messages - **Atmos-Specific Requirements**: Infrastructure tools handle AWS keys, API tokens, and sensitive configs that must never appear in logs This leads to: - 🚫 Duplicated capability checking throughout the codebase - 🚫 Inconsistent output behavior across commands - 🚫 **Secrets accidentally leaked to logs** (the primary driver for this work) - 🚫 Broken pipelines when output assumptions change - 🚫 Difficult testing (mocking TTY detection is painful) ## The Atmos Solution: Write Once, Works Everywhere **Atmos's I/O system complements Charm Bracelet** by adding the infrastructure-critical layer that rendering libraries don't provide: centralized I/O control with automatic secret masking. Lip Gloss handles the beautiful rendering; Atmos ensures that rendering never exposes sensitive data. With Atmos's new I/O system, developers write code once: ```go // Atmos approach - simple! ui.Success("Deployment complete!") ``` That's it. No capability checking, no color detection, no TTY handling. The system automatically: ### 🎨 Color Degradation - **TrueColor terminal** (iTerm2, Windows Terminal): Full 24-bit colors - **256-color terminal**: 256-color palette - **16-color terminal** (basic xterm): ANSI colors - **No color** (CI, `NO_COLOR=1`, pipes): Plain text ### 📏 Width Adaptation - **Wide terminal** (120+ cols): Uses full width with proper wrapping - **Narrow terminal** (80 cols): Wraps at 80 characters - **Config override**: Respects `atmos.yaml` `settings.terminal.max_width` - **Unknown width**: Sensible defaults ### 🔍 TTY Detection - **Interactive terminal**: Full styling, colors, icons, formatting - **Piped** (`atmos deploy | tee`): Plain text automatically - **Redirected** (`atmos > file`): Plain text automatically - **CI environment**: Detects CI and disables interactivity ### 🎭 Markdown Rendering ```go ui.Markdown("# Deployment Report\n\n**Status:** Success") ``` - **Color terminal**: Styled markdown with colors, bold, headers - **No-color terminal**: Plain text formatting (notty style) - **Render failure**: Gracefully falls back to plain content ### 🔒 Automatic Secret Masking ```go data.WriteJSON(config) // Contains AWS_SECRET_ACCESS_KEY ``` **Output automatically masked:** ```json { "aws_access_key_id": "AKIAIOSFODNN7EXAMPLE", "aws_secret_access_key": "***MASKED***" } ``` No manual redaction needed. The system automatically detects and masks: - AWS access keys and secrets (AKIA\*, ASIA\*) - Sensitive environment variable patterns - Common token formats - JSON/YAML quoted variants ### 🎯 Channel Separation ```go // Data to stdout (pipeable) data.WriteJSON(result) // Messages to stderr (human-readable) ui.Info("Processing components...") ui.Success("Deployment complete!") ``` Users can now safely pipe data while seeing status: ```bash atmos terraform output | jq .vpc_id # Still sees progress on stderr: # ℹ Loading configuration... # ✓ Output retrieved! ``` ### 📝 Logging vs Terminal Output **Important distinction:** This I/O system is for **terminal output** (user-facing data and messages), not **logging** (system events and debugging). - **Terminal Output** (`ui.*`, `data.*`): User-facing messages, status updates, command results - Goes to **stdout/stderr** - Formatted for humans - Respects TTY detection and color settings - Automatically masked for secrets - **Logging** (`log.*`): System events, debugging, internal state - Goes to **log files** (or `/dev/stderr` if configured) - Machine-readable format - Controlled by `--logs-level` flag - Not affected by terminal capabilities Read more in the [CLI Configuration](/cli/configuration) documentation (see `logs` section) and [Global Flags](/cli/global-flags) for `--logs-level` and `--logs-file` options. ## Real-World Examples ### Before: Manual Everything ```go func deploy(cmd *cobra.Command, args []string) error { // Capability checking isTTY := isatty.IsTerminal(os.Stderr.Fd()) // Using Charm Bracelet for styling var infoStyle, errorStyle, successStyle lipgloss.Style if isTTY { infoStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("12")) errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("9")) successStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("10")) } // Choose output format if isTTY { fmt.Fprintf(os.Stderr, "%s\n", infoStyle.Render("ℹ Starting deployment...")) } else { fmt.Fprintf(os.Stderr, "Starting deployment...\n") } // Do deployment result, err := performDeploy() if err != nil { if isTTY { fmt.Fprintf(os.Stderr, "%s\n", errorStyle.Render("✗ Deployment failed")) } else { fmt.Fprintf(os.Stderr, "Deployment failed\n") } return err } // Mask secrets before output sanitized := maskSecrets(result) // Output data json.NewEncoder(os.Stdout).Encode(sanitized) if isTTY { fmt.Fprintf(os.Stderr, "%s\n", successStyle.Render("✓ Deployment complete!")) } else { fmt.Fprintf(os.Stderr, "Deployment complete!\n") } return nil } ``` ### After: Clean and Simple ```go func deploy(cmd *cobra.Command, args []string) error { ui.Info("Starting deployment...") result, err := performDeploy() if err != nil { ui.Error("Deployment failed") return err } data.WriteJSON(result) // Secrets automatically masked ui.Success("Deployment complete!") return nil } ``` **Result: Dramatically less code, zero capability checking, automatic secret masking, perfect degradation.** ## Environment Support The system automatically respects all standard conventions: ### Environment Variables - `NO_COLOR=1` - Disables all colors - `CLICOLOR=0` - Disables colors - `FORCE_COLOR=1` - Forces color even when piped - `TERM=dumb` - Uses plain text output - `CI=true` - Detects CI environment - `ATMOS_FORCE_TTY=true` - Forces TTY mode with sane defaults (for screenshots) - `ATMOS_FORCE_COLOR=true` - Forces TrueColor even for non-TTY (for screenshots) ### CLI Flags - `--no-color` - Disables colors - `--color` - Enables color (only if TTY) - `--force-color` - Forces TrueColor even for non-TTY (for screenshots) - `--force-tty` - Forces TTY mode with sane defaults (for screenshots) - `--redirect-stderr` - Redirects UI to stdout ### Terminal Detection - TTY/PTY detection via `isatty` - Color profile via `termenv` - Width via `ioctl TIOCGWINSZ` - CI detection via standard env vars ## Testing Benefits Testing becomes trivial: ```go func TestDeployCommand(t *testing.T) { // Setup test I/O with buffers stdout, stderr, cleanup := setupTestUI(t) defer cleanup() // Run command err := deploy(cmd, args) // Verify output went to correct channels assert.Contains(t, stderr.String(), "Deployment complete") assert.Contains(t, stdout.String(), `"status":"success"`) } ``` No TTY mocking, no color detection stubbing, no complex test fixtures. ## Migration Guide ### Old Pattern (Atmos main branch before this PR) ```go // Old: Direct fmt.Fprintf with explicit stream access fmt.Fprintf(os.Stderr, "Starting...\n") fmt.Fprintf(os.Stdout, "%s\n", jsonOutput) // Or with context retrieval ioCtx, _ := io.NewContext() fmt.Fprintf(ioCtx.UI(), "Starting...\n") fmt.Fprintf(ioCtx.Data(), "%s\n", jsonOutput) ``` ### New Pattern ```go // New: Package-level functions with automatic I/O setup ui.Writeln("Starting...") data.Writeln(jsonOutput) ``` ### Available Functions **Data Output (stdout):** ```go data.Write(text) // Plain text data.Writef(fmt, ...) // Formatted data.Writeln(text) // With newline data.WriteJSON(v) // JSON data.WriteYAML(v) // YAML ``` **UI Output (stderr):** ```go ui.Write(text) // Plain (no icon/color) ui.Writef(fmt, ...) // Plain formatted ui.Writeln(text) // Plain with newline ui.Success(text) // ✓ in green ui.Error(text) // ✗ in red ui.Warning(text) // ⚠ in yellow ui.Info(text) // ℹ in cyan ui.Markdown(content) // Rendered → stdout ui.MarkdownMessage(content)// Rendered → stderr ``` ## Architecture The magic happens through clean separation of concerns: ``` Developer Code ↓ Package Functions (data.*, ui.*) ↓ Formatter (color/style selection) ↓ Terminal (capability detection) ↓ I/O Layer (masking + routing) ↓ stdout/stderr ``` Each layer handles one responsibility: - **Package functions** - Simple API for developers - **Formatter** - Returns styled strings (pure, no I/O) - **Terminal** - Detects capabilities (TTY, color, width) - **I/O Layer** - Masks secrets, routes to correct stream ## Performance Zero overhead for capability detection: - Capabilities detected once at startup - Results cached for lifetime of command - No per-call TTY checks - No per-call color detection ## What's Next This foundation enables exciting future enhancements: - **Progress bars** - Automatic for TTY, plain for pipes - **Interactive prompts** - Automatic TTY detection - **Spinner animations** - Show in TTY, silent in CI ## Try It Now Update to the latest Atmos version and start using the new I/O system: ```go // Replace manual TTY checking and Lip Gloss styling - if isatty.IsTerminal(os.Stderr.Fd()) { - style := lipgloss.NewStyle().Foreground(lipgloss.Color("10")) - fmt.Fprintf(os.Stderr, "%s\n", style.Render("✓ Done")) - } else { - fmt.Fprintf(os.Stderr, "Done\n") - } + ui.Success("Done") // Replace manual JSON output - json.NewEncoder(os.Stdout).Encode(data) + data.WriteJSON(data) // Replace manual secret masking - fmt.Println(maskSecrets(output)) + data.Writeln(output) // Automatic masking ``` ## Feedback We'd love to hear your feedback on the new I/O system! [Open an issue on GitHub](https://github.com/cloudposse/atmos/issues/new) or join the conversation in [Slack](https://slack.cloudposse.com). --- **Tags:** #feature #enhancement #contributors --- ## Atmos AI Atmos is designed for AI agents to operate your infrastructure directly. It can call MCP servers and be called as one, reason about your infrastructure on its own, and share the same skills and context with whatever AI tool your team already uses. Together, these make it easier for a team to work with the infrastructure and repositories Atmos manages — whichever AI tool each person happens to be using. - **[Agent Harness](#agent-harness)** — chat, ask, or script against your infrastructure with `atmos ai chat`/`ask`/`exec`, using either API tokens or your existing Claude Code/Codex/Copilot/Gemini subscription. - **[MCP](#mcp)** — connect Atmos to external MCP servers as a client, expose Atmos's own tools as a server, and distribute MCP server configuration from `atmos.yaml` so Atmos and every agent harness on your team use the same servers. - **[Command Analysis](#ai-powered-command-analysis)** — add `--ai` to any command for instant analysis of its output. - **[Agent Skills](#agent-skills)** — install and share domain knowledge that any Agent Skills-compatible tool can use. - **[Editor & Assistant Setup](#ai-assistants--editor-integration)** — wire all of the above into Claude Code, Cursor, Windsurf, and other coding assistants. > ⚠️ Experimental ## Quick Start ### With API Tokens **File:** `atmos.yaml` ```yaml ai: enabled: true default_provider: "anthropic" providers: anthropic: model: "claude-sonnet-4-6" api_key: !env "ANTHROPIC_API_KEY" ``` ### With Your Existing Subscription (No API Key) Use your locally installed Claude Code, OpenAI Codex, GitHub Copilot, or Gemini CLI binary. The CLI tool handles auth via its own subscription — no API key configuration needed. :::note Bring your own CLI tool Atmos assumes one of these is already installed and authenticated on your machine — it doesn't install or log you into `claude`/`codex`/`copilot`/`gemini` for you. If you don't have one yet, the commands below install and authenticate each. ::: **File:** `atmos.yaml` ```yaml ai: enabled: true default_provider: "claude-code" # or "codex-cli", "copilot-cli", "gemini-cli", or "auto" to auto-detect providers: claude-code: max_turns: 10 ``` ```shell # Claude Code brew install --cask claude-code && claude auth login # OpenAI Codex npm install -g @openai/codex && codex login # GitHub Copilot CLI npm install -g @github/copilot && copilot # then /login (or set GH_TOKEN) # Gemini CLI (authenticates on first run) npm install -g @google/gemini-cli && gemini ``` ```shell export ANTHROPIC_API_KEY="your-api-key" atmos ai chat # Interactive chat atmos ai ask "What stacks do we have?" # Single question atmos ai exec "validate stacks" --format json # CI/CD automation atmos ai sessions list # List sessions atmos ai skill list # List installed skills atmos terraform plan vpc -s prod --ai # AI analysis of any command atmos terraform plan vpc -s prod --ai --skill atmos-terraform # With domain expertise atmos terraform plan vpc -s prod --ai --skill atmos-terraform,atmos-stacks # Multiple skills ``` **AI Configuration** Configure AI providers, models, API keys, skills, tools, sessions, and instructions in your `atmos.yaml`. Configuration Reference[Read more](/cli/configuration/ai) ## Agent Harness Atmos can act as an agent harness in its own right — `atmos ai chat`, `ask`, and `exec` talk to your infrastructure directly, without you opening a separate AI tool. Point it at an **API provider** (Atmos manages the tool-execution loop itself) or a **CLI provider** that reuses a subscription you already have. **API providers** call the provider's API directly with purchased tokens. | Provider | Config Key | Auth | |---------------|---------------|------------------------| | Anthropic | `anthropic` | `ANTHROPIC_API_KEY` | | OpenAI | `openai` | `OPENAI_API_KEY` | | Google Gemini | `gemini` | `GEMINI_API_KEY` | | Grok (xAI) | `grok` | `XAI_API_KEY` | | GitHub Models | `github` | `GITHUB_TOKEN` | | AWS Bedrock | `bedrock` | AWS IAM credentials | | Azure OpenAI | `azureopenai` | `AZURE_OPENAI_API_KEY` | | Ollama | `ollama` | None (local) | **CLI providers** invoke a locally installed AI tool as a subprocess, reusing your existing subscription. The CLI tool manages its own tool execution loop, and MCP servers are passed through for tool access. | Provider | Config Key | Binary | Auth | MCP | |--------------|---------------|----------|-------------------------------|-------------------------------| | Claude Code | `claude-code` | `claude` | Claude Pro/Max subscription | Full | | OpenAI Codex | `codex-cli` | `codex` | ChatGPT Plus/Pro subscription | Full | | GitHub Copilot | `copilot-cli` | `copilot` | Copilot subscription (`/login` or `GH_TOKEN`) | Full | | Gemini CLI | `gemini-cli` | `gemini` | Google account (free tier) | Blocked for personal accounts | :::tip When to use which - **Interactive development with MCP** — `claude-code` or `codex-cli` (subscription, full MCP), or any of the API providers - **CI/CD pipelines** — API providers (env var auth, no interactive login); `github` is zero-secret in GitHub Actions (built-in `GITHUB_TOKEN` with `permissions: models: read`) - **Cost-conscious** — `gemini-cli` (free tier, prompt-only) - **Enterprise** — `bedrock` or `azureopenai` (compliance, audit trails) ::: * **[`atmos ai chat`](/cli/commands/ai/chat)** Interactive chat with session management, provider switching, and skill selection. * **[`atmos ai ask`](/cli/commands/ai/ask)** Ask a single question and get an immediate response. Ideal for scripting and CI/CD. * **[`atmos ai exec`](/cli/commands/ai/exec)** Execute Atmos and shell commands via AI prompts with structured output. * **[`atmos ai sessions`](/cli/commands/ai/sessions)** Manage chat sessions: list, clean, export, and import. **AI with API Providers** Multi-provider AI configuration with sessions, tools, and custom skills using API tokens. Browse Example[Read more](/examples/ai) **AI with Claude Code CLI** Use your Claude Pro/Max subscription with MCP server pass-through for AWS tools. No API keys needed. Browse Example[Read more](/examples/ai-claude-code) ## MCP Atmos can use external MCP servers (AWS, GCP, custom tooling) as a client, and expose its own tools as a server — both directions can be enabled at once, and the same servers work whether you're inside `atmos ai chat` or handing them to Claude Code, Cursor, or another coding assistant. **MCP Documentation** Full setup for both directions: adding servers, installing into your AI client, smart routing vs. CLI pass-through, and the complete command reference. MCP Documentation[Read more](/mcp) **Try the MCP Example** Explore a complete example with pre-configured AWS MCP servers for cost analysis, security, IAM, and documentation. Browse Example[Read more](/examples/mcp) ## AI-Powered Command Analysis Add `--ai` to any Atmos command for instant AI-powered output analysis. Pair with `--skill` for domain-specific expertise — multiple skills can be combined with commas or repeated flags. ```bash # Basic AI analysis atmos terraform plan vpc -s prod --ai # Single skill for domain expertise atmos terraform plan vpc -s prod --ai --skill atmos-terraform # Multiple skills (comma-separated) atmos terraform plan vpc -s prod --ai --skill atmos-terraform,atmos-stacks # Multiple skills (repeated flag) atmos terraform plan vpc -s prod --ai --skill atmos-terraform --skill atmos-stacks # Via environment variables ATMOS_AI=true ATMOS_SKILL=atmos-terraform,atmos-stacks atmos terraform plan vpc -s prod ``` **AI-Powered Command Analysis** Add `--ai` to any Atmos command for instant output analysis. Combine with `--skill` for domain-specific expertise. Global Flags Reference[Read more](/cli/global-flags) ## Agent Skills Atmos ships agent skills -- browsable in the [Agent Skills Directory](/ai/skills) -- that give AI coding assistants deep knowledge of Atmos conventions. Skills follow the [Agent Skills](https://agentskills.io) open standard and work across Claude Code, Gemini CLI, OpenAI Codex, Cursor, Windsurf, GitHub Copilot, and more. - **[`atmos ai skill`](/cli/commands/ai/skill)** Install, list, and uninstall community AI skills from GitHub. ### [Skills Configuration](/cli/configuration/ai/skills) Configure skills in `atmos.yaml`. ## AI Assistants & Editor Integration **Configure AI Assistants** Set up Claude Code, Cursor, Windsurf, GitHub Copilot, Gemini CLI, OpenAI Codex, and other AI coding assistants to use Atmos agent skills. Setup Guide[Read more](/projects/setup-editor/ai-assistants) **Claude Code Integration** Use Claude Code with the Atmos MCP server and create specialized `atmos-expert` subagents for deep infrastructure expertise. Claude Code Guide[Read more](/ai/claude-code-integration) ## Commands - **[`atmos --ai`](/cli/global-flags)** Add `--ai` flag to any command for AI-powered output analysis. Use `--skill` flag for domain-specific expertise (supports multiple skills via commas or repeated flag). See [MCP Documentation](/mcp#managing-servers) for the full `atmos mcp` command reference. ## Troubleshooting Having issues? See the [Troubleshooting Guide](/ai/troubleshooting) for solutions to common problems with providers, tools, sessions, and connectivity. --- ## Claude Code Integration Use Claude Code with the Atmos MCP server to access your infrastructure directly from your development environment. Create specialized `atmos-expert` subagents that provide deep infrastructure expertise while you code. > ⚠️ Experimental ## Overview Claude Code subagents are specialized AI assistants that enhance your development workflow with domain-specific expertise. When you create an `atmos-expert` subagent, it can use the Atmos MCP server to access your infrastructure directly from your IDE. ### Architecture ```text ┌─────────────────────────────────────────────────┐ │ Claude Code │ │ │ │ You: @atmos-expert "List my stacks" │ │ ↓ │ │ ┌──────────────────┐ │ │ │ atmos-expert │ Specialized Context │ │ │ Subagent │ Isolated Conversation │ │ └────────┬─────────┘ │ │ │ │ └───────────┼─────────────────────────────────────┘ │ MCP Protocol ↓ ┌────────────────────┐ │ Atmos MCP Server │ Universal Interface │ • describe_stacks │ │ • list_components │ │ • validate_stack │ └────────┬───────────┘ │ ↓ ┌────────────────────┐ │ Atmos CLI │ Your Infrastructure └────────────────────┘ ``` ## When to Use Subagents vs Built-in AI - **Write Terraform code** Claude Code Subagent (in IDE) - **Edit component files** Claude Code Subagent (in IDE) - **Get help while coding** Claude Code Subagent (in IDE) - **Analyze infrastructure** Built-in Atmos AI ( `atmos ai chat` ) - **Use Ollama/GPT/Gemini** Built-in Atmos AI (7 providers) - **Multi-day project discussion** Built-in Atmos AI (persistent sessions) - **Quick one-off question** Built-in Atmos AI ( `atmos ai ask` ) - **Team collaboration** Built-in Atmos AI (shareable sessions) ## Setup Guide ### Step 1: Configure Atmos MCP Server Create or edit `~/.config/Claude/claude_desktop_config.json`: **File:** `~/.config/Claude/claude_desktop_config.json` ```json { "mcpServers": { "atmos": { "command": "/usr/local/bin/atmos", "args": ["mcp", "start"], "env": { "ATMOS_BASE_PATH": "/path/to/your/infrastructure" } } } } ``` :::tip macOS with Homebrew If you installed Atmos via Homebrew, use `/opt/homebrew/bin/atmos` as the command path. ::: **Restart Claude Desktop** after editing the config. ### Step 2: Create the Subagent Create `~/.claude/agents/atmos-expert.md`: **File:** `~/.claude/agents/atmos-expert.md` ```markdown --- name: atmos-expert description: Expert in Atmos infrastructure orchestration, stack management, and Terraform/Helmfile best practices. tools: - mcp__atmos__describe_stacks - mcp__atmos__describe_component - mcp__atmos__list_stacks - mcp__atmos__list_components - mcp__atmos__validate_component - mcp__atmos__validate_stack - Read - Glob - Grep model: inherit --- You are an expert in Cloud Posse's Atmos infrastructure orchestration framework. ## Your Expertise - **Stack Architecture**: Atmos stack hierarchies, inheritance, and composition - **Component Management**: Terraform/Helmfile component design and best practices - **Configuration Patterns**: YAML configuration, templating, and variable management - **Troubleshooting**: Diagnosing and resolving Atmos configuration issues ## Available Atmos Tools 1. **describe_stacks**: Get detailed stack configurations with inheritance resolved 2. **describe_component**: Get component configuration for specific stacks 3. **list_stacks**: List all available stacks 4. **list_components**: List all available components 5. **validate_component**: Validate component configuration 6. **validate_stack**: Validate entire stack configuration ## How to Help Users 1. **Analyze before answering**: Use MCP tools to understand the actual configuration 2. **Provide context**: Explain why a configuration works a certain way 3. **Suggest improvements**: Recommend Cloud Posse best practices 4. **Show examples**: Provide concrete YAML examples when helpful 5. **Troubleshoot systematically**: Use validation tools to identify issues ``` :::info Project-Specific Subagents You can also create `.claude/agents/atmos-expert.md` in your project root for project-specific behavior. Project-level subagents override user-level subagents when names conflict. ::: ### Step 3: Use the Subagent In Claude Code, invoke the subagent with `@`: ``` @atmos-expert List all available stacks in my infrastructure ``` The subagent will use the MCP server to query your infrastructure and provide expert guidance. ## Example: Stack Troubleshooting ``` You: @atmos-expert Why is prod-use1-eks failing validation? atmos-expert: [Uses validate_stack and describe_component] I found 2 issues in prod-use1-eks: 1. Missing required variable `cluster_version` Location: stacks/catalog/eks/defaults.yaml:15 2. Invalid CIDR block format Location: stacks/catalog/eks/prod-use1.yaml:23 Current: "10.0.0/16" Should be: "10.0.0.0/16" [Provides fixes with line numbers] ``` ## Troubleshooting ### Subagent Not Using MCP Tools **Check MCP server is running**: ```shell # Test MCP server directly atmos mcp start # Should show: Starting Atmos MCP server... ``` **Verify tool names** -- MCP tools require the prefix `mcp____`: - Correct: `mcp__atmos__describe_stacks` - Wrong: `describe_stacks` (missing prefix) **Restart Claude Desktop** after config changes. ### Subagent Not Being Invoked 1. Check that the description clearly indicates when to use the agent 2. Use explicit invocation: `@atmos-expert your question` 3. Verify file location: `~/.claude/agents/atmos-expert.md` (user-level) or `.claude/agents/atmos-expert.md` (project-level) ### MCP Server Not Found ```shell # Check atmos is in PATH which atmos # Should output: /usr/local/bin/atmos or /opt/homebrew/bin/atmos ``` Update Claude Desktop config to use the correct path. ## Comparison with Built-in Atmos AI - **Access Method** **Claude Code Subagent:** `@atmos-expert` in IDE. **Built-in Atmos AI:** `atmos ai chat` in terminal. - **Tool Protocol** **Claude Code Subagent:** MCP (slight overhead). **Built-in Atmos AI:** Direct (faster). - **Context** **Claude Code Subagent:** Independent per subagent. **Built-in Atmos AI:** Persistent SQLite sessions. - **AI Provider** **Claude Code Subagent:** Claude (Sonnet/Opus/Haiku). **Built-in Atmos AI:** 7 providers (Claude, GPT, Gemini, Grok, Ollama, Bedrock, Azure). - **IDE Integration** **Claude Code Subagent:** Full code access. **Built-in Atmos AI:** CLI only. - **Code Editing** **Claude Code Subagent:** Can modify files. **Built-in Atmos AI:** Read-only. - **Session Persistence** **Claude Code Subagent:** Per-conversation. **Built-in Atmos AI:** Across CLI invocations. - **Best For** **Claude Code Subagent:** Development workflow. **Built-in Atmos AI:** Operations workflow. ## Related Documentation - [MCP Server Setup](/ai/mcp-server) - Configure the Atmos MCP server - [Built-in AI Chat](/cli/commands/ai/chat) - Use the integrated AI assistant - [Tool Execution](/cli/configuration/ai/tools) - How AI uses Atmos tools - [Configuration](/cli/configuration/ai) - AI configuration options ## External Resources - [Claude Code Subagents Documentation](https://docs.claude.com/en/docs/claude-code/sub-agents) - [Model Context Protocol](https://modelcontextprotocol.io/) - [Awesome Claude Code Subagents](https://github.com/VoltAgent/awesome-claude-code-subagents) --- ## MCP Server Integration The Atmos MCP Server exposes Atmos AI tools through the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), an open standard that lets any compatible AI client -- Claude Desktop, Claude Code, VS Code, Cursor, and [many others](https://modelcontextprotocol.io/clients) -- connect to your infrastructure tools without custom integrations. > ⚠️ Experimental **See also:** [MCP Command Reference](/cli/commands/mcp/start) for flag details and transport modes | [MCP Configuration](/cli/configuration/mcp) for tool and permission settings in `atmos.yaml` :::tip Safe by Default The default **stdio transport** runs as a local subprocess with no network exposure -- the same security model as running `atmos` from your terminal. No ports are opened and no remote connections are accepted. If you use [HTTP transport](#http-transport) for remote access, see [Security Considerations](#security-considerations) for recommended safeguards. ::: ## Quick Start This walkthrough gets you from zero to querying stacks in Claude Desktop. **Prerequisites:** Atmos v1.63.0+ and [Claude Desktop](https://claude.ai/download) v0.7.0+. ### 1. Enable MCP and AI in atmos.yaml **File:** `atmos.yaml` ```yaml mcp: enabled: true # required — MCP is off by default ai: enabled: true tools: enabled: true require_confirmation: false # optional, smoother experience ``` :::info The MCP server is **disabled by default**. You must explicitly set `mcp.enabled: true` in addition to enabling AI. This ensures that enabling AI chat features does not inadvertently expose an MCP endpoint. ::: ### 2. Configure Claude Desktop ```shell # Automated: writes mcp.enabled + the client entry for you. atmos mcp add self --install ``` `atmos mcp add self --install` does steps 1 and 2 for supported clients (Claude Code, Cursor, VS Code, Codex, Gemini) in one command — see [`atmos mcp add`](/cli/commands/mcp/add). Claude Desktop isn't one of the auto-installable clients yet, so configure it manually below. Create or edit the config file at `~/.config/claude/claude_desktop_config.json` (macOS/Linux) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows): **File:** `claude_desktop_config.json` ```json { "mcpServers": { "atmos": { "command": "atmos", "args": ["mcp", "start"], "cwd": "/path/to/your/atmos/project" } } } ``` :::tip Working Directory Set `cwd` to the directory containing your `atmos.yaml`. On Windows, use `atmos.exe` as the command and double backslashes for paths (e.g., `C:\\Users\\You\\project`). ::: ### 3. Restart and Verify Completely quit Claude Desktop and reopen it. After a few seconds, look for the MCP indicator icon in the bottom-right corner. Click it to confirm "atmos" is listed with a green dot. Then try asking: ``` List all my Atmos stacks ``` Claude will call `atmos_list_stacks` and return your actual stack names. If something goes wrong, check the [Troubleshooting](#troubleshooting) section. ## Available Tools When connected, the MCP server exposes all enabled Atmos AI tools. For full details on each tool, its parameters, and the permission system, see [AI Tools Configuration](/cli/configuration/ai/tools). - **`atmos_describe_component`** Get detailed component configuration for a stack. - **`atmos_list_stacks`** List and filter stacks. - **`atmos_validate_stacks`** Validate stack configurations. - **`atmos_validate_schema`** Validate atmos.yaml (and other configured YAML files) against their JSON Schemas. - **`describe_affected`** Show components affected by git changes. - **`read_component_file`** Read a component source file. - **`read_stack_file`** Read a stack configuration file. - **`write_component_file`** Modify a component file (requires permission). - **`write_stack_file`** Modify a stack file (requires permission). All tools respect your configured [permission settings](/cli/configuration/ai/tools). ## Client Setup Beyond the [Quick Start](#quick-start) with Claude Desktop, here is how to configure other popular MCP clients. Every client uses the same underlying command (`atmos mcp start`); only the config format differs. ### Desktop Apps and IDEs ### VS Code Create `.vscode/mcp.json` in your project: **File:** `.vscode/mcp.json` ```json { "servers": { "atmos": { "type": "stdio", "command": "atmos", "args": ["mcp", "start"], "cwd": "${workspaceFolder}" } } } ``` Alternatively, press `Cmd+Shift+P` / `Ctrl+Shift+P` and search "MCP: Add Server". :::tip Enable `chat.mcp.discovery.enabled` in VS Code settings to auto-discover MCP servers from your Claude Desktop config. ::: ### Cursor Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project): **File:** `~/.cursor/mcp.json` ```json { "mcpServers": { "atmos": { "command": "atmos", "args": ["mcp", "start"], "cwd": "/path/to/your/atmos/project" } } } ``` ### Windsurf **File:** `~/.windsurf/mcp.json` ```json { "mcpServers": { "atmos": { "command": "atmos", "args": ["mcp", "start"], "cwd": "/path/to/your/atmos/project" } } } ``` ### Cline Click the "MCP Servers" icon in Cline's navigation, then "Configure MCP Servers" and add: **File:** `cline_mcp_settings.json` ```json { "mcpServers": { "atmos": { "command": "atmos", "args": ["mcp", "start"], "cwd": "/path/to/your/atmos/project", "disabled": false } } } ``` ### Continue.dev Create `.continue/mcpServers/atmos.yaml` in your workspace: **File:** `.continue/mcpServers/atmos.yaml` ```yaml name: atmos command: atmos args: - mcp - start cwd: /path/to/your/atmos/project ``` ### CLI Tools ### Claude Code ```shell # stdio transport claude mcp add --transport stdio atmos -- atmos mcp start # HTTP transport claude mcp add --transport http atmos http://localhost:8080 # With project scope and env vars claude mcp add --scope project --transport stdio atmos \ --env ATMOS_BASE_PATH=/path/to/project \ -- atmos mcp start ``` Or add a `.mcp.json` file to your project root: **File:** `.mcp.json` ```json { "mcpServers": { "atmos": { "type": "stdio", "command": "atmos", "args": ["mcp", "start"] } } } ``` Manage servers with `claude mcp list`, `claude mcp get atmos`, and `claude mcp remove atmos`. ### Gemini CLI ```shell # stdio transport gemini mcp add atmos atmos mcp start # HTTP transport gemini mcp add --transport http atmos http://localhost:8080 ``` Or configure manually in `~/.gemini/settings.json`: **File:** `~/.gemini/settings.json` ```json { "mcpServers": { "atmos": { "command": "atmos", "args": ["mcp", "start"], "timeout": 30000 } } } ``` ### OpenAI Codex **File:** `~/.codex/config.json` ```json { "mcpServers": { "atmos": { "command": "atmos", "args": ["mcp", "start"] } } } ``` ### Grok CLI ```shell # Interactive: start grok, then run /mcp add atmos "atmos" "mcp" "start" # Or via CLI grok mcp add atmos --transport stdio --command "atmos" --args "mcp,start" ``` ### Client Comparison - ****Claude Desktop** — `~/.config/claude/claude_desktop_config.json`** General infrastructure queries. - ****VS Code Copilot** — `.vscode/mcp.json`** Coding with infrastructure context. - ****Cursor** — `~/.cursor/mcp.json`** AI-first development. - ****Cline** — GUI + `cline_mcp_settings.json`** Autonomous coding. - ****Continue.dev** — `.continue/mcpServers/atmos.yaml`** Multi-model workflows. - ****Windsurf** — `~/.windsurf/mcp.json`** AI-powered development. - ****Claude Code** — `.mcp.json` or CLI** Terminal-based workflows. - ****Gemini CLI** — `~/.gemini/settings.json`** Google Cloud users. - ****Codex CLI** — `~/.codex/config.json`** OpenAI ecosystem. - ****Grok CLI** — `.grok/settings.json`** xAI users. ## Example Queries Once connected, ask questions in natural language. Claude automatically picks the right tool. ``` List all my Atmos stacks Show me the vpc component configuration in prod-us-east-1 What stacks use the RDS component? Validate all stack configurations Generate a Terraform plan for vpc in staging Read the stack file for prod-us-east-1 What are the differences between dev and prod for vpc? ``` ## Security Considerations ### stdio Transport (Default) Runs as a local subprocess under your user account with no network exposure. This is the recommended transport for desktop apps and local development -- no additional security configuration is needed. ### HTTP Transport :::warning HTTP Transport Security HTTP transport has no built-in authentication or encryption. Only use it when you need remote or multi-client access, and always add network-level security. ::: For any deployment beyond `localhost`: - **Network isolation** Use firewall rules, private networks, or VPN. Bind to `localhost` for single-user scenarios. - **Reverse proxy** Put nginx or Caddy in front with TLS and authentication. See the nginx example below. - **SSH tunneling** Forward the port over SSH for secure remote access: `ssh -L 8080:localhost:8080 user@server` . - **Tool permissions** Restrict tools in `atmos.yaml` using `ai.tools.allowed` and `ai.tools.blocked` . Disable write operations if not needed. - **Monitoring** Poll `/health` and log all tool executions. ## Advanced Deployments ### Docker **File:** `Dockerfile` ```dockerfile FROM cloudposse/atmos:latest COPY atmos.yaml /atmos/atmos.yaml COPY stacks/ /atmos/stacks/ COPY components/ /atmos/components/ WORKDIR /atmos EXPOSE 8080 # Binding to 0.0.0.0 inside a container is safe; Docker port mapping controls external access. CMD ["atmos", "mcp", "start", "--transport", "http", "--host", "0.0.0.0", "--port", "8080"] ``` ```shell docker build -t atmos-mcp . docker run -p 8080:8080 -e ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY}" atmos-mcp ``` ## Troubleshooting ### `stdio` Connection Issues **Server not appearing in Claude Desktop:** 1. Verify the config file location and JSON syntax. Use the paths listed in [Quick Start](#2-configure-claude-desktop). 2. Fully quit Claude Desktop (Cmd+Q / Alt+F4), wait a few seconds, and reopen. 3. Confirm `atmos` is in your PATH: ```shell which atmos atmos mcp start # should start without errors; Ctrl+C to exit ``` 4. Check Claude Desktop logs for errors mentioning "atmos": - macOS: `~/Library/Logs/Claude/mcp*.log` - Windows: `%APPDATA%\Claude\logs\mcp*.log` - Linux: `~/.config/Claude/logs/mcp*.log` **Tools not appearing in conversations:** Tools register for MCP regardless of `ai.tools.enabled` (that setting only gates `atmos ai chat`/`ask`/`exec`'s own tool-use loop) — `mcp.enabled: true` is the MCP server's own opt-in. Make sure you have not restricted the set with `ai.tools.allowed`, and that the tool isn't in `ai.tools.blocked`. If tools still do not appear, try asking Claude explicitly: "Use the atmos\_list\_stacks tool to show my stacks." **"Command not found" errors:** Use the full path to `atmos` in your config. Run `which atmos` to find it, then set `"command": "/usr/local/bin/atmos"` (or wherever it is installed). ### HTTP Connection Issues ```shell # Check if the server is running curl http://localhost:8080/health # Check if the port is in use lsof -i :8080 # Try a different port atmos mcp start --transport http --port 3000 ``` If a firewall is blocking connections, allow the port (`sudo ufw allow 8080/tcp` on Linux, or add `atmos` to the macOS application firewall). ### Common Configuration Errors **"MCP server is not enabled"** -- Set `mcp.enabled: true` in `atmos.yaml`. This is the MCP server's own opt-in; `ai.enabled`/`ai.tools.enabled` are unrelated to it. **Tools not executing** -- Check `ai.tools.allowed` and `ai.tools.blocked` in your AI tool config. Remove overly restrictive entries. **Slow responses** -- Increase `ai.timeout_seconds` (default varies by provider). For HTTP transport, check network latency. Monitor resource usage via `/health`. ## Related Documentation - [MCP Command Reference](/cli/commands/mcp/start) -- Command flags and transport modes - [MCP Configuration](/cli/configuration/mcp) -- Configure tools and permissions - [AI Tools Configuration](/cli/configuration/ai/tools) -- Tool details and permission system - [MCP Specification](https://modelcontextprotocol.io) -- Official protocol documentation - [MCP Go SDK](https://github.com/modelcontextprotocol/go-sdk) -- SDK used by the Atmos MCP Server - [MCP Clients](https://modelcontextprotocol.io/clients) -- 100+ compatible applications ### Client Setup Guides - [Claude Desktop](https://claude.ai/desktop) - [Claude Code MCP Guide](https://docs.claude.com/en/docs/claude-code/mcp) - [VS Code MCP Servers](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) - [Cursor MCP](https://docs.cursor.com/context/model-context-protocol) - [Cline MCP](https://docs.cline.bot/mcp/configuring-mcp-servers) - [Continue.dev Reference](https://docs.continue.dev/reference) - [Gemini CLI MCP](https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html) - [OpenAI Codex CLI](https://developers.openai.com/codex/cli/) ## Feedback and Support - [GitHub Issues](https://github.com/cloudposse/atmos/issues) -- Report bugs or request features - [MCP GitHub](https://github.com/modelcontextprotocol) -- MCP protocol issues --- ## AI Troubleshooting Solutions for common Atmos AI issues, organized by symptom. > ⚠️ Experimental ## Quick Checks Before diving into specific errors, verify these basics: ```yaml ai: enabled: true default_provider: "anthropic" ``` ```shell [ -n "$ANTHROPIC_API_KEY" ] && echo "Set" || echo "NOT set" ``` ```shell atmos ai ask "test" ``` ## Common Errors ### "AI features are not enabled" Add `ai.enabled: true` to your `atmos.yaml`. ### "API key not found" Export the key for your provider: ### Anthropic ```bash export ANTHROPIC_API_KEY="sk-ant-..." # From console.anthropic.com ``` ### OpenAI ```bash export OPENAI_API_KEY="sk-..." # From platform.openai.com ``` ### Gemini ```bash export GEMINI_API_KEY="..." # From aistudio.google.com ``` ### Grok ```bash export XAI_API_KEY="xai-..." # From x.ai/api ``` ### Azure OpenAI ```bash export AZURE_OPENAI_API_KEY="..." # From Azure Portal > OpenAI resource > Keys ``` ### AWS Bedrock No API key needed. Uses AWS SDK credentials (IAM roles, profiles, env vars). ```bash aws sts get-caller-identity # Verify AWS credentials ``` ### Ollama No API key needed. Just make sure the server is running: ```bash curl http://localhost:11434/api/version ``` ### "Failed to create AI client" Usually means an invalid or revoked API key, or insufficient credits. Test the key directly: ```shell curl https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{"model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello"}]}' ``` ### "Unsupported AI provider" Valid provider names: - **API providers:** `anthropic`, `openai`, `gemini`, `grok`, `ollama`, `bedrock`, `azureopenai` - **CLI providers:** `claude-code`, `codex-cli`, `gemini-cli` ### Rate Limiting (429 Errors) Long conversations send full history with each request. Reduce it: ```yaml ai: max_history_messages: 20 ``` ## Provider Issues ### Ollama **"Connection refused"** — Start the server and verify your config: ```shell ollama serve curl http://localhost:11434/api/version ``` ```yaml providers: ollama: base_url: "http://localhost:11434/v1" ``` **"Model not found"** — Pull the model and use the exact name from `ollama list`: ```shell ollama list ollama pull llama4 ``` **Slow or out of memory** — Use a smaller model (e.g. `llama3.1:8b` needs ~8GB RAM) or reduce `max_tokens`. ### AWS Bedrock **"Authentication failed"** — Verify AWS credentials: ```shell aws sts get-caller-identity ``` **"Access denied"** — Your IAM role needs `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream` permissions. Also enable model access in AWS Console under Bedrock > Model access. **"Model not found"** — Use the full Bedrock model ID and check your region: ```yaml providers: bedrock: model: "anthropic.claude-sonnet-4-6" base_url: "us-east-1" ``` ### Azure OpenAI **"Authentication failed"** — Get the key from Azure Portal > OpenAI resource > Keys and Endpoint. **"Resource not found"** — Copy the exact endpoint URL from Azure Portal: ```yaml providers: azureopenai: base_url: "https://your-resource-name.openai.azure.com" ``` **"Deployment not found"** — Use your deployment name (not the model name): ```yaml providers: azureopenai: model: "gpt-4o" # Your deployment name api_version: "2025-04-01-preview" ``` List deployments with `az cognitiveservices account deployment list --name your-resource --resource-group your-rg`. ### Other Providers - ****Anthropic** — "Authentication error"** Key must start with `sk-ant-` . - ****OpenAI** — "Insufficient quota"** Add billing info at platform.openai.com. - ****Grok** — "Connection failed"** Set `base_url: "https://api.x.ai/v1"` . See [AI Providers](/cli/configuration/ai/providers) for full provider documentation. ## CLI Providers ### "CLI provider binary not found" The CLI tool isn't installed or not on PATH: ### Claude Code ```bash brew install --cask claude-code claude auth login ``` ### OpenAI Codex ```bash npm install -g @openai/codex codex login ``` ### Gemini CLI ```bash npm install -g @google/gemini-cli gemini # Authenticates on first run ``` Or set an explicit path: ```yaml providers: claude-code: binary: /usr/local/bin/claude ``` ### MCP Servers Not Working with Codex CLI Codex CLI MCP servers don't inherit the parent process environment. If `atmos auth exec` fails with "identity not found", ensure `ATMOS_PROFILE` is exported: ```bash export ATMOS_PROFILE=managers # Or your profile name atmos ai ask "What did we spend on EC2?" ``` Atmos automatically injects `ATMOS_*` env vars into each MCP server's config, but the env var must be set in the shell. ### Gemini CLI MCP Blocked Gemini CLI blocks MCP for all personal Google accounts (`oauth-personal` auth) regardless of subscription tier. This is a server-side Google restriction. **Workaround:** Use `claude-code` or `codex-cli` for MCP workflows. Gemini CLI works for prompt-only queries. ## Sessions ### Sessions Not Persisting Enable sessions and use named sessions: ```yaml ai: sessions: enabled: true path: ".atmos/sessions" ``` ```shell atmos ai chat --session my-project ``` ### Database Errors If you get "Failed to initialize session storage", back up and recreate: ```shell cp .atmos/sessions/sessions.db .atmos/sessions/sessions.db.backup rm .atmos/sessions/sessions.db atmos ai chat # Creates a new database ``` ### Conversation Memory Not Working - Use `--session name` — anonymous sessions don't persist - Add `sessions.enabled: true` to atmos.yaml - Check directory permissions: `chmod 755 .atmos/sessions` All seven providers support conversation memory. ### Auto-Compact Not Working Verify the required settings are all present: ```yaml ai: max_history_messages: 50 # Required — threshold is calculated from this sessions: enabled: true auto_compact: enabled: true trigger_threshold: 0.75 # Triggers at 38 messages (50 x 0.75) ``` If `use_ai_summary: true` and no summaries appear, check that the AI provider is configured and the API key is valid. Test with `use_ai_summary: false` first. **Triggers too often?** Increase `trigger_threshold` or `max_history_messages`. **Too expensive?** Set `use_ai_summary: false` to use simple concatenation instead of AI summaries. See [Auto-Compact](/cli/configuration/ai/sessions#auto-compact-configuration) for details. ## MCP Server ### "MCP server is not enabled" The MCP server is disabled by default and must be explicitly enabled: ```yaml mcp: enabled: true ai: enabled: true tools: enabled: true ``` ### Not Starting Test the MCP server directly: ```shell atmos mcp start # Should output: Starting Atmos MCP server on stdio... ``` For Claude Desktop, use the full path to atmos: ```json { "mcpServers": { "atmos": { "command": "/usr/local/bin/atmos", "args": ["mcp", "start"] } } } ``` ### Tools Not Available Restart the MCP client and check for errors with debug logging: ```shell ATMOS_LOGS_LEVEL=Debug atmos mcp start ``` See [MCP Server](/ai/mcp-server) for the complete setup guide. ## LSP Integration ### Server Not Found Install and verify: ```shell npm install -g yaml-language-server && yaml-language-server --version brew install terraform-ls && terraform-ls version ``` If installed but not found, use the full path: ```yaml lsp: servers: yaml-ls: command: "/usr/local/bin/yaml-language-server" ``` ### Not Validating Files Verify LSP is enabled and the server config matches your file types: ```yaml lsp: enabled: true servers: yaml-ls: filetypes: ["yaml", "yml"] ``` See [LSP Client](/lsp/lsp-client) for detailed configuration. ## Skills ### Skill Not Appearing Press `Ctrl+A` in chat to open the skill selector. Skills need all three required fields: ```yaml ai: skills: my-skill: display_name: "My Skill" # Required description: "..." # Required system_prompt: "..." # Required ``` `Ctrl+A` only works in the main chat view — press `Esc` first if you're in another panel. ### Skill Gives Generic Responses The `system_prompt` needs more detail. Include a role definition, focus areas, and tool usage instructions: ```yaml system_prompt: | You are a specialized Atmos stacks analyst. FOCUS: Stack configuration, dependency analysis. APPROACH: Use atmos_describe_component first, then recommend. Always use tools to gather data before answering. ``` ### Tool Access Denied The tool isn't in the skill's `allowed_tools`. Add it, or switch to the **General** skill (`Ctrl+A`) which has access to all tools. Tool names use the `atmos_` prefix: `atmos_describe_component`, `atmos_list_stacks`, `atmos_validate_stacks`, etc. See [AI Tools](/cli/configuration/ai/tools) for the full list. ## Claude Code Subagents ### Subagent Not Invoked Check the file exists and has proper frontmatter: ```shell ls -la .claude/agents/atmos-expert.md ``` ```markdown --- name: atmos-expert description: Expert in Atmos infrastructure... tools: - mcp__atmos__describe_stacks model: inherit --- ``` Invoke with `@atmos-expert your question`. ### Can't Access MCP Tools Tool names need the `mcp__atmos__` prefix. The MCP server must be running and the server name must match: ```yaml tools: - mcp__atmos__describe_stacks # Correct - describe_stacks # Wrong ``` Restart Claude Desktop after changing subagent or MCP configuration. See [Claude Code Integration](/ai/claude-code-integration) for the complete guide. ## Project Instructions ### AI Not Using Instructions Verify instructions are enabled and the file exists: ```yaml ai: instructions: enabled: true file: "ATMOS.md" ``` ```shell ls -la ATMOS.md ``` If the file doesn't exist, instructions are silently skipped. Create it manually. ### Large File Performance Keep `ATMOS.md` under 10KB. Remove outdated content to reduce tokens per request. ## Debugging When nothing else works: ```shell atmos ai ask "test" --logs-level=Debug ``` ```shell atmos describe config | grep -A 10 "ai" ``` **3. Test network connectivity:** ### Anthropic ```bash curl -sI https://api.anthropic.com | head -1 ``` ### OpenAI ```bash curl -sI https://api.openai.com | head -1 ``` ### Ollama ```bash curl http://localhost:11434/api/version ``` ### Bedrock ```bash aws bedrock list-foundation-models --region us-east-1 --max-results 1 ``` **4. Try a minimal config** to rule out config problems: ```yaml ai: enabled: true default_provider: "anthropic" ``` ## Performance **Slow responses** — Try a faster model (`claude-haiku-4-5-20251001`, `gpt-5-mini`, `gemini-2.5-flash`), go local with Ollama, reduce `max_tokens`, or limit history with `max_history_messages: 20`. **Timeout errors** — Break complex questions into smaller parts. ## Getting Help 1. Review [AI Configuration](/cli/configuration/ai) 2. Search [GitHub Issues](https://github.com/cloudposse/atmos/issues) 3. Open a new issue with: Atmos version, provider/model, error message, and reproduction steps --- ## Best Practices > Physics is the law, everything else is a recommendation. > Anyone can break laws created by people, but I have yet to see anyone break the laws of physics. > — **Elon Musk** Learn how to best leverage Stacks and Components together with Atmos. --- ## Component Best Practices Here are some essential best practices to follow when designing architectures using infrastructure as code (IaC), focusing on optimizing component design, reusability, and lifecycle management. These guidelines are designed to help developers and operators build efficient, scalable, and reliable systems, ensuring a smooth and effective infrastructure management process. Also, be sure to review the [Terraform Best Practices](/best-practices/terraform) for additional guidance on using Terraform with Atmos. > Physics is the law, everything else is a recommendation. > Anyone can break laws created by people, but I have yet to see anyone break the laws of physics. > — **Elon Musk** ## Keep Your Components Small to Reduce the Blast Radius of Changes Focus on creating single purpose components that small, reusable components that adhere to the UNIX philosophy by doing one thing well. This strategy leads to simpler updates, more straightforward troubleshooting, quicker plan/apply cycles, and a clearer separation of responsibilities. Best of all, your state remains small and complexity remains manageable. Anti-patterns to avoid include: - Combining VPCs with databases in the same component - Defining every dependency needed by an application in a single component (provided there's no shared lifecycle) ## Split Components By Lifecycle To keep your component small, consider breaking them apart by their Software Development Lifecycle (SDLC). Things that always change together, go together. Things that seldom change together, should be managed separately. Keep the coupling loose, and use remote state for cohesion. For instance, a VPC, which is rarely destroyed, should be managed separately from more dynamic resources like clusters or databases that may frequently scale or undergo updates. ## Make Them Opinionated, But Not Too Opinionated Ensure components are generalized to prevent the proliferation of similar components, thereby promoting easier testing, reuse, and maintenance. :::important Don't Treat Components like Child Modules Don't force users to use generic components if that will radically complicate the configuration. The goal is to make 80% of your infrastructure highly reusable with generic single purpose components. The remaining 20% might need to be specialized for your use case, and that's okay. ::: ## Avoid Single Resource Components If you find yourself writing a component that is so small, it manages only a single resource e.g. (an IAM Policy), consider if it should be part of a larger component. :::tip Stack Configurations are Not a Replacement for Terraform The biggest risk for newcomers to Atmos is to over architect components into extremely DRY single-purpose components. Stack configurations in YAML should not just be a proxy for terraform resources. Use terraform for its strengths, compliment it with YAML when it makes sense for very straight forward configuration. ::: ## Use Parameterization, But Avoid Over-Parameterization Good parameterization ensures components are reusable, but components become difficult to test and document with too many parameters. Often time, child modules might accept more parameters than the root module. You can always add more parameters to the root module as needed, but it's hard to remove them once they are there. ## Avoid Creating Factories Inside of Components [Factories are common software design patterns](https://en.wikipedia.org/wiki/Factory_\(object-oriented_programming\)) that allow you to create multiple instances of a component. To minimize the blast radius of changes and maintain fast plan/apply cycles, do not embed factories within components that provision lists of resources. Examples of anti-patterns include: - Reading a configuration file inside of Terraform to create multiple Buckets - Using a `for_each` loop to create multiple DNS records from a variable input (you may hit rate limits when you zones get large enough; it's happened to us) Instead, leverage [Stack configurations to serve as factories](/learn/stacks) for provisioning multiple component instances. This approach keeps the state isolated and scales efficiently with the increasing number of component instances. Please note, it's perfectly fine to use `for_each` loops sometimes to provision groups of resources, just use them with moderation and be aware of the potential downsides, such as creating massive states with a wide blast radius. For example, maybe you can safely manage a collection of resources this way. :::note Do as we say, not as we do It is with humility that we state this best practice. Even many of our own Cloud Posse components, do not follow this because they were written before we realized the overwhelming benefits of this approach. ::: ## Use Components Inside of Factories Google discusses the "factories" approach in the post [Resource Factories: A descriptive approach to Terraform](https://medium.com/google-cloud/resource-factories-a-descriptive-approach-to-terraform-581b3ebb59c). This concept is familiar to every major programming framework, and you can apply it to Terraform too. However, unlike Google's approach of creating the factory inside the component ([which we don't recommend](#avoid-creating-factories-inside-of-components)), we suggest using the stack configuration as the factory and the component as the product. By following this method, you create a single component for a specific purpose, such as a VPC, database, or Kubernetes cluster. Then, you can instantiate multiple instances of that component in your stack configuration. In the factory pattern, the component acts like the "factory class," and when defined in the stack configuration, it is used to create and configure multiple component instances. A component provides specific functionality but is not responsible for its own instantiation or configuration; this responsibility is delegated to the factory. This approach decouples your architecture from the configuration, resulting in smaller state files and independent lifecycle management for each instance. Most importantly, it maximizes the reusability of your components. ## Use Component Libraries & Vendoring Utilize a centralized [component library](/components) to distribute and share components across the organization efficiently. This approach enhances discoverability by centralizing where components are stored, preventing sprawl, and ensuring components are easily accessible to everyone. Employ vendoring to retrieve remote dependencies, like components, ensuring the practice of immutable infrastructure. ## Organize Related Components with Folders Organize multiple related components in a common folder. Use nested folders as necessary, to logically group components. For example, by grouping components by cloud provider and layer (e.g. `components/terraform/aws/network/`) ## Document Component Interfaces and Usage Utilize tools such as [terraform-docs](https://terraform-docs.io) to thoroughly document the input variables and outputs of your component. Include snippets of stack configuration to simplify understanding for developers on integrating the component into their stack configurations. Providing examples that cover common use-cases of the component is particularly effective. ## Version Components for Breaking Changes Use versioned folders within the component to delineate major versions (e.g. `/components/terraform//v1/`) ## Use a Monorepo for Your Components For streamlined development and simplified dependency management, smaller companies should consolidate stacks and components in a single monorepo, facilitating easier updates and unified versioning. Larger companies and enterprises with multiple monorepos can benefit from a central repository for upstream components, and then use vendoring to easily pull in these shared components to team-specific monorepos. ## Maintain Loose Coupling Between Components Avoid directly invoking one component from within another to ensure components remain loosely coupled. Specifically for Terraform components (root modules), this practice is unsupported due to the inability to define a backend in a child module, potentially leading to unexpected outcomes. It's crucial to steer clear of this approach to maintain system integrity. ## Reserve Code Generation as an Escape Hatch for Emergencies We generally advise against using code generation for application logic (components), because it's challenging to ensure good test coverage (e.g. with `terratest`) and no one likes to code review machine-generated boilerplate in Pull Requests. This guidance is about generating Terraform application logic. Atmos also supports declarative auxiliary file generation through the [`generate`](/stacks/generate) section. Use it for files that are derived from stack configuration, such as `locals.tf`, `context.auto.tfvars.json`, generated documentation, or small Terraform override files for vendored components. Those files should usually be ignored and regenerated by Atmos instead of reviewed as hand-maintained source. If you find yourself in a situation that seems to require code generation, take a step back and consider if that's the right approach. - Do not code generate providers to [overcome "limitations" in Terraform](https://github.com/hashicorp/terraform/issues/19932#issuecomment-1817043906), for example, to iterate over providers. This is a red flag. Instead, architect your components to work with a single provider - If you are programmatically combining several child modules, consider if they should instead be separated by lifecycle. When you follow these rules, root modules become highly reusable, and you reduce the amount of state managed by a single component, and therefore, the blast radius of changes. ## Separate Your State by Region For Disaster Recovery purposes, always strive to keep the state of your components separate by region. You don't want a regional outage to affect your ability to manage infrastructure in other regions. ## Limit Providers to One or Two Per Component Avoid using multiple providers in a single component, as it reduces the reusability of the component and increases the complexity and blast radius of what it manages. Consider instead "hub" and "spoke" models, where each spoke is its own component with its own lifecycle. In this model, the "spoke" will usually have two providers, one for the current context and one for the "hub." --- ## Stacks Best Practices Here are some essential best practices to follow when designing the Stack configurations that describe your architectures. These guidelines are intended to help developers and operators think about how they model the configuration of their infrastructure in Atmos, for maximum clarity and long-term maintainability. > Physics is the law, everything else is a recommendation. > Anyone can break laws created by people, but I have yet to see anyone break the laws of physics. > — **Elon Musk** ## Define Factories in Stack Configurations Avoid creating factories inside of components, which make them overly complicate and succumb to their massive state. Instead, use stack configurations to serve as factories for provisioning multiple component instances. This approach keeps the state isolated and scales efficiently with the increasing number of component instances. ## Treat Stack Templates like an Escape Hatch Apply them carefully and only when necessary. Using templates instead of inheritance can make stack configurations complex and hard to manage. Be careful using stack templates together with the [factory pattern](#define-factories-in-stack-configurations). The simplest templates are the best templates. Using variable interpolation is perfectly fine, but avoid using complex logic, conditionals, and loops in templates. If you find yourself needing to do this, consider if you are solving the problem in the right way. ## Avoid Too Many Levels of Imports It's very difficult for others to follow relationships when there are too many nested levels and overrides. :::warning Complexity rashes **If you have more than (3) levels of imports, you're probably developing a complexity rash.** Overly DRY configurations can lead to complexity rashes that are difficult to debug and maintain, and impossible for newcomers to understand. ::: ## Balance DRY Principles with Configuration Clarity Avoid overly DRY configuration as it leads to complexity rashes. Sometimes repeating configuration is beneficial for maintenance and clarity. In recent years, the DevOps industry has often embraced the DRY (Don’t Repeat Yourself) principle to an extreme. (And Atmos delivers!) While DRY aims to reduce redundancy and improve maintainability by eliminating duplicate code, overzealous application of this principle leads to complications and rigidity. DRY is not a panacea. In fact, sometimes a bit of repetition is **beneficial**, particularly when anticipating future divergence in configurations or functionality. A balance between DRY and WET (Write Everything Twice) can offer more flexibility, and make it easier to see the entire context in one place without needing to trace through multiple abstractions or indirections Here’s why: 1. **Cognitive Load:** The more you strive for DRYness, the more indirection and abstraction layers you introduce. This makes it harder for developers because they need to navigate through multiple layers of imports and abstractions to grasp the complete picture. 2. **Plan for Future Divergence:** When initially similar configurations are likely diverge over time, keeping them separate will make future changes easier. 3. **Premature Optimization:** Over-optimizing for DRYness may be a form of premature optimization. It’s important to recognize when to prioritize flexibility and clarity over minimal repetition. ## Reserve Code Generation for Stack Configuration While we generally advise against using code generation for application logic (components), it's beneficial for creating configurations where appropriate, such as developer environments and SaaS tenants. These configurations ought to be committed. This is different from Atmos [`generate`](/stacks/generate) blocks, which create auxiliary Terraform component files from stack configuration at runtime. Generated component files should usually be ignored and regenerated by Atmos, while generated stack configuration is often committed when it represents durable tenants, environments, or blueprints. Also, consider if you can [use templates](/templates) instead. ## Use Mixin Pattern for Snippets of Stack Configuration Employ the [mixin pattern](/howto/mixins) for clarity when there are brief configuration snippets that are reusable. Steer clear of minimal stack configurations simply for the sake of DRYness as it frequently leads to too many levels of imports. ## Use YAML Anchors to DRY Configuration YAML anchors are pretty sweet and you don’t get those with tfvars. :::important YAML Anchors Gotchas When you define [YAML anchors](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases), they can only be used within the scope of the same file. This is not an Atmos limitation, but how YAML works. For example, do not work together with [imports](/stacks/imports), where you define an anchor in one stack configuration and try to use it in another. ::: ## Enforce Standards using OPA Policies Apply OPA or JSON Schema validation within stacks to establish policies governing component usage. These policies can be tailored as needed, allowing the same component to be validated differently depending on its context of use. --- ## Terraform Best Practices with Atmos These are some of the best practices we recommend when using Terraform with Atmos. They are opinionated and based on our experience working with Terraform and Atmos. When followed, they lead to more reusable and maintainable infrastructure as code. > Physics is the law, everything else is a recommendation. > Anyone can break laws created by people, but I have yet to see anyone break the laws of physics. > — **Elon Musk** Also, since [Terraform "root modules" are components](/components/terraform), be sure to review the [Component Best Practices](/best-practices/components) for additional guidance on using components with Atmos. :::tip [Cloud Posse](https://github.com/cloudposse) publishes their general [Terraform Best Practices](https://docs.cloudposse.com/best-practices/terraform), which are more general and not specific to Atmos. ::: ## Never Include Components Inside of Other Components We do not recommend consuming one terraform component inside of another as that would defeat the purpose; each component is intended to be a loosely coupled unit of IaC with its own lifecycle. Furthermore, since components define a state backend and providers, it's not advisable to call one root module from another root module. As only the stack backend of the first root module will be used, leading to unpredictable results. ## Use Terraform Overrides to Extend ("Monkey Patch") Vendored Components When you need to extend a component, we recommend using [Terraform Overrides](https://developer.hashicorp.com/terraform/language/files/override). It's essentially a Terraform-native way of [Monkey Patching](https://en.wikipedia.org/wiki/Monkey_patch). This way, you can maintain the original component as a dependency and only override the parts you need to change. Atmos can [generate Terraform override files](/stacks/generate#terraform-overrides-for-vendored-components) from stack configuration, which keeps the patch separate from vendored source code. This is useful when you need a small, stack-specific extension but still want to re-vendor the upstream component cleanly. :::warning Pitfall! Use this technique cautiously because your overrides may break if the upstream interfaces change. There’s no contract that an upstream component will remain the same. ::: To gain a deeper understanding of how this works, you have to understand how [Terraform overrides work](https://developer.hashicorp.com/terraform/language/files/override), and then it will make sense how [vendoring with Atmos](/vendor/) and the Atmos [`generate`](/stacks/generate) section can be used to extend components. Comparison to Other Languages or Frameworks #### Swizzling In [Objective-C](https://spin.atomicobject.com/method-swizzling-objective-c/) and [Swift-UI](https://medium.com/@pallavidipke07/method-swizzling-in-swift-5c9d9ab008e4), swizzling is the method of changing the implementation of an existing selector. In Docusaurus, [swizzling a component](https://docusaurus.io/docs/swizzling) means providing an alternative implementation that takes precedence over the component provided by the theme. #### Monkey Patching You can think of it also like [Monkey Patching](https://en.wikipedia.org/wiki/Monkey_patch) in [Ruby](http://blog.headius.com/2012/11/refining-ruby.html) or [React components](https://medium.com/@singhalaryan06/monkey-patching-mocking-hooks-and-methods-in-react-f6afef73e423), enabling you to override the default implementation. Gatsby has a similar concept called theme [shadowing](https://www.gatsbyjs.com/docs/how-to/plugins-and-themes/shadowing/). --- ## Atmos Cheatsheet **List Stacks** ```shell atmos list stacks ``` **Folder Structure** ``` ├── atmos.yaml ├── components │   └── myapp │   ├── main.tf │   ├── outputs.tf │   └── variables.tf └── stacks ├── catalog │   └── myapp.yaml └── deploy ├── dev.yaml ├── prod.yaml └── staging.yaml ``` **Stack Schema** ``` import: - catalog/something vars: key: value components: terraform: $component: vars: foo: "bar" ``` **Stack Imports Schema** ``` import: - catalog/something - path: "catalog/something/else" context: key: value skip_templates_processing: false ignore_missing_template_values: false skip_if_missing: false ``` **Validate Stacks** ```shell atmos validate stacks ``` **List Components** ```shell atmos list components ``` **Validate Components** ```shell atmos validate component $component -s $stack atmos validate component $component -s $stack --schema-type jsonschema --schema-path $component.json atmos validate component $component -s $stack --schema-type opa --schema-path $component.rego atmos validate component $component -s $stack --schema-type opa --schema-path $component.rego --module-paths catalog atmos validate component $component -s $stack --timeout 15 ``` **List Workflows** ```shell atmos list workflows ``` **Plan Root Modules** ```shell atmos terraform plan ``` **Apply Root Modules** ```shell atmos terraform apply $component --stack $stack atmos terraform apply $component --stack $stack -auto-approve atmos terraform apply $component --stack $stack $planfile ``` **Deploy Root Modules** ```shell atmos terraform apply atmos terraform apply $component --stack $stack -out $planfile atmos terraform apply $component --stack $stack -var "key=value" ``` **Describe Affected** ```shell atmos describe affected atmos describe affected --verbose=true atmos describe affected --ref refs/heads/main atmos describe affected --ref refs/heads/my-new-branch --verbose=true atmos describe affected --ref refs/heads/main --format json atmos describe affected --ref refs/tags/v1.16.0 --file affected.yaml --format yaml atmos describe affected --sha 3a5eafeab90426bd82bf5899896b28cc0bab3073 --file affected.json atmos describe affected --sha 3a5eafeab90426bd82bf5899896b28cc0bab3073 atmos describe affected --ssh-key atmos describe affected --ssh-key --ssh-key-password atmos describe affected --repo-path atmos describe affected --include-spacelift-admin-stacks=true ``` --- ## CLI Commands Cheat Sheet ``` atmos ``` Start an interactive UI to select an Atmos command, component and stack. Press "Enter" to execute the command. [Read more](/cli/commands/help) ``` atmos help ``` Show help for all Atmos CLI commands [Read more](/cli/commands/help) ``` atmos docs ``` Open the Atmos documentation in a web browser [Read more](/cli/commands/docs) ``` atmos version atmos --version ``` Get the Atmos CLI version [Read more](/cli/commands/version/usage) ``` atmos completion ``` Generate completion scripts for `Bash`, `Zsh`, `Fish` and `PowerShell` [Read more](/cli/commands/completion) ``` atmos describe affected ``` Generate a list of the affected Atmos components and stacks given two Git commits [Read more](/cli/commands/describe/affected) ``` atmos describe component ``` Describe the complete configuration for an Atmos component in an Atmos stack [Read more](/cli/commands/describe/component) ``` atmos describe config ``` Show the final (deep-merged) CLI configuration of all `atmos.yaml` file(s) [Read more](/cli/commands/describe/config) ``` atmos describe dependents ``` Show a list of Atmos components in Atmos stacks that depend on the provided Atmos component [Read more](/cli/commands/describe/dependents) ``` atmos describe stacks ``` Show the fully deep-merged configuration for all Atmos stacks and the components in the stacks [Read more](/cli/commands/describe/stacks) ``` atmos describe workflows ``` Show the configured Atmos workflows [Read more](/cli/commands/describe/workflows) ``` atmos terraform ``` Execute `terraform` commands [Read more](/cli/commands/terraform/usage) ``` atmos terraform clean ``` Delete the `.terraform` folder, the folder that `TF_DATA_DIR` ENV var points to, `.terraform.lock.hcl` file, `varfile` and `planfile` for a component in a stack [Read more](/cli/commands/terraform/clean) ``` atmos terraform deploy ``` Execute `terraform apply -auto-approve` on an Atmos component in an Atmos stack [Read more](/cli/commands/terraform/deploy) ``` atmos terraform generate backend ``` Generate a Terraform backend config file for an Atmos terraform component in an Atmos stack [Read more](/cli/commands/terraform/generate/backend) ``` atmos terraform generate backends ``` Generate the Terraform backend config files for all Atmos terraform components in all stacks [Read more](/cli/commands/terraform/generate/backends) ``` atmos terraform generate varfile ``` Generate a varfile (`.tfvar` ) for an Atmos terraform component in an Atmos stack [Read more](/cli/commands/terraform/generate/varfile) ``` atmos terraform generate varfiles ``` Generate the terraform varfiles (`.tfvar`) for all Atmos terraform components in all stacks [Read more](/cli/commands/terraform/generate/varfiles) ``` atmos terraform shell ``` Start a new `SHELL` configured with the environment for an Atmos component in a stack to allow executing all native terraform commands inside the shell without using any atmos-specific arguments and flags [Read more](/cli/commands/terraform/shell) ``` atmos terraform workspace ``` Calculate the Terraform workspace for an Atmos component (from the context variables and stack config), then run `terraform init -reconfigure`, then select the workspace by executing the `terraform workspace select` command [Read more](/cli/commands/terraform/workspace) ``` atmos helmfile ``` Execute `helmfile` commands [Read more](/cli/commands/helmfile/usage) ``` atmos helmfile generate varfile ``` Generate a varfile for a helmfile component in an Atmos stack [Read more](/cli/commands/helmfile/generate-varfile) ``` atmos validate component ``` Validate an Atmos component in a stack using JSON Schema and OPA policies [Read more](/cli/commands/validate/component) ``` atmos validate stacks ``` Validate all Atmos stack configurations [Read more](/cli/commands/validate/stacks) ``` atmos vendor pull ``` Pull sources and mixins from remote repositories for Terraform and Helmfile components and other artifacts [Read more](/cli/commands/vendor/pull) ``` atmos workflow ``` Perform sequential execution of `atmos` and `shell` commands defined as workflow steps [Read more](/cli/commands/workflow) ``` atmos aws eks update-kubeconfig ``` Download `kubeconfig` from an EKS cluster and save it to a file [Read more](/cli/commands/aws/eks/update-kubeconfig) ``` atmos atlantis generate repo-config ``` Generates repository configuration for Atlantis [Read more](/cli/commands/atlantis/generate-repo-config) --- ## Components Cheatsheet **Folder Structure** ``` ├── atmos.yaml ├── components │   └── myapp │   ├── main.tf │   ├── outputs.tf │   └── variables.tf └── stacks ├── catalog │   └── myapp.yaml └── deploy ├── dev.yaml ├── prod.yaml └── staging.yaml ``` **List Components** ```shell atmos list components ``` **Validate Components** ```shell atmos validate component $component -s $stack atmos validate component $component -s $stack --schema-type jsonschema --schema-path $component.json atmos validate component $component -s $stack --schema-type opa --schema-path $component.rego atmos validate component $component -s $stack --schema-type opa --schema-path $component.rego --module-paths catalog atmos validate component $component -s $stack --timeout 15 ``` **Plan Root Modules** ```shell atmos terraform plan $component --stack $stack atmos terraform plan $component --stack $stack -out $planfile ``` **Apply Root Modules** ```shell atmos terraform apply $component --stack $stack atmos terraform apply $component --stack $stack -auto-approve atmos terraform apply $component --stack $stack $planfile ``` **Deploy Root Modules** ```shell atmos terraform deploy atmos terraform deploy $component --stack $stack -out $planfile atmos terraform deploy $component --stack $stack -var "key=value" ``` --- ## Stacks Cheatsheet **Folder Structure** ``` ├── atmos.yaml ├── components │   └── myapp │   ├── main.tf │   ├── outputs.tf │   └── variables.tf └── stacks ├── catalog │   └── myapp.yaml └── deploy ├── dev.yaml ├── prod.yaml └── staging.yaml ``` **Stack Schema** ```yaml import: - catalog/something vars: key: value components: terraform: $component: vars: foo: "bar" ``` **Stack Overrides** ```yaml terraform: overrides: env: {} settings: {} vars: {} command: "opentofu" ``` **Spacelift Settings** ```yaml terraform: components: $component: settings: spacelift: # The `autodeploy` setting was overridden with the value # from `terraform.overrides.settings.spacelift.autodeploy` autodeploy: true workspace_enabled: true ``` **List Components** ```shell atmos list components ``` **Validate Components** ```shell atmos validate component $component -s $stack atmos validate component $component -s $stack --schema-type jsonschema --schema-path $component.json atmos validate component $component -s $stack --schema-type opa --schema-path $component.rego atmos validate component $component -s $stack --schema-type opa --schema-path $component.rego --module-paths catalog atmos validate component $component -s $stack --timeout 15 ``` --- ## Vendoring Cheatsheet **Filesystem Layout** ``` ├── atmos.yaml ├── vendor.yaml └── components └── myapp ├── main.tf ├── outputs.tf └── variables.tf ``` **Vendor Schema** ```yaml title="vendor.yaml" apiVersion: atmos/v1 kind: AtmosVendorConfig metadata: name: example-vendor-config description: Atmos vendoring manifest spec: imports: - "vendor/something" sources: - component: "vpc" source: "oci://public.ecr.aws/cloudposse/components/terraform/stable/aws/vpc:{{.Version}}" version: "latest" targets: ["components/terraform/infra/vpc/{{.Version}}"] included_paths: ["**/*.tf"] tags: - test - networking ``` **Component Schema** ```yaml title="components/$component/component.yaml" apiVersion: atmos/v1 kind: ComponentVendorConfig metadata: name: vpc-flow-logs-bucket-vendor-config description: Source and mixins config for vendoring of 'vpc-flow-logs-bucket' component spec: source: uri: github.com/cloudposse/terraform-aws-components.git//modules/vpc-flow-logs-bucket?ref={{.Version}} version: 1.398.0 included_paths: ["**/*.tf"] excluded_paths: ["**/context.tf"] mixins: - uri: https://raw.githubusercontent.com/cloudposse/terraform-null-label/0.25.0/exports/context.tf filename: context.tf ``` **Vendor Pull** ```shell atmos vendor pull atmos vendor pull --everything atmos vendor pull --component vpc-mixin-1 atmos vendor pull -c vpc-mixin-2 atmos vendor pull -c vpc-mixin-3 atmos vendor pull -c vpc-mixin-4 atmos vendor pull --tags test atmos vendor pull --tags networking,storage ``` --- ## Native CI Atmos brings first-class CI/CD support directly into the CLI. Run Atmos commands in GitHub Actions and get rich job summaries, status checks, output variables, pull-request plan comments, and stored planfile verification without wrapper actions. PR comments require `ci.comments.enabled: true` and `pull-requests: write`. And because every command is **git-aware**, the same CLI powers GitOps end to end: plan and apply only what changed, vendor reusable catalogs across repositories, and commit results back to your source of truth. Kubernetes components also write native job summaries, with a deliberately smaller v1 surface. :::note Replaces the old `cloudposse/github-action-*` Actions You do not need the separate [`cloudposse/github-action-atmos-terraform-*` Actions](/deprecated/github-actions) anymore. The `atmos` CLI now does this work: - It writes job summaries. - It posts status checks. - It sets output variables. - It posts pull-request plan summaries. - It manages planfiles. Do not use the old Actions in new workflows. Existing workflows that use them still work. `actions/checkout` plus an `atmos` command is the minimum for a basic workflow (job summaries, output variables) — it is not universally sufficient. Status checks, check runs, PR comments, and OIDC need matching permissions, and SBOM uploads and `github/artifacts` planfile storage also need the `github-runtime` action. See [Permissions](#permissions) below. ::: > ⚠️ Experimental ## Quick Start **File:** `atmos.yaml` ```yaml ci: enabled: true summary: enabled: true output: enabled: true variables: - has_changes - has_additions - has_destructions - plan_summary checks: enabled: true comments: enabled: true behavior: upsert ``` ```shell - name: Plan run: atmos terraform plan vpc -s prod env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Deploy run: atmos terraform deploy vpc -s prod env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` **CI Configuration** Configure CI providers, job summaries, output variables, status checks, pull-request plan comments, planfile storage, and templates in your `atmos.yaml`. Configuration Reference[Read more](/cli/configuration/ci) ## GitHub Actions Workflows Running Atmos in GitHub Actions reduces to two steps for the basics: **check out the repository, then run an `atmos` command.** Atmos detects the CI environment automatically and produces job summaries and output variables with no extra setup. Status checks, check runs, and PR comments need matching [permissions](#permissions); SBOM uploads and planfile storage in `github/artifacts` also need the `github-runtime` action. Terraform commands use the full native CI feature set. Kubernetes commands currently emit human-readable job summaries only; they do not write `$GITHUB_OUTPUT` values, commit statuses, PR comments, or stored artifacts. The examples below pin the Atmos version via a [repository variable](https://docs.github.com/en/actions/learn-github-actions/variables) named `ATMOS_VERSION` (e.g. set to `1.200.0`). We don't publish a `latest` tag, so always pin to a specific release. ### Permissions Atmos's native CI features rely on the standard GitHub Actions permission scopes — grant only what each workflow's triggers require: | Feature | Permission | | --- | --- | | Job summaries (`$GITHUB_STEP_SUMMARY`) | none required | | Output variables (`$GITHUB_OUTPUT`) | none required | | Commit [status checks](/cli/configuration/ci/checks) | `statuses: write` | | Check runs (modern Checks API) | `checks: write` | | PR comments | `pull-requests: write` | | Checkout | `contents: read` | | OIDC token issuance | `id-token: write` | | SBOM workflow artifact upload | `contents: read`; GitHub Actions runtime credentials (see below) | There is no `comments: write` scope — PR comment writes use `pull-requests: write` (PR comments are issue comments under the hood). If you disable a feature in `atmos.yaml` (e.g. `ci.checks.enabled: false`), you can drop the matching permission. ### SBOM Artifacts Running `atmos sbom generate --upload` uses the detected native CI provider to retain the generated SBOM with the CI run. In GitHub Actions, this is a workflow artifact, not a Dependency Graph import. GitHub's SBOM REST API can export or request a GitHub-generated SPDX report, but it does not accept an arbitrary submitted SBOM. Add `github-runtime` before the command so Atmos can access the runtime API for Actions artifacts. ```yaml - uses: cloudposse/atmos/actions/github-runtime@v1 with: mode: env - run: atmos sbom generate --format spdx-json --output sbom.spdx.json --upload env: GITHUB_TOKEN: ${{ github.token }} ``` ### Validate Workflows Lint GitHub Actions workflows directly with Atmos before running infrastructure commands: ```yaml title=".github/workflows/validate.yml" - name: Validate workflows run: atmos validate ci ``` `atmos validate ci` is an alias for [`atmos ci validate`](/cli/commands/ci/validate). With no file arguments it checks every `.yml` and `.yaml` workflow under the current working directory's `.github/workflows`. In GitHub Actions it emits inline annotations when `ci.enabled: true` and annotations are enabled. Use `--format=sarif` to write a SARIF document for an explicit code-scanning upload step. To lint workflow fixtures or another workflow directory, pass `--workflow-path`. The repository includes an intentionally invalid scenario that is useful for a local smoke test: ```shell atmos ci validate --workflow-path tests/fixtures/scenarios/invalid-github-actions-workflows/.github/workflows ``` The command reports the invalid `branch` key and exits with status 1. Atmos respects `.github/actionlint.yaml` or `.github/actionlint.yml`, including custom self-hosted runner labels. See [`atmos ci validate`](/cli/commands/ci/validate) for file selection, output formats, and configuration details. ### Plan on Pull Request **File:** `.github/workflows/plan.yml` ```yaml name: Plan on: pull_request: permissions: id-token: write contents: read statuses: write checks: write pull-requests: write jobs: plan: runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} steps: - uses: actions/checkout@v6 - run: atmos terraform plan vpc -s prod ``` ### Apply on Merge **File:** `.github/workflows/apply.yml` ```yaml name: Apply on: push: branches: [main] permissions: id-token: write contents: read statuses: write checks: write jobs: apply: runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} steps: - uses: actions/checkout@v6 - run: atmos terraform deploy vpc -s prod ``` `atmos terraform deploy` runs a fresh plan and applies it with `-auto-approve`. When [planfile storage](/ci/planfile-storage) is configured under `components.terraform.planfiles` in `atmos.yaml`, a CI deploy **automatically** downloads the planfile uploaded during the PR run, generates a fresh plan, and performs a semantic comparison before applying (failing on drift by default). The `--verify-plan` flag (or `ATMOS_TERRAFORM_VERIFY_PLAN=true`) is only a per-run override — it forces verification on (and `--verify-plan=false` forces it off), and it **requires planfile storage to be configured**: without it there is no stored plan to verify against, and the deploy errors. You can also run `atmos terraform apply` directly. See [Planfile Storage](/ci/planfile-storage) for details. :::note Storing planfiles in the `github/artifacts` backend requires the runner's artifact credentials, which GitHub withholds from `run:` steps. Add the [`github-runtime` action](/ci/planfile-storage#using-github-artifacts-in-github-actions) before the `plan`/`deploy` steps in the workflows above. ::: ### Deploy Affected Fan out across only the components that changed in the PR using `atmos describe affected --format=matrix`. When `ci.enabled: true` is set in `atmos.yaml`, the matrix is automatically written to `$GITHUB_OUTPUT` — no `--output-file` flag needed. **File:** `.github/workflows/deploy-affected.yml` ```yaml on: pull_request: permissions: id-token: write contents: read statuses: write checks: write pull-requests: write jobs: affected: runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} outputs: matrix: ${{ steps.affected.outputs.matrix }} steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - id: affected run: atmos describe affected --format=matrix deploy: needs: affected if: ${{ needs.affected.outputs.matrix != '' }} runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} strategy: matrix: ${{ fromJson(needs.affected.outputs.matrix) }} fail-fast: false steps: - uses: actions/checkout@v6 - env: COMPONENT: ${{ matrix.component }} STACK: ${{ matrix.stack }} run: atmos terraform deploy "$COMPONENT" -s "$STACK" ``` The `affected` job emits a matrix of `{component, stack}` pairs; the `deploy` job spreads across them in parallel. ### Caching the Toolchain Every CI job reinstalls the same toolchain (Terraform, Helm, and friends). The **build cache** restores the Atmos cache root — which includes the toolchain install path — at the start of a job and saves it at the end, using the same GitHub Actions cache store that `actions/cache` uses. A warm cache turns a multi-minute toolchain install into a near-instant restore. The most secure, lowest-boilerplate wiring is the `actions/cache` composite: Atmos derives the cache key and paths, native `actions/cache` does the storage, and **no runtime token is exposed to your job.** **File:** `.github/workflows/plan.yml` ```yaml steps: - uses: actions/checkout@v6 - uses: cloudposse/atmos/actions/cache@v1 # pin to a release or SHA - run: atmos toolchain install # near-instant on a cache hit - run: atmos terraform plan vpc -s prod ``` Prefer fully automatic caching? Set `ci.cache.auto: both` in `atmos.yaml` and Atmos restores on start and saves on exit for every invocation — no extra workflow steps: **File:** `atmos.yaml` ```yaml ci: cache: enabled: true # master switch (required) auto: both # restore on start AND save on end ``` You can also drive the cache explicitly with the [`atmos ci cache`](/cli/commands/ci/cache) subcommands (`restore`, `save`, `paths`, `list`, `delete`). The cache key defaults to a hash of the toolchain lockfile plus OS/arch with a prefix restore-key fallback, mirroring `actions/cache`; entries are write-once, so an exact hit skips the save. See the [cache configuration reference](/cli/configuration/ci/cache) for keys, paths, and the four GitHub Actions integration options with their security trade-offs. ### Deploy All Fan out across **every** instance defined in your stacks using `atmos list instances --format=matrix`. Use this for full deploys, drift sweeps across the whole estate, or initial bootstraps. Like `describe affected`, it auto-routes to `$GITHUB_OUTPUT` when CI is enabled. **File:** `.github/workflows/deploy-all.yml` ```yaml on: workflow_dispatch: schedule: - cron: '0 6 * * *' # Daily drift sweep permissions: id-token: write contents: read statuses: write checks: write jobs: inventory: runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} outputs: matrix: ${{ steps.list.outputs.matrix }} steps: - uses: actions/checkout@v6 - id: list run: atmos list instances --format=matrix deploy: needs: inventory if: ${{ needs.inventory.outputs.matrix != '' }} runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} strategy: matrix: ${{ fromJson(needs.inventory.outputs.matrix) }} fail-fast: false steps: - uses: actions/checkout@v6 - env: COMPONENT: ${{ matrix.component }} STACK: ${{ matrix.stack }} run: atmos terraform deploy "$COMPONENT" -s "$STACK" ``` ### Authentication The shape of the story: **define a CI profile in `atmos.yaml`, point the workflow at it, done.** Atmos exchanges the GitHub OIDC token for cloud credentials transparently — there is no `atmos auth login` step in CI. **1. Define a `github` [profile](/cli/configuration/profiles).** The profile name is arbitrary; we use `github` to match the [example repos](https://github.com/cloudposse-examples/atmos-native-ci). It holds the `github/oidc` provider plus the identity CI should use: **File:** `atmos.yaml` ```yaml auth: providers: github-oidc: kind: github/oidc region: us-east-1 identities: plat-dev/terraform: provider: github-oidc role_arn: arn:aws:iam::111122223333:role/atmos-terraform default: true # Use this identity unless a component overrides it ``` The IAM role's trust policy must allow GitHub's OIDC issuer for your repo — see [Configuring OpenID Connect in AWS](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) for the trust-policy template. Other clouds work the same way: see [`azure/oidc`](/cli/configuration/auth/providers) and [`gcp/workload-identity-federation`](/cli/configuration/auth/providers). **2. Pick how identities are selected.** All three work under the same profile: - **One identity for everything** Mark one identity `default: true` (as above), or set the `ATMOS_IDENTITY` env var. Simplest setup — works when CI talks to one cloud account/role. - **Per-component identity** Set `settings.identity` on a component or stack to pick a different identity for that scope. Useful when prod components need a different role than dev. - **Inheritance** Identities flow through the [stack inheritance](/stacks) chain like everything else, so you can set the identity on a base stack and let descendants inherit (or override) it. **3. Wire the workflow.** Two pieces: the `id-token: write` permission, and `ATMOS_PROFILE` set to the profile name. **File:** `.github/workflows/apply.yml` ```yaml permissions: id-token: write # Required for GitHub to mint the OIDC token contents: read statuses: write checks: write env: ATMOS_PROFILE: github jobs: deploy: runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} steps: - uses: actions/checkout@v6 - run: atmos terraform deploy vpc -s prod ``` `id-token: write` lets GitHub issue the OIDC JWT; `ATMOS_PROFILE: github` activates the profile defined above. **No `atmos auth login` step is needed** — Atmos exchanges the OIDC token for cloud credentials when it runs the terraform command. (`atmos auth login` exists for interactive/local use; in CI it's redundant.) ### Gating Production with Environments GitHub Actions [environments](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) buy you three things: per-environment secrets and variables, required manual approvals before the job runs, and deployment history visible in the GitHub UI. Wire one in by adding `environment:` to the deploy job: **File:** `.github/workflows/apply.yml` ```yaml jobs: deploy-prod: runs-on: ubuntu-latest container: image: ghcr.io/cloudposse/atmos:${{ vars.ATMOS_VERSION }} environment: prod # Requires approval if configured in repo settings permissions: id-token: write contents: read statuses: write checks: write env: ATMOS_PROFILE: github steps: - uses: actions/checkout@v6 - run: atmos terraform deploy vpc -s prod ``` The **GitHub environment** (`prod`) and the **Atmos stack** (`-s prod`) are independent concepts — one gates the workflow run, the other selects the stack configuration. Many teams happen to name them the same; nothing requires it. For deeper auth reference: [Profiles](/cli/configuration/profiles), [Auth concepts](/stacks/auth), [Providers](/cli/configuration/auth/providers), [Identities](/cli/configuration/auth/identities). **Working Examples** Two reference repositories you can clone and adapt — both include `atmos.yaml`, stack configuration, components, and full workflows. They build on the patterns above with use-case-specific design patterns (preview environments, image promotion, label gating). Basic Example[Read more](https://github.com/cloudposse-examples/atmos-native-ci)   Matrix Example[Read more](https://github.com/cloudposse-examples/atmos-native-ci-advanced) :::warning Concurrency groups aren't a deployment queue By default (`concurrency.queue: single`), a GitHub Actions `concurrency` group holds at most two runs at a time: one **in progress** and one **pending**. Triggering a third run cancels the pending one and takes its place — this eviction happens regardless of `cancel-in-progress`, so intermediate triggers can be silently dropped. Setting `cancel-in-progress: true` additionally cancels the **in-progress** run itself, which can interrupt a Terraform apply and leave a remote state lock that needs manual recovery. Setting `queue: max` instead allows up to 100 pending runs before any get canceled, but it is still not a FIFO deployment queue and cannot be combined with `cancel-in-progress: true`. Remote state locking only prevents concurrent writers — it doesn't protect against a partially applied change or automatically recover an interrupted run. Inspect the affected resources, confirm the previous run has actually stopped, and only then run [`atmos terraform force-unlock`](/cli/commands/terraform/force-unlock) before retrying the apply. GitHub environments add approval gates and merge queues order merges, but neither guarantees deployment _execution_ order — reach for an explicit promotion workflow or deployment controller when you require that. ::: ## Features ### [Job Summaries](/ci/job-summaries) Rich Markdown summaries with resource counts, inline badges, collapsible diffs, and captured command output written to `$GITHUB_STEP_SUMMARY`. Terraform includes plan/apply summaries plus the broader native CI features below. Helm and Helmfile currently write summaries only. Templates are fully customizable with Go template syntax. Kubernetes summaries are intentionally compact: `plan`/`diff` show created, changed, and no-change objects; `apply`/`deploy` show applied or delivered objects; `delete` shows deleted and not-found objects; `validate` shows valid and invalid objects plus errors. ### [Outputs](/cli/configuration/ci/output) Terraform plan and apply results exported as CI output variables for use in downstream jobs. On GitHub Actions, these are written to `$GITHUB_OUTPUT`. Kubernetes commands do not emit output variables in v1. ### [Checks](/cli/configuration/ci/checks) Live commit status checks showing real-time operation progress — "Plan in progress" while running and "3 to add, 1 to change, 0 to destroy" when complete. ### [PR Comments](/cli/configuration/ci/comments) When `ci.comments.enabled: true`, Terraform plans running in a pull-request context can post their rendered plan summary as a PR comment. The comment uses the same template as the job summary and, with the default `upsert` behavior, updates one comment per command, component, and stack on later runs. This requires GitHub Actions `pull-requests: write`; comments are not posted for non-PR runs, when no summary is available, or by apply, deploy, Kubernetes, Helm, or Helmfile commands. ### [Planfile Storage](/ci/planfile-storage) Store and retrieve planfiles across CI pipeline stages using S3, GitHub Artifacts, or local filesystem. The `deploy` command downloads stored planfiles, generates a fresh plan, and performs a semantic comparison to detect drift before applying. ### [Build Cache](/cli/configuration/ci/cache) Warm-start the toolchain across CI jobs by restoring and saving the Atmos cache root via the CI provider's cache store — the same store `actions/cache` uses. Runs automatically with `ci.cache.auto: both`, or explicitly with the [`atmos ci cache`](/cli/commands/ci/cache) subcommands. ### [GitOps](/cli/configuration/git) Atmos is **git-aware**, which is what makes true GitOps possible: the repository is the source of truth, and the pipeline reconciles only what changed. - **Plan what's affected** [`atmos describe affected`](/cli/commands/describe/affected) diffs two Git commits and reports exactly which components and stacks changed — including changes that ripple through dependencies, imports, and remote state. See [Deploy Affected](#deploy-affected) for the matrix workflow. - **Apply only what changed** Fan out across just the affected components, so a PR plans — and a merge applies — only the work that actually changed, instead of re-running the entire estate on every commit. - **Reusable across repositories** Publish service catalogs and module libraries once and [vendor](/vendor/) them into every workload repository, so many repos share one versioned source of truth instead of copy-pasting configuration. - **Automate workload repositories** Commit generated artifacts back to a source-of-truth repository as part of the pipeline. Define managed repositories once under `git.repositories` , then have Atmos commit and push automatically — with signed commits, a bot author identity, and bounded non-fast-forward retries — from [`kind: git` hooks](/stacks/hooks#kind-git) , the [`atmos git`](/cli/commands/git/usage) commands, or CI workflows. This is the foundation for GitOps with Argo CD, Flux, or downstream CI consuming the committed output. ## Commands CI features are activated with the `--ci` flag on supported commands or automatically when running in a CI environment (e.g. GitHub Actions): - **[`atmos terraform plan [--ci]`](/cli/commands/terraform/plan)** Run plan with job summary, output variables, status checks, and planfile upload. - **[`atmos terraform apply [--ci]`](/cli/commands/terraform/apply)** Run apply with job summary, output variables, and status checks. - **[`atmos terraform deploy [--ci]`](/cli/commands/terraform/deploy)** Deploy with stored planfile verification, drift detection, and full CI reporting. - **[`atmos helm template|diff|apply|deploy|delete [--ci]`](/cli/commands/helm/usage)** Run native Helm components with job summaries for rendered/applied object metadata. - **[`atmos helmfile template|diff|apply|sync|deploy|destroy [--ci]`](/cli/commands/helmfile/usage)** Run Helmfile components with job summaries that include captured masked command output. - **[`atmos terraform planfile`](/cli/commands/terraform/planfile)** Manage stored planfiles: upload, download, list, delete, and show. - **[`atmos describe affected --format=matrix`](/cli/commands/describe/affected)** Generate GitHub Actions matrix strategy from affected components. - **[`atmos kubernetes render|plan|diff|apply|deploy|delete|validate [--ci]`](/cli/commands/kubernetes/usage)** Run Kubernetes operations with a native job summary only. No output variables, status checks, comments, or artifacts are emitted. ## Providers Atmos auto-detects the CI environment and selects the appropriate provider: - ****GitHub Actions**** Integrates with GitHub job summaries, commit status checks, and output variables. Requires `GITHUB_TOKEN` for checks and PR features. - ****Generic CI**** Prints summaries, checks, and outputs to stdout. Useful for local development and testing, or any CI provider without native integration. :::note Looking for our old GitHub Actions? The Cloud Posse `cloudposse/github-action-atmos-terraform-*` actions have been [deprecated](/deprecated/github-actions) in favor of native CI. Existing workflows still function, but new projects should use the patterns above. ::: ## Related - [CI Configuration](/cli/configuration/ci) - Configure CI integration in `atmos.yaml` - [CI Commands](/cli/commands/ci) - CI command reference - [Profiles](/cli/configuration/profiles) - Configure CI-specific profiles - [Auth](/stacks/auth) - Configure OIDC authentication for CI --- ## Job Summaries When CI mode is enabled, Atmos writes rich Markdown summaries to `$GITHUB_STEP_SUMMARY` with resource badges, collapsible diffs, command output, and clear warnings about destructive changes. Kubernetes, Helm, and Helmfile components write compact summaries for their native operations. > ⚠️ Experimental ## Plan Summary A plan summary includes resource counts as inline badges, destruction warnings, and a collapsible resource list: ```markdown ## Plan: `vpc` in `plat-ue2-dev` [![create](https://shields.io/badge/CREATE-3-success?style=for-the-badge)](#) [![change](https://shields.io/badge/CHANGE-1-warning?style=for-the-badge)](#) [![destroy](https://shields.io/badge/DESTROY-2-critical?style=for-the-badge)](#) > [!CAUTION] > **Terraform will delete resources!**
Plan: 3 to add, 1 to change, 2 to destroy ### Create - `aws_vpc.main` - `aws_subnet.public[0]` - `aws_subnet.public[1]` ### Change - `aws_security_group.web` ### Destroy - `aws_security_group.deprecated` - `aws_route.legacy`
``` ## Multi-component Plan Summary When `atmos terraform plan` runs more than one component through the dependency graph in CI (`--all`, `--components`, or `--query`), Atmos writes one deterministic aggregate summary after all scheduler workers finish. This avoids concurrent writes to `$GITHUB_STEP_SUMMARY` while preserving per-component detail. The aggregate summary includes: - Total component counts grouped as changed, failed, no changes, and skipped - Total resource counts across successful, non-skipped components - Failed, changed, no-change, and skipped component groups - A per-component table with stack, component, status, summary, resource counts, and duration - Collapsible details for failed and changed components Skipped dependency-blocked components are shown separately from failed components. If any component fails, the aggregate CI exit code is `1`; otherwise it is `2` when any component changed and `0` when every component completed with no changes. ## Apply Summary Apply summaries show the result of the apply operation, including resource counts and any terraform outputs that were produced. ## Test Summary `atmos terraform test` emits a pass/fail summary for the Terraform test framework (`*.tftest.hcl`). The summary shows total / passed / failed / skipped badges, a per-run results table (✅ pass, ❌ fail, ⏭️ skip), and inlines the failing assertions. This pairs naturally with [emulators](/stacks/components/emulator): point `terraform test` at a local emulator and the apply-backed `run` blocks execute — and report — entirely in CI without a cloud account. ## Kubernetes Summary Kubernetes summaries are intentionally smaller than Terraform summaries. They are designed for humans reading the CI run, not for downstream workflow conditionals. For Kubernetes commands, Atmos writes: - `render`: rendered object count and object list - `plan` / `diff`: created, changed, and no-change object counts, plus a collapsible **Kubernetes Diff** block with the per-object unified diff (GitHub renders the `+`/`-` lines in green/red) - `apply` / `deploy`: applied or delivered object counts - `delete`: deleted and not-found object counts - `validate`: valid and invalid object counts - failures: an error section with the command failure The `plan`/`diff` diff is computed from the server-side dry-run against live cluster state, with server-managed noise (`managedFields`, `resourceVersion`, `status`, …) stripped. `Secret` objects are **omitted** from the diff so their data is never written to the (unmasked) job summary; they still appear in the object list with their action. Large diffs are truncated to their tail to stay within the platform job-summary size limit. Kubernetes CI summaries do not emit `$GITHUB_OUTPUT` variables, commit statuses, PR comments, or stored artifacts in v1. ## Helm Summaries Native Helm components write summaries for these operations when `ci.enabled: true` and CI mode is detected or forced with `--ci`/`ATMOS_CI`: | Command | Summary template | |---------|------------------| | `template`, `render` | `helm.template` | | `diff`, `plan` | `helm.diff` | | `apply`, `deploy` | `helm.apply` | | `delete`, `destroy` | `helm.delete` | Helm summaries are summaries-only. They do not write `$GITHUB_OUTPUT` values, commit statuses, PR comments, or artifacts. The summary includes component, stack, command status, a local reproduction command, and Helm metadata such as release name, namespace, chart, target, object counts, object kinds, and rendered manifest size when available. When `plan` or `apply` selects multiple components with `--all` or `--affected`, Atmos writes one deterministic aggregate summary after the dependency-graph run completes instead of writing a separate summary from every component. The aggregate includes result counts and a stable per-component table with stack, component, chart, release, namespace, target, status, and duration. Plan summaries distinguish changed and unchanged components and include collapsible diffs; failed components include their error details. The same aggregate behavior applies to the `diff` and `deploy` aliases and composes with tag and label filters. For a single cluster-backed apply/deploy/delete operation, the component summary also includes an operation-specific `release` block with the effective wait strategy, timeout, chart-hook state, and applicable recovery, Job-wait, CRD, cleanup, and history values. For external delivery, apply/deploy reports `applied: false` while delete reports `deleted: false`; both include the selected target kind and `reason: external_target` instead of presenting stored release policy as active. ## Helmfile Summaries Helmfile components write summaries for these operations when `ci.enabled: true` and CI mode is detected or forced with `--ci`/`ATMOS_CI`: | Command | Summary template | |---------|------------------| | `template` | `helmfile.template` | | `diff` | `helmfile.diff` | | `apply`, `sync`, `deploy` | `helmfile.apply` | | `destroy` | `helmfile.destroy` | Helmfile summaries are summaries-only. The summary includes component, stack, command status, a local reproduction command, and captured masked stdout/stderr in a collapsible section. ## Configuration - **`ci.enabled`** Master switch for all native-CI integration (summaries, outputs, checks). It must be `true` for any of them to run — the per-feature toggles below have no effect on their own. **Default:** `false` - **`ci.summary.enabled`** Enable or disable job summaries (requires `ci.enabled: true`). **Default:** `true` - **`ci.summary.template`** Override the default summary template with a custom template file path. ## Template Customization Override the default plan and apply summary templates with your own Markdown templates. Templates use Go template syntax with access to plan/apply context data. ### Custom Template Configuration **File:** `atmos.yaml` ```yaml ci: templates: base_path: ".atmos/ci/templates" terraform: plan: "plan.md" apply: "apply.md" helm: template: "helm-template.md" diff: "helm-diff.md" apply: "helm-apply.md" delete: "helm-delete.md" helmfile: template: "helmfile-template.md" diff: "helmfile-diff.md" apply: "helmfile-apply.md" destroy: "helmfile-destroy.md" ``` - **`ci.templates.base_path`** Directory containing custom template files, relative to the repository root. **Default:** `.atmos/ci/templates` - **`ci.templates.terraform.plan`** Filename of the custom plan summary template within the base path. **Default:** `plan.md` (built-in) - **`ci.templates.terraform.apply`** Filename of the custom apply summary template within the base path. **Default:** `apply.md` (built-in) - **`ci.templates.helm.*`** Filenames of custom native Helm summary templates for `template`, `diff`, `apply`, and `delete`. - **`ci.templates.helmfile.*`** Filenames of custom Helmfile summary templates for `template`, `diff`, `apply`, and `destroy`. ### Template Context Templates receive a context object for the operation being summarized. The built-in templates are located under `pkg/ci/plugins/*/templates/` in the Atmos source. #### Plan Template Variables | Variable | Type | Description | |----------|------|-------------| | `.Component` | string | Component name | | `.Stack` | string | Stack name | | `.Command` | string | Terraform command (`plan`) | | `.HasChanges` | bool | Whether the plan has changes | | `.Additions` | int | Number of resources to create | | `.Changes` | int | Number of resources to change | | `.Destructions` | int | Number of resources to destroy | | `.Imports` | int | Number of resources to import | | `.Summary` | string | One-line plan summary | | `.Resources` | object | Resource lists by action type | | `.Warnings` | \[]string | Terraform warning messages | #### Apply Template Variables | Variable | Type | Description | |----------|------|-------------| | `.Component` | string | Component name | | `.Stack` | string | Stack name | | `.Command` | string | Terraform command (`apply`) | | `.Success` | bool | Whether apply succeeded | | `.Summary` | string | One-line apply summary | | `.Resources` | object | Resource lists by action type | | `.Outputs` | map | Terraform outputs | | `.Warnings` | \[]string | Terraform warning messages | ### Example Custom Template **File:** `.atmos/ci/templates/plan.md` ```markdown ## {{ .Component }} / {{ .Stack }} {{ if .HasChanges }} **Changes detected:** {{ .Additions }} to add, {{ .Changes }} to change, {{ .Destructions }} to destroy {{ if gt .Destructions 0 }} > **Warning:** This plan destroys resources! {{ end }} {{ else }} No changes. Infrastructure is up-to-date. {{ end }} ``` ## Related - [Native CI Overview](/ci) - Feature overview - [CI Configuration](/cli/configuration/ci) - Full configuration reference --- ## Planfile Storage Planfile storage enables the plan-then-deploy workflow in CI. When configured, `atmos terraform plan` uploads planfiles to storage, and `atmos terraform deploy` downloads and verifies them before applying. > ⚠️ Experimental ## Configuration **File:** `atmos.yaml` ```yaml components: terraform: planfiles: # Drift verification on `deploy`: fail (default under CI) | warn | off verify: fail # Stores are tried in priority order priority: - "github" - "s3" - "local" # Named stores stores: github: type: github/artifacts options: retention_days: 7 s3: type: aws/s3 options: bucket: "my-terraform-planfiles" prefix: "atmos/" region: "us-east-1" local: type: local/dir options: path: ".atmos/planfiles" ``` ## Storage Backends | Backend | Type | Best For | |---------|------|----------| | **GitHub Artifacts** | `github/artifacts` | GitHub Actions workflows (recommended) | | **S3** | `aws/s3` | AWS-native environments, cross-provider workflows | | **Local** | `local/dir` | Testing and development | :::tip If `components.terraform.planfiles` is not configured, planfile storage operations are silently skipped. CI summaries and status checks still work without planfile storage. The one exception: explicitly requesting verification with `--verify-plan` (or `ATMOS_TERRAFORM_VERIFY_PLAN=true`) errors when planfile storage is not configured — there is no stored plan to verify against, and the flag cannot stand in for the storage settings it depends on. ::: ## Using GitHub Artifacts in GitHub Actions The `github/artifacts` backend talks to the **GitHub Actions Artifacts API directly** — Atmos is the native client, not a wrapper around `actions/upload-artifact`. To do that it needs the runner's runtime credentials, `ACTIONS_RUNTIME_TOKEN` and `ACTIONS_RESULTS_URL`. GitHub injects those into **action steps** (`uses:`) but **withholds them from `run:` steps** — a deliberate least-privilege decision. So when Atmos's own commands do the upload from a `run:` step, they will fail with: ``` GitHub Artifacts upload requires running within GitHub Actions (ACTIONS_RUNTIME_TOKEN and ACTIONS_RESULTS_URL must be set) ``` Surface the credentials with the Atmos [`github-runtime`](https://github.com/cloudposse/atmos/tree/main/actions/github-runtime) action. With `mode: env` it exports the credentials to `$GITHUB_ENV` once, and GitHub then injects them automatically into every later `run:` step — Atmos's commands pick them up from the environment with no per-step wiring: **File:** `.github/workflows/atmos.yaml` ```yaml steps: - uses: cloudposse/atmos/actions/github-runtime@v1 # pin to a release or SHA with: mode: env # Credentials are now in the environment of every later run step. - run: atmos terraform plan mycomponent -s prod --ci # uploads the planfile to github/artifacts - run: atmos terraform deploy mycomponent -s prod --ci # downloads, verifies, then applies ``` To keep the credentials out of unrelated steps, use the default `mode: output` instead: the action emits masked step outputs that you thread, via `env:`, only into the steps that store planfiles. This is the same `github-runtime` mechanism the `atmos ci cache` backend uses — see the [cache configuration docs](/cli/configuration/ci/cache) for the full security tradeoff between scoped (`mode: output`) and ambient (`mode: env`) credentials. **File:** `Least-privilege alternative (mode: output)` ``` steps: - uses: cloudposse/atmos/actions/github-runtime@v1 id: ghr - run: atmos terraform plan mycomponent -s prod --ci env: ACTIONS_RUNTIME_TOKEN: ${{ steps.ghr.outputs.runtime-token }} ACTIONS_RESULTS_URL: ${{ steps.ghr.outputs.results-url }} ``` :::note You only need this when **Atmos** performs the upload from a `run:` step. The credentials are already present inside `uses:` action steps, and the `local/dir` and `aws/s3` backends don't use them at all. ::: ## How It Works ### Plan Phase When `atmos terraform plan` runs in CI mode with planfile storage configured: 1. Terraform generates a binary planfile 2. The planfile is bundled with the lock file into a tar archive 3. SHA256 integrity checksums are computed 4. The bundle is uploaded to the configured storage backend ### Deploy Phase When `atmos terraform deploy` runs in CI mode with planfile storage configured, verification is **automatic**: 1. Downloads the stored planfile bundle from storage 2. Verifies SHA256 integrity checksums 3. Generates a fresh plan against current infrastructure 4. Performs JSON-structural comparison between stored and fresh plans 5. **If plans match:** Applies the **fresh** plan (generated with the apply-time identity and state) 6. **If plans drift:** Fails the deploy (mode `fail`) or warns and proceeds (mode `warn`) Atmos reconciles rather than replays: it applies the freshly generated plan once the diff confirms it matches what was reviewed, so the deploy uses the apply-time state and credentials while still guaranteeing no material drift. This avoids the brittleness of a saved plan — which goes stale when the state moves, and whose base credentials come from the apply environment, not the plan (so a plan built on a PR can fail to apply on merge). (To replay the stored binary directly, use `--from-plan` / `--planfile`.) Verification runs on `deploy` only — `apply` stays a thin wrapper. :::note GitHub Actions runtime token With the `github/artifacts` store, the automatic download (like the upload) talks to the GitHub Artifacts runtime API, so `deploy` must also run after the [`github-runtime`](https://github.com/cloudposse/atmos/tree/main/actions/github-runtime) action surfaces `ACTIONS_RUNTIME_TOKEN` / `ACTIONS_RESULTS_URL` — see [Using GitHub Artifacts in GitHub Actions](#using-github-artifacts-in-github-actions) above. ::: ## Plan Verification Verification compares the **JSON plan structures** (`terraform show -json`) of the stored and fresh plans — not a naive text diff — detecting meaningful changes while ignoring cosmetic noise (sorted keys, masked sensitive values, skipped computed-hash attributes and data sources). This semantic comparison is deliberate. A plan legitimately contains values that vary between review and apply — attributes "known after apply," computed fields, hashes, ordering, timestamps. A byte-for-byte check (or Terraform's own saved-plan apply, which any state-lineage change invalidates) would reject a plan that is still perfectly valid. Verification needs **wiggle room**: tolerate benign variation while still catching substantive drift — a resource added, removed, or actually changed. It is configurable via `components.terraform.planfiles.verify`: ```yaml components: terraform: planfiles: verify: fail # fail (default under CI) | warn | off ``` - **`fail`** (default under CI): fail the deploy on drift. - **`warn`**: log the drift but proceed with the fresh plan. - **`off`**: skip verification (and the stored-plan download). A companion boolean, `components.terraform.planfiles.required`, governs whether a stored plan **must exist** to verify against. It defaults to tracking `verify` strictness (required when verification resolves to `fail`), so a fail-by-default CI deploy fails loudly instead of silently applying an unverified fresh plan. See [Missing stored plan](/components/terraform/planfiles#missing-stored-plan). Per-run overrides: `--verify-plan` (force `fail`) and `--verify-plan=false` (force `off`); the CLI flag beats config, which beats the CI default. See [`atmos terraform deploy`](/cli/commands/terraform/deploy#automatic-plan-verification-in-ci) and [Planfile drift verification](/components/terraform/planfiles#drift-verification). ## CLI Commands **Planfile Commands** Manage stored planfiles with upload, download, list, delete, and show commands. Planfile Reference[Read more](/cli/commands/terraform/planfile) ## Related - [Native CI Overview](/ci) - Feature overview - [CI Cache Configuration](/cli/configuration/ci/cache) - The same `github-runtime` credential mechanism, in depth - [CI Configuration](/cli/configuration/ci) - Full configuration reference - [`atmos terraform planfile`](/cli/commands/terraform/planfile) - CLI commands --- ## Atmos CLI Atmos is a CLI for provisioning infrastructure with Terraform, Helmfile, Packer, and more, using stack-based configuration. Run it with a subcommand for scripting and automation, or with no arguments at all to launch an interactive UI for running Atmos commands against any component or stack. Press `Enter` to execute the command for the selected stack and component ## Usage Just run the `atmos` command in your terminal to start the interactive UI. Use the arrow keys to select stacks and components to deploy. ```shell atmos ``` :::tip To quickly check the Atmos version, use `atmos --version` or `atmos version`. See the [version command](/cli/commands/version/usage) for more details. ::: - Use the `right/left` arrow keys to navigate between the "Commands", "Stacks" and "Components" views - Use the `up/down` arrow keys (or the mouse wheel) to select a command to execute, component and stack - Use the `/` key to filter/search for the commands, components, and stacks in the corresponding views - Use the `Tab` key to flip the "Stacks" and "Components" views. This is useful to be able to use the UI in two different modes: - `Mode 1: Components in Stacks`. Display all available stacks, select a stack, then show all the components that are defined in the selected stack - `Mode 2: Stacks for Components`. Display all available components, select a component, then show all the stacks where the selected component is configured - Press `Enter` to execute the selected command for the selected stack and component ## Interactive Picker To get an idea of what it looks like, just [try our quickstart](/quick-start/) and run the [`atmos`](/cli) command to start an interactive UI in the terminal. The recording below browses stacks and components, filters the list, flips between Mode 1 and Mode 2 with `Tab`, and finally presses `Enter` to execute `validate component`. ## Featured Examples Interactive mode is one way in — Atmos is a full CLI you can script and automate. Here's a taste of what it can do; browse the [full list of examples](/examples) for more. --- ## atmos Use these commands to perform operations. ## Commands --- ## atmos about Use this command to display information about Atmos, its features, and benefits. _\[Video: atmos about]_ ## Usage ```bash atmos about ``` ## Description The `about` command displays a summary of what Atmos is and its key features. This is useful for: - Getting a quick overview of Atmos capabilities - Sharing information with team members new to Atmos - Understanding the core value proposition ## Output The command displays: - **What Atmos is**: An open-source framework for managing Infrastructure as Code at scale - **Key Features**: Reusable components, stacks, YAML-based configuration, multi-tool support - **Why Atmos**: Benefits like reduced complexity, consistency, and accelerated delivery ## See Also - [`atmos support`](/cli/commands/support) - Get help and support options - [`atmos version`](/cli/commands/version/usage) - Show Atmos version - [Introduction to Atmos](/intro) - Learn more about Atmos --- ## atmos ai ask Use this command to ask the AI assistant a specific question and get an immediate response, without entering an interactive chat session. > ⚠️ Experimental **Configure AI** Learn how to configure AI providers, skills, tools, and sessions in your atmos.yaml. Configuration Reference[Read more](/cli/configuration/ai) ## Description The `atmos ai ask` command allows you to ask questions directly from the command line. The AI assistant analyzes your question in the context of your Atmos configuration and provides a detailed response. This is ideal for: - Quick one-off questions - Scripting and automation - Getting specific information without interactive mode - CI/CD pipeline integration ## Usage ```shell atmos ai ask [question] ``` ## Arguments - **`question`** The question to ask the AI assistant (required). Use quotes for multi-word questions. ## Flags - **`--include `** Add glob patterns to include in context (can be repeated) - **`--exclude `** Add glob patterns to exclude from context (can be repeated) - **`--no-auto-context`** Disable automatic context discovery - **`--no-tools`** Disable tool execution for faster, simpler queries - **`--mcp `** MCP servers to use (comma-separated). Skips automatic server routing and starts only the specified servers. Can be repeated: `--mcp aws-iam --mcp aws-billing` . Use [`atmos mcp list`](/cli/commands/mcp/list) to see available server names. Environment variable: `ATMOS_AI_MCP` :::tip Smart MCP Routing When multiple MCP servers are configured with API providers, Atmos automatically selects only the servers relevant to your question using a lightweight routing call. Use `--mcp` to bypass routing and specify servers directly. CLI providers (`claude-code`, `codex-cli`, `gemini-cli`) skip routing — all servers are passed to the CLI tool, which handles selection internally. ::: ## Examples ### General Atmos Questions ```shell $ atmos ai ask "What are Atmos stacks?" 👽 Thinking... Atmos stacks are configurations that define how components are deployed across different environments. A stack is essentially a YAML file that specifies: 1. Which components to deploy 2. Variable values for those components 3. Environment-specific settings 4. Stack inheritance relationships Stacks enable you to: - Manage multiple environments (dev, staging, prod) - Share common configurations across environments - Override settings for specific use cases - Organize infrastructure logically ``` ### Configuration-Specific Questions ```shell $ atmos ai ask "what stacks and components do we have in the infra?" 👽 Thinking... I'll list all the available stacks and components in your infrastructure. Based on the infrastructure configuration, here are the stacks and components: ## Stacks (4 total) 1. **ue1-network** - Network stack in us-east-1 2. **ue1-prod** - Production stack in us-east-1 3. **uw2-network** - Network stack in us-west-2 4. **uw2-prod** - Production stack in us-west-2 ## Components by Stack ### ue1-network • tgw/attachment - Transit Gateway attachment • tgw/hub - Transit Gateway hub • vpc - Virtual Private Cloud ### ue1-prod • tgw/attachment - Transit Gateway attachment • vpc - Virtual Private Cloud ### uw2-network • tgw/attachment - Transit Gateway attachment • tgw/cross-region-hub-connector - Cross-region Transit Gateway connector • vpc - Virtual Private Cloud ### uw2-prod • tgw/attachment - Transit Gateway attachment • vpc - Virtual Private Cloud ## Summary Your infrastructure is organized across two AWS regions (us-east-1 and us-west-2), with dedicated network and production stacks in each region. The network stacks contain Transit Gateway hubs for network connectivity, while the production stacks use Transit Gateway attachments to connect to the network infrastructure. All stacks include VPC components for network isolation. ``` ```shell $ atmos ai ask "what is the metadata section of the vpc component in the ue1-prod stack?" 👽 Thinking... The metadata section of the vpc component in the ue1-prod stack is: component: mock locked: true ``` ```shell $ atmos ai ask "what is the metadata section of the vpc component in the ue1-prod stack?" 👽 Thinking... I'll help you find the metadata section of the vpc component in the ue1-prod stack. Based on the results, the metadata section of the vpc component in the ue1-prod stack is: metadata: component: mock locked: true This metadata indicates that: • The component field is set to "mock" (which is the actual Terraform component being used) • The locked field is set to true (indicating this component configuration is locked) ``` ### Component-Specific Questions ```shell atmos ai ask "Describe the vpc component in the dev stack" atmos ai ask "What variables does the eks component require?" atmos ai ask "How is the rds component configured in production?" ``` ### Best Practices Questions ```shell atmos ai ask "What's the best way to organize my components?" atmos ai ask "How should I structure my stack inheritance?" atmos ai ask "What are the security best practices for Atmos?" ``` ### Workflow Questions ```shell atmos ai ask "How do I use workflows in Atmos?" atmos ai ask "What's the difference between terraform and helmfile components?" atmos ai ask "How do I vendor external components?" ``` ### MCP Server Questions ```shell # Auto-routing selects the right server automatically atmos ai ask "What did we spend on EC2 last month?" atmos ai ask "Is GuardDuty enabled in us-east-1?" # Specify servers directly (skip routing) atmos ai ask --mcp aws-iam "List all admin roles" atmos ai ask --mcp aws-iam,aws-cloudtrail "Who accessed the admin role?" # Environment variable ATMOS_AI_MCP=aws-billing atmos ai ask "Show our billing summary" ``` ## Advanced Usage ### In Scripts and Automation ```bash #!/bin/bash # Get AI assistance in a script response=$(atmos ai ask "What components are affected by changes to networking?") echo "$response" # Use in CI/CD if atmos validate stacks; then atmos ai ask "What's the recommended deployment order for these stacks?" fi ``` ### Complex Multi-Part Questions ```shell # Use quotes for multi-word questions atmos ai ask "How do I migrate from one cloud provider to another using Atmos?" # Ask about specific error messages atmos ai ask "I'm getting error 'component not found'. What does this mean and how do I fix it?" ``` ## Output Format The command outputs: 1. **Thinking indicator** - Shows the AI is processing 2. **AI response header** - Identifies the response source 3. **Formatted response** - Markdown-formatted answer with code blocks, lists, and emphasis ## Comparison with Other Commands - **`atmos ai ask`** One-off questions, scripting, quick answers. - **`atmos ai chat`** Extended conversations, learning, exploration. ## Related Commands --- ## atmos ai chat Use this command to start an interactive chat session with the Atmos AI assistant. Get intelligent help with Atmos concepts, configuration analysis, and best practices. > ⚠️ Experimental **Configure AI** Learn how to configure AI providers, skills, tools, and sessions in your atmos.yaml. Configuration Reference[Read more](/cli/configuration/ai) ## Description The `atmos ai chat` command opens a terminal-based chat interface where you can have extended conversations with the AI assistant about your Atmos infrastructure. The AI assistant has access to your current Atmos configuration and can help with: - Explaining Atmos concepts and architecture - Analyzing your specific components and stacks - Suggesting optimizations and best practices - Debugging configuration issues - Providing step-by-step implementation guidance ## Usage ```shell atmos ai chat [flags] ``` ### Flags - **`--session `** Start or resume a named session. If the session doesn't exist, it will be created. If it exists, the conversation history will be loaded. - **`--mcp `** MCP servers to use (comma-separated). Starts only the specified servers instead of all configured servers. Can be repeated: `--mcp aws-iam --mcp aws-billing` . Use [`atmos mcp list`](/cli/commands/mcp/list) to see available server names. Environment variable: `ATMOS_AI_MCP` :::tip MCP in Chat Mode In chat mode, automatic server routing is skipped because the question isn't known when servers start. Use `--mcp` to select specific servers, or all configured servers will be started. ::: ### Examples ```shell # Start anonymous session (auto-generated name) atmos ai chat # Start or resume named session atmos ai chat --session vpc-refactor # Resume existing session atmos ai chat --session my-session # Start with specific MCP servers atmos ai chat --mcp aws-billing atmos ai chat --mcp aws-iam,aws-security ``` ## Examples ### Basic Chat Session ```shell atmos ai chat ``` This opens an interactive chat where you can ask questions like: ``` You: What components are available in my configuration? AI: Based on your configuration, I can see the following components... You: How do I validate my stack configuration? AI: You can validate stack configurations using... ``` ### Using with Different Providers All configured providers are available concurrently. Press `Ctrl+P` during a chat session to switch between them. ## Interactive Features The chat interface supports: - **Multi-turn conversations**: Ask follow-up questions - **Context awareness**: The AI remembers your conversation - **Markdown rendering**: AI responses rendered with rich formatting (bold, italic, lists, tables) - **Syntax highlighting**: Code blocks displayed with language-specific syntax highlighting - **History navigation**: Navigate through previous messages with ↑/↓ arrow keys (like Bash history) - **Multi-line input**: Press `Shift+Enter` or `Alt+Enter` to add newlines in your messages - **Skill switching**: Press `Ctrl+A` to switch between [21+ specialized AI skills](/cli/configuration/ai/skills) (e.g., `atmos-terraform`, `atmos-stacks`, `atmos-validation`) - **Session management**: Press `Ctrl+L` to switch between sessions without leaving the chat - **Provider switching**: Press `Ctrl+P` to switch between configured AI providers mid-conversation - **Session history**: See session metadata (name, created date, message count) in the header - **Exit commands**: Type `exit`, `quit`, or press `Ctrl+C` to close the chat ### Keyboard Shortcuts - **`Enter`** Send message. - **`Shift+Enter` or `Alt+Enter`** Add newline (multi-line input). - **`↑`** Navigate to previous message in history. - **`↓`** Navigate to next message in history. - **`Ctrl+L`** Open session picker. - **`Ctrl+N`** Create new session. - **`Ctrl+A`** Switch AI skill. - **`Ctrl+P`** Switch AI provider. - **`Ctrl+C`** Quit chat. ### History Navigation Navigate through your previous messages using the arrow keys, just like in Bash or Zsh: ``` You: What components are available? AI: [Response about components...] # Press ↑ to recall your last message You: What components are available? [← Previous message restored] # Edit and resend, or press ↓ to return to empty input ``` **How it works:** - Press `↑` to recall previous messages, starting from most recent - Press `↓` to move forward through history - When you reach the end of history (↓ past the last message), your original unsent input is restored - Sending a message automatically resets history navigation This is especially useful for: - Reusing similar queries with minor changes - Correcting typos in sent messages - Iterating on complex questions - Avoiding retyping long commands ### Markdown Rendering & Syntax Highlighting AI responses are automatically rendered with rich markdown formatting for better readability: **Supported Formatting:** - **Bold** and _italic_ text - Headers and subheadings - Bulleted and numbered lists - Tables with proper alignment - Code blocks with syntax highlighting - Blockquotes and horizontal rules ### Multi-line Messages You can compose multi-line messages using `Shift+Enter`: ``` You: I need help with a complex Terraform configuration. Here's what I'm trying to do: [Shift+Enter] 1. Create a VPC with multiple subnets [Shift+Enter] 2. Set up route tables for each subnet [Shift+Enter] 3. Configure NAT gateways [Enter to send] ``` This is especially useful for: - Pasting code snippets - Writing detailed questions - Formatting structured information - Creating multi-paragraph descriptions ### Session Picker While chatting, press `Ctrl+L` to open the session picker: ``` Session List ↑/↓: Navigate | Enter: Select | Esc/q: Back | Ctrl+C: Quit → vpc-refactor (Jan 20, 14:30, 47 messages) rds-troubleshooting (Jan 18, 09:15, 23 messages) session-20251015-091032 (Jan 15, 09:10, 12 messages) ``` **Session Picker Controls:** - `↑`/`↓` or `j`/`k` - Navigate sessions - `Enter` - Switch to selected session - `Esc` or `q` - Return to current chat - `Ctrl+C` - Quit application This allows you to seamlessly switch between different conversation topics without restarting the chat. ### Provider Switching While chatting, press `Ctrl+P` to switch between configured AI providers: ``` Provider Selection ↑/↓: Navigate | Enter: Select | Esc/q: Back | Ctrl+C: Quit → Anthropic Claude - Industry-leading reasoning and coding OpenAI GPT - Most popular, widely adopted models Google Gemini - Strong multimodal capabilities xAI Grok - Real-time data access Ollama - Local models for privacy and offline use ``` **Provider Switching Controls:** - `↑`/`↓` or `j`/`k` - Navigate providers - `Enter` - Switch to selected provider - `Esc` or `q` - Return to current chat - `Ctrl+C` - Quit application When you switch providers: - **Message history is preserved** - Continue your conversation with a different AI - **Session is updated** - The new provider is saved to your session - **Seamless transition** - No need to restart the chat or lose context **Example Use Case:** ``` You: [Using Claude] Explain the difference between Terraform and Helmfile Claude: [Detailed technical explanation...] [Press Ctrl+P, switch to GPT-4] You: Can you give me a simpler explanation? GPT-4: [Alternative explanation with different perspective...] ``` This feature is useful for: - Comparing responses from different AI models - Using specialized models for specific tasks - Switching to local models (Ollama) for privacy-sensitive questions - Trying different providers to find the best fit for your workflow **Note:** Only providers configured in your `atmos.yaml` will appear in the provider picker. ## Common Use Cases ### Learning Atmos Concepts ``` You: I'm new to Atmos. What should I know about stacks and components? AI: [Provides detailed explanation of Atmos architecture] ``` ### Debugging Configuration Issues ``` You: I'm getting a validation error for my vpc component. How do I fix it? AI: [Analyzes the error and provides specific guidance] ``` ### Best Practices ``` You: What's the best way to organize my stack configurations? AI: [Suggests organizational patterns and best practices] ``` ## Related Commands --- ## atmos ai exec Give the AI a natural-language prompt and it will run Atmos commands, shell commands, and tool calls to carry out the task -- then return structured results. Designed for scripting, automation, and CI/CD pipelines where you need the AI to act, not just answer. > ⚠️ Experimental **Configure AI** Learn how to configure AI providers, skills, tools, and sessions in your atmos.yaml. Configuration Reference[Read more](/cli/configuration/ai) ## Description The `atmos ai exec` command takes a natural-language prompt, lets the AI run Atmos commands and shell commands to fulfill it, and returns structured output. Unlike `atmos ai ask` which produces human-readable answers, `exec` actually executes tools and commands, then returns machine-parseable results with exit codes, tool call details, and token usage. Use it for: - **Automation**: The AI runs `atmos describe`, `atmos validate`, shell commands, etc. on your behalf - **CI/CD Pipelines**: Reliable exit codes and JSON output for deployment workflows - **Batch Processing**: Process multiple infrastructure tasks programmatically ## Usage ```shell atmos ai exec [prompt] [flags] ``` The prompt can be provided as: - **Command argument**: `atmos ai exec "your prompt"` - **Stdin pipe**: `echo "your prompt" | atmos ai exec` ## Arguments - **`prompt`** The prompt to execute (optional if using stdin). Use quotes for multi-word prompts. ## Flags - **`--format, -f`** Output format: `text` , `json` , `markdown` (default: `text` ) - **`--output, -o`** Output file path (default: stdout) - **`--no-tools`** Disable tool execution for faster, simpler queries - **`--context`** Include stack context in the prompt - **`--provider, -p`** Override AI provider. API: `anthropic` , `openai` , `gemini` , `grok` , `ollama` , `bedrock` , `azureopenai` . CLI: `claude-code` , `codex-cli` , `gemini-cli` . - **`--session, -s`** Session ID for conversation context (enables multi-turn execution) - **`--include`** Add glob patterns to include in context (can be repeated) - **`--exclude`** Add glob patterns to exclude from context (can be repeated) - **`--no-auto-context`** Disable automatic context discovery - **`--mcp `** MCP servers to use (comma-separated). Skips automatic server routing and starts only the specified servers. Can be repeated: `--mcp aws-iam --mcp aws-billing` . Use [`atmos mcp list`](/cli/commands/mcp/list) to see available server names. Environment variable: `ATMOS_AI_MCP` :::tip Smart MCP Routing When multiple MCP servers are configured with API providers, Atmos automatically selects only the servers relevant to your prompt using a lightweight routing call. Use `--mcp` to bypass routing and specify servers directly. CLI providers (`claude-code`, `codex-cli`, `gemini-cli`) skip routing — all servers are passed to the CLI tool, which handles selection internally. ::: ## Exit Codes The command uses standard Unix exit codes for automation: - **`0`** Success. - **`1`** AI error (API failure, invalid response, configuration error). - **`2`** Tool execution error. ## Output Formats ### Text Format (Default) Plain text response, suitable for human reading and logging: ```shell atmos ai exec "List all available stacks" ``` Output: ``` Based on your configuration, here are the available stacks: 1. dev-us-east-1 2. staging-us-east-1 3. prod-us-east-1 ... ``` ### JSON Format Structured output with complete metadata: ```shell atmos ai exec "Describe the vpc component" --format json ``` Output: ```json { "success": true, "response": "The VPC component creates a Virtual Private Cloud...", "tool_calls": [ { "tool": "atmos_describe_component", "args": {"component": "vpc", "stack": "dev-us-east-1"}, "duration_ms": 245, "success": true, "result": {...} } ], "tokens": { "prompt": 1234, "completion": 567, "total": 1801, "cached": 890 }, "metadata": { "model": "claude-sonnet-4-6", "provider": "anthropic", "duration_ms": 2340, "timestamp": "2025-10-31T10:00:00Z", "tools_enabled": true, "stop_reason": "end_turn" } } ``` ### Markdown Format Formatted output with tool execution summaries: ```shell atmos ai exec "Analyze prod stacks" --format markdown ``` Output: ```markdown The production stacks are configured with: - High availability across 3 AZs - Auto-scaling enabled - Enhanced monitoring --- ## Tool Executions (3) 1. ✅ **atmos_list_stacks** (45ms) 2. ✅ **atmos_describe_component** (120ms) 3. ❌ **atmos_validate_component** (89ms) ``` ## Examples ### Basic Execution ```shell # Simple question atmos ai exec "What is Atmos?" # With JSON output atmos ai exec "List all stacks" --format json # Save to file atmos ai exec "Analyze VPC configuration" --output analysis.md --format markdown # Disable tools for faster execution atmos ai exec "Explain Atmos concepts" --no-tools ``` ### Stdin Piping ```shell # Pipe prompt from stdin echo "Validate stack configuration" | atmos ai exec --format json # From file cat prompt.txt | atmos ai exec --format json > result.json # Multi-line prompt atmos ai exec --format json < security.json if jq -e '.success == false' security.json; then echo "Security issues found" exit 1 fi # Generate deployment report atmos ai exec "Analyze prod environment" \ --output deployment-report.md \ --format markdown # Pre-deployment validation for stack in $(atmos list stacks); do echo "Validating $stack..." atmos ai exec "Validate stack $stack" --format json --no-tools done # GitHub Actions integration - name: AI Review run: | result=$(atmos ai exec "Review changes" --format json) echo "$result" | jq -r '.response' >> $GITHUB_STEP_SUMMARY ``` ### MCP Server Integration ```shell # Auto-routing selects the right server atmos ai exec "Check security posture in us-east-1" --format json # Specify servers directly atmos ai exec --mcp aws-iam "List all admin roles" --format json atmos ai exec --mcp aws-security,aws-iam "Audit our security posture" --format markdown # Environment variable ATMOS_AI_MCP=aws-billing atmos ai exec "Monthly cost report" --format json > costs.json ``` ### Multi-Turn Automation Use `--session` to maintain conversation context across multiple `exec` calls. The session name is an arbitrary string that groups related calls together -- the AI remembers all previous messages in the same session. ```shell # First call - establish context atmos ai exec "Plan deployment for staging" \ --session staging-deploy \ --format json > plan.json # Second call - AI remembers the plan from the first call atmos ai exec "What are the risks?" \ --session staging-deploy \ --format json > risks.json # Third call - builds on all previous context in this session atmos ai exec "Generate rollback procedure" \ --session staging-deploy \ --format json > rollback.json ``` ### Batch Processing ```shell #!/bin/bash # Process multiple stacks stacks=$(atmos list stacks) for stack in $stacks; do echo "Analyzing $stack..." atmos ai exec "Analyze stack $stack for cost optimization" \ --format json \ --output "reports/${stack}-cost-analysis.json" done # Aggregate results jq -s '.' reports/*.json > aggregated-report.json ``` ### Error Handling ```shell #!/bin/bash # Check exit code if ! atmos ai exec "Validate configuration" --format json; then echo "Validation failed with exit code $?" exit 1 fi # Parse JSON for errors result=$(atmos ai exec "Check stacks" --format json) if echo "$result" | jq -e '.success == false'; then error_type=$(echo "$result" | jq -r '.error.type') error_msg=$(echo "$result" | jq -r '.error.message') echo "Error ($error_type): $error_msg" exit 1 fi # Tool execution error handling result=$(atmos ai exec "Complex analysis" --format json) tool_errors=$(echo "$result" | jq '[.tool_calls[] | select(.success == false)]') if [ "$(echo "$tool_errors" | jq 'length')" -gt 0 ]; then echo "Tool execution errors detected:" echo "$tool_errors" | jq -r '.[] | "- \(.tool): \(.error)"' fi ``` ### Provider Override ```shell # Use specific provider for a task atmos ai exec "Complex reasoning task" --provider anthropic --format json # Fast provider for simple queries atmos ai exec "List stacks" --provider openai --format json --no-tools # Local provider for privacy atmos ai exec "Analyze sensitive config" --provider ollama --format json ``` ## Advanced Usage ### Output Parsing with jq ```bash # Extract just the response atmos ai exec "Question" --format json | jq -r '.response' # Check for specific tool execution atmos ai exec "Analyze" --format json | \ jq '.tool_calls[] | select(.tool == "atmos_describe_component")' # Get token usage atmos ai exec "Question" --format json | jq '.tokens' # Check execution time atmos ai exec "Question" --format json | jq '.metadata.duration_ms' ``` ### Integration Patterns **Email Reports:** ```bash atmos ai exec "Weekly infrastructure summary" --format markdown | \ mail -s "Weekly Report" team@example.com ``` **Slack Notifications:** ```bash result=$(atmos ai exec "Check production health" --format json) response=$(echo "$result" | jq -r '.response') curl -X POST $SLACK_WEBHOOK -d "{\"text\":\"$response\"}" ``` **Ticket Creation:** ```bash analysis=$(atmos ai exec "Identify issues in prod" --format json) if echo "$analysis" | jq -e '.success == true'; then issues=$(echo "$analysis" | jq -r '.response') # Create ticket with issues gh issue create --title "Infrastructure Issues" --body "$issues" fi ``` ## Comparison with Other Commands - **`atmos ai ask`** Non-interactive. Human-readable text output. Best for quick terminal questions. - **`atmos ai chat`** Interactive. Rich TUI with markdown. Best for extended conversations. - **`atmos ai exec`** Non-interactive. JSON/Text/Markdown output. Best for automation, scripting, CI/CD. ## Best Practices 1. **Use `--format json` for automation** - Provides structured, parseable output 2. **Set `--no-tools` for simple queries** - Faster execution when tools aren't needed 3. **Use `--session` for multi-turn workflows** - Maintains context across calls 4. **Check exit codes in scripts** - Handle errors appropriately 5. **Disable tool confirmation in CI/CD** - Set `require_confirmation: false` in config 6. **Use `--output` for file persistence** - Avoid shell redirection issues 7. **Parse JSON with jq** - Robust handling of structured output 8. **Set appropriate timeouts** - Adjust `timeout_seconds` based on your needs ## Related Commands --- ## atmos ai sessions Use this command to manage your AI chat sessions. List active sessions, clean old ones, export sessions for backup or sharing, and import sessions from checkpoint files. > ⚠️ Experimental **Configure AI** Learn how to configure AI providers, skills, tools, and sessions in your atmos.yaml. Configuration Reference[Read more](/cli/configuration/ai) ## Description The `atmos ai sessions` command provides session management capabilities for Atmos AI conversations. Sessions allow you to: - Save and resume conversations with full context - Share AI interactions with your team via checkpoint files - Back up important troubleshooting sessions - Clean up old sessions to manage storage - Import sessions from other team members or projects Each session maintains its own message history, allowing you to have multiple ongoing conversations about different aspects of your infrastructure. ## Subcommands ### `atmos ai sessions list` Lists all available AI chat sessions with their details. **Usage:** ```shell atmos ai sessions list ``` **Example:** ```text Name Created Updated Messages Model Provider eks-troubleshooting 2025-10-29 11:20:00 2025-10-29 16:45:00 18 gpt-4o openai vpc-migration 2025-10-30 14:30:00 2025-10-31 09:15:00 42 claude-sonnet-4-6 anthropic ``` Output uses the standard list format. When piped, it outputs a plain newline-separated list of session names. ### `atmos ai sessions clean` Remove old AI chat sessions based on retention policy. **Usage:** ```shell atmos ai sessions clean [flags] ``` **Flags:** - **`--older-than `** Delete sessions older than this duration. Supported units: `h` (hours), `d` (days), `w` (weeks), `m` (months). Default: `30d` **Examples:** ```shell # Delete sessions older than 30 days (default) atmos ai sessions clean # Delete sessions older than 7 days atmos ai sessions clean --older-than 7d # Delete sessions older than 24 hours atmos ai sessions clean --older-than 24h # Delete sessions older than 2 weeks atmos ai sessions clean --older-than 2w # Delete sessions older than 3 months atmos ai sessions clean --older-than 3m ``` ### `atmos ai sessions export` Export an AI chat session to a checkpoint file for backup or sharing. **Usage:** ```shell atmos ai sessions export [flags] ``` **Flags:** - **`-o, --output `** Output file path (required). Format is auto-detected from file extension. - **`-f, --format `** Explicit output format: `json` , `yaml` , or `markdown` . If not specified, format is detected from the file extension. - **`--context`** Include project context (ATMOS.md content, files accessed). Default: `false` - **`--metadata`** Include session metadata. Default: `true` **Export Formats:** - **JSON** - Machine-readable format, perfect for programmatic processing and archival - **YAML** - Human-readable and editable, good for version control - **Markdown** - Human-readable report format, ideal for documentation and sharing **Examples:** ```shell # Export to JSON (auto-detected from extension) atmos ai sessions export vpc-migration --output session.json # Export to YAML with project context atmos ai sessions export eks-troubleshooting --output backup.yaml --context # Export to Markdown for documentation atmos ai sessions export security-audit --output report.md --format markdown # Export with explicit JSON format atmos ai sessions export my-session --output backup.txt --format json ``` **Checkpoint File Contents:** A checkpoint file contains: - **Version** - Checkpoint format version for compatibility - **Exported metadata** - Export timestamp and user - **Session information** - Name, model, provider, timestamps, custom metadata - **Complete message history** - All user and assistant messages with timestamps - **Session statistics** - Message counts by role, token usage (if tracked) - **Project context** (optional) - ATMOS.md content, working directory, files accessed ### `atmos ai sessions import` Import an AI chat session from a checkpoint file. **Usage:** ```shell atmos ai sessions import [flags] ``` **Flags:** - **`-n, --name `** Name for the imported session. If not specified, uses the checkpoint's session name. - **`--overwrite`** Overwrite existing session with the same name. Default: `false` (will error if session exists) - **`--context`** Include project context from checkpoint. Default: `true` **Examples:** ```shell # Import session from JSON checkpoint atmos ai sessions import session.json # Import with custom name atmos ai sessions import backup.yaml --name restored-session # Import and overwrite existing session atmos ai sessions import session.json --overwrite # Import without project context atmos ai sessions import backup.json --context=false ``` **After Import:** Once imported, the session can be resumed with: ```shell atmos ai chat --session ``` ## Use Cases ### Team Collaboration Export and share sessions with your team to collaborate on complex infrastructure problems: ```shell # Developer exports troubleshooting session atmos ai sessions export db-migration-issue --output db-migration.json # Share file with team (git, Slack, email) # Team member imports and continues the conversation atmos ai sessions import db-migration.json --name db-migration-collab atmos ai chat --session db-migration-collab ``` ### Session Backup and Archival Back up important sessions for future reference: ```shell # Export critical sessions as markdown documentation atmos ai sessions export prod-incident-2025-10 --output docs/incidents/2025-10-incident.md # Export to YAML for version control atmos ai sessions export architecture-decisions --output .atmos/archives/architecture.yaml ``` ### Knowledge Transfer New team members can import sessions from experienced colleagues: ```shell # Senior engineer exports onboarding session atmos ai sessions export atmos-getting-started --output onboarding/atmos-intro.json --context # New team member imports and reviews atmos ai sessions import onboarding/atmos-intro.json --name my-onboarding atmos ai sessions export my-onboarding --output onboarding-review.md --format markdown ``` ### Cross-Project Learning Import sessions from other projects to reuse solutions: ```shell # Export session from project A cd /path/to/project-a atmos ai sessions export vpc-setup --output ~/vpc-setup.json --context # Import to project B with new name cd /path/to/project-b atmos ai sessions import ~/vpc-setup.json --name vpc-reference ``` ## Session Lifecycle 1. **Create** - Sessions are created automatically when you start a new chat: ```shell atmos ai chat --session my-new-session ``` 2. **List** - View all your sessions: ```shell atmos ai sessions list ``` 3. **Resume** - Continue a previous conversation: ```shell atmos ai chat --session my-new-session ``` 4. **Export** - Save a session for backup or sharing: ```shell atmos ai sessions export my-new-session --output backup.json ``` 5. **Import** - Restore or share a session: ```shell atmos ai sessions import backup.json ``` 6. **Clean** - Remove old sessions: ```shell atmos ai sessions clean --older-than 30d ``` ## Related Commands ## Tips - **Use descriptive session names** - Makes it easier to find and resume sessions later - **Export before major changes** - Create checkpoints before significant configuration changes - **Regular cleanup** - Use `atmos ai sessions clean` regularly to manage storage - **Markdown exports for docs** - Export important sessions as Markdown for team documentation - **YAML for version control** - YAML format works well with git and is human-readable - **JSON for automation** - Use JSON format for programmatic processing and archival --- ## atmos ai skill Use this command to manage community and custom AI skills. Install skills from GitHub repositories, list installed skills, and remove skills you no longer need. > ⚠️ Experimental **See also:** [Agent Skills Directory](/ai/skills) to browse every skill | [AI Skills Configuration](/cli/configuration/ai/skills) for configuring skills in `atmos.yaml` ## Description Skills are specialized AI assistants that provide expert knowledge for specific domains. They follow the [Agent Skills](https://agentskills.io) open standard and can be installed from GitHub repositories. The `atmos ai skill` command lets you: - Install official skills by name (offline) or community skills from GitHub - List the full catalog of available skills alongside what's installed - Update installed bundled skills to the latest version shipped with your `atmos` binary - Remove skills you no longer need :::tip Global `--skill` Flag You can use installed skills with any Atmos command via the global `--skill` flag (requires `--ai`). The skill's system prompt is sent to the AI provider for domain-specific analysis. ```shell atmos terraform plan vpc -s ue1-prod --ai --skill atmos-terraform ``` See [Global Flags](/cli/global-flags) for details. ::: ## What Are Agent Skills? Atmos ships official agent skills that give AI coding assistants deep, accurate knowledge of Atmos conventions, stack configuration, Terraform orchestration, authentication, validation, and more -- browse them all in the [Agent Skills Directory](/ai/skills). They live in the `agent-skills/` folder at the root of the Atmos repository and work across Claude Code, Gemini CLI, OpenAI Codex, Cursor, Windsurf, GitHub Copilot, and more. Each official skill is a self-contained package with: - **SKILL.md** -- The primary instruction file (under 500 lines) with YAML frontmatter metadata - **references/** -- Deeper reference files that the AI loads only when the task requires them Skills use a three-tier progressive disclosure pattern so AI context windows stay focused: 1. **Router** (`AGENTS.md`) -- A lightweight index that maps user tasks to the right skill 2. **Skill** (`SKILL.md`) -- Domain-specific instructions, patterns, and examples 3. **References** (`references/*.md`) -- Detailed specifications, schemas, and command references The AI loads the router first, identifies which skill applies, loads that skill, and only pulls in reference files when deep detail is needed. You do **not** invoke skills manually -- your AI tool activates the right skill automatically based on your question. For step-by-step client setup, see [Configure AI Assistants](/projects/setup-editor/ai-assistants). ### Official SKILL.md Format **File:** `SKILL.md` ```markdown --- name: atmos-stacks description: "Stack configuration: imports, inheritance, deep merging, locals, vars, settings, metadata, overrides, atmos.yaml setup" metadata: copyright: Copyright Cloud Posse, LLC 2026 version: "1.0.0" category: core-config references: - references/import-patterns.md - references/inheritance-deep-merge.md --- # Atmos Stacks Instructions for the AI assistant... ``` - **`name`** Unique identifier for the skill (must match the directory name). - **`description`** Human-readable summary of what the skill teaches. - **`metadata.category`** Groups the skill in the [Agent Skills Directory](/ai/skills) (e.g. `orchestrators` , `security` , `ci-automation` ). - **`references`** Optional list of deeper reference files the AI should load when more detail is needed. Skills build on two open standards: [AGENTS.md](https://agents.md/) for project-level AI instructions, and [Agent Skills](https://agentskills.io/specification) (`SKILL.md`) for packaging AI capabilities with instructions, references, and assets. ### Contributing an Official Skill Official skills are maintained in the Atmos repository under `agent-skills/`. To contribute one: 1. Follow the existing folder structure: `agent-skills/skills//SKILL.md` 2. Keep the primary `SKILL.md` under 500 lines for optimal context usage 3. Place detailed reference material in `references/` subdirectories 4. Update `AGENTS.md` to include your skill in the routing table 5. Use YAML frontmatter with `name`, `description`, `metadata` (containing `copyright`, `version`, and `category`), and optionally `references` fields New skills appear automatically in the [Agent Skills Directory](/ai/skills) -- no separate doc update needed. See the [Atmos Contributing Guide](/community) for general contribution guidelines. ## Usage ```shell atmos ai skill [flags] ``` ## Subcommands ### `atmos ai skill install` Install a skill by its bundled name (offline) or from a GitHub repository. The official Atmos skills are embedded in the binary, so installing one by its bare name (for example `atmos-terraform`) works fully offline — no network or Git clone required. Run `atmos ai skill list` to see every available skill. You can also install any skill from a GitHub repository. Omit `` entirely to install every bundled skill at once. By default, the skill is also auto-distributed into any detected AI client's project-local skill directory (`.github/skills/` for VS Code/Copilot, `.claude/skills/` for Claude Code, `.gemini/skills/` for Gemini), so it works with zero extra flags. Use `--client`/`--all-clients` to control which clients receive a copy, or `--path` to take full manual control of the install location (this skips auto-distribution). Use `--scope user` (or `--global`) to distribute into each client's personal, user-level skill directory instead (`~/.claude/skills/`, `~/.copilot/skills/` for VS Code/Copilot, `~/.gemini/skills/`), so the skill is available across every project rather than just this one. When neither `--scope` nor `--global` is given and the command is running in an interactive terminal, Atmos prompts you to choose project or user scope; `--yes`, a non-TTY session, or CI skips the prompt and defaults to `project`. If a client's target directory already exists as a symbolic link (for example this repo's own `.claude/skills/` entries, which intentionally point into `agent-skills/skills/` for contributor auto-discovery), that client is skipped with a warning instead of writing through the symlink. **Usage:** ```shell atmos ai skill install [source] [flags] ``` **Flags:** - **`--force`** Reinstall if the skill is already installed. - **`-y, --yes`** Skip the confirmation prompt. - **`--path`** Override the skill install directory (default: `~/.atmos/skills` ). Relative paths resolve against the current working directory, e.g. `--path .github/skills` for VS Code/Copilot auto-discovery. Setting `--path` skips auto-distribution to AI clients. - **`-c, --client`** AI client to distribute the skill to (repeatable): `claude-code` , `vscode` , `gemini` . Defaults to auto-detected clients. - **`--all-clients`** Distribute the skill to every supported AI client. - **`--scope`** Distribution scope: `project` (writes into this repo's client directories, default) or `user` (writes into each client's personal, user-level directory instead). Wins over `--global` if both are set. When omitted (along with `--global` ) in an interactive terminal, Atmos prompts you to choose; non-interactive runs ( `--yes` , no TTY, or CI) fall back to `project` . - **`-g, --global`** Alias for `--scope user` . **Examples:** ```shell # Install an official skill by name (offline) atmos ai skill install atmos-terraform # Install every bundled skill at once (offline) atmos ai skill install # Install a skill from GitHub atmos ai skill install github.com/user/skill-name # Install a specific version atmos ai skill install github.com/user/skill-name@v1.2.3 # Force reinstall atmos ai skill install github.com/user/skill-name --force # Install without confirmation atmos ai skill install github.com/user/skill-name --yes # Install to a custom directory (skips auto-distribution) atmos ai skill install atmos-terraform --path .github/skills # Distribute to a specific AI client atmos ai skill install atmos-terraform --client vscode # Distribute to every supported AI client atmos ai skill install atmos-terraform --all-clients # Distribute into each client's personal, user-level directory atmos ai skill install atmos-terraform --scope user ``` ### `atmos ai skill list` List Atmos skills — both the official skills bundled with Atmos and any community skills installed on this system. A filled dot (`●`) marks an installed skill; a hollow dot (`○`) marks one that is available to install. The official catalog is embedded in the binary, so listing works offline. **Usage:** ```shell atmos ai skill list [flags] ``` **Flags:** - **`-d, --detailed`** Show detailed information for each skill, including source, version, install date, and location. - **`--installed`** Show only installed skills, hiding the rest of the available catalog. **Examples:** ```shell # List all skills (available and installed) atmos ai skill list # Show only installed skills atmos ai skill list --installed # Show detailed information atmos ai skill list --detailed ``` ### `atmos ai skill update` Update installed bundled skills to their latest catalog version. Bundled skills are static copies made at install time — upgrading the `atmos` binary alone does not refresh a skill you already installed, even if that release bundles newer skill content. `update` compares each installed bundled skill's recorded version against the catalog embedded in the running binary and reinstalls only the ones that are actually outdated; skills already at the current version are left untouched. Omit `` entirely to update every installed bundled skill that has a newer version available (a single confirmation, not one per skill). Skills installed from a GitHub repository are not supported yet — there's no cheap way to check whether a git-sourced skill's upstream has moved without re-fetching it; run `atmos ai skill install --force` to refresh one of those manually. An outdated skill is reinstalled the same way `atmos ai skill install --force` would install it, so the same client-distribution and scope flags apply. **Usage:** ```shell atmos ai skill update [name] [flags] ``` **Flags:** - **`-y, --yes`** Skip the confirmation prompt. - **`--path`** Override the skill install directory (default: `~/.atmos/skills` ). Relative paths resolve against the current working directory. - **`-c, --client`** AI client to distribute the updated skill to (repeatable): `claude-code` , `vscode` , `gemini` . Defaults to auto-detected clients. - **`--all-clients`** Distribute the updated skill to every supported AI client. - **`--scope`** Distribution scope: `project` (default) or `user` . Wins over `--global` if both are set. - **`-g, --global`** Alias for `--scope user` . **Examples:** ```shell # Update a single bundled skill if a newer version is available atmos ai skill update atmos-terraform # Update every installed bundled skill that has an update available atmos ai skill update # Skip the confirmation prompt atmos ai skill update --yes # Update and redistribute to a specific AI client atmos ai skill update atmos-terraform --client vscode ``` ### `atmos ai skill uninstall` Remove an installed skill. Any copies auto-distributed to AI clients during install are removed as well. Omit `` entirely to uninstall every installed skill at once. Use `--scope user` (or `--global`) if the skill was installed with `--scope user`, so cleanup targets each client's personal, user-level skill directory instead of the project one. When neither `--scope` nor `--global` is given and the command is running in an interactive terminal, Atmos prompts you to choose project or user scope; `--force`, a non-TTY session, or CI skips the prompt and defaults to `project`. If a client's copy is actually a symbolic link (for example this repo's own `.claude/skills/` entries), it is left in place with a warning rather than being deleted. **Usage:** ```shell atmos ai skill uninstall [name] [flags] ``` **Flags:** - **`-f, --force`** Skip the confirmation prompt. - **`-c, --client`** AI client to remove the skill from (repeatable): `claude-code` , `vscode` , `gemini` . Defaults to auto-detected clients. - **`--all-clients`** Remove the skill from every supported AI client. - **`--scope`** Distribution scope: `project` (default) or `user` . Use `user` to clean up a skill that was installed with `--scope user` . Wins over `--global` if both are set. When omitted (along with `--global` ) in an interactive terminal, Atmos prompts you to choose; non-interactive runs ( `--force` , no TTY, or CI) fall back to `project` . - **`-g, --global`** Alias for `--scope user` . **Examples:** ```shell # Uninstall a skill atmos ai skill uninstall skill-name # Uninstall every installed skill at once atmos ai skill uninstall # Uninstall without confirmation atmos ai skill uninstall skill-name --force # Remove the skill from a specific AI client only atmos ai skill uninstall skill-name --client vscode # Remove the skill from every supported AI client atmos ai skill uninstall skill-name --all-clients # Remove a skill that was installed with --scope user atmos ai skill uninstall skill-name --scope user ``` ## Creating and Publishing Your Own Skill Skills installed via `atmos ai skill install ` follow the same [Agent Skills open standard](https://agentskills.io) format -- a single SKILL.md file with YAML frontmatter and a Markdown body containing the system prompt. ``` your-skill-repo/ ├── SKILL.md # Skill definition (required) ├── README.md # Documentation (recommended) ├── examples/ # Usage examples (optional) └── LICENSE # License file (recommended) ``` The frontmatter declares metadata and tool access. The Markdown body is the AI system prompt. **File:** `SKILL.md (abbreviated)` ``` --- name: terraform-expert display_name: "Terraform Expert" version: 1.0.0 author: Cloud Posse description: > Specialized AI skill for Terraform component development, debugging, and best practices. category: refactor atmos: min_version: 1.50.0 max_version: "" tools: allowed: - atmos_describe_component - read_component_file - read_file - edit_file - execute_bash restricted: - edit_file - execute_bash capabilities: - terraform-development - component-architecture dependencies: - terraform repository: https://github.com/yourorg/atmos-skill-terraform --- # Skill: Terraform Expert ## Role You are a specialized AI skill for Terraform component development... ## Instructions [Detailed instructions for the AI skill go here] ## Restrictions - Always confirm before executing destructive commands - Never commit secrets or credentials to code ``` - **`name`** Unique skill identifier in kebab-case. - **`display_name`** User-facing display name. - **`version`** Semantic version (e.g., `1.2.3` ). - **`author`** Author name or organization. - **`description`** Brief description of skill purpose. - **`category`** One of: `general` , `analysis` , `refactor` , `security` , `validation` , `optimization` . - **`atmos.min_version`** Minimum compatible Atmos version. - **`tools.allowed`** Tools the skill can use. Tools in `restricted` require user confirmation each time. - **`repository`** GitHub repository URL. ### Publishing Your Skill Create a GitHub repository with a valid SKILL.md, tag a release, and share it: ```shell mkdir atmos-skill-terraform && cd atmos-skill-terraform git init # Create SKILL.md with frontmatter and prompt (see format above) git add . && git commit -m "Initial skill release" git remote add origin git@github.com:yourorg/atmos-skill-terraform.git git push -u origin main git tag v1.0.0 && git push origin v1.0.0 ``` Share your skill in [Atmos Discussions](https://github.com/cloudposse/atmos/discussions) and add the `atmos-skill` topic to your repository. :::tip Naming Convention - **Repository**: prefix with `atmos-skill-`, use kebab-case (e.g., `atmos-skill-cost-optimizer`) - **Skill name**: no prefix, kebab-case (e.g., `cost-optimizer`) - **Display name**: title case (e.g., "Cost Optimizer") ::: ## Tool Access and Security Skills declare which tools they need. Review the `tools.allowed` section before installing any skill. :::danger Review Before Installing **Always review skill source code on GitHub before installing skills that request:** - File write access (`edit_file`, `write_stack_file`, `write_component_file`) - Command execution (`execute_bash_command`, `execute_atmos_command`) ::: Use the `restricted` field to require user confirmation for sensitive operations. A tool listed in both `allowed` and `restricted` means the skill can use it, but the user must approve each invocation. ## Troubleshooting ### Skill Not Found After Installation Skill may be disabled or the registry is corrupted. Check with `atmos ai skill list` and inspect `~/.atmos/skills/registry.json`. ### Version Compatibility Error ``` Error: skill requires Atmos >= 1.50.0, but current version is 1.48.0 ``` Upgrade Atmos (`brew upgrade atmos`) or install an older skill version (`atmos ai skill install user/skill@v0.9.0`). ### Invalid Metadata Error The skill's SKILL.md has malformed frontmatter. Report the issue to the skill author or try a different version tag. ### Registry Corruption ```shell # Backup and reset registry cp ~/.atmos/skills/registry.json ~/.atmos/skills/registry.json.backup rm ~/.atmos/skills/registry.json # Reinstall skills atmos ai skill install cloudposse/atmos ``` ## Related Commands --- ## atmos ai Atmos AI is a built-in assistant that understands your stacks, components, and configuration. Ask questions, debug issues, or automate infrastructure tasks directly from the terminal. It supports API providers (Anthropic, OpenAI, Gemini, Ollama, AWS Bedrock, Azure OpenAI) and CLI providers (Claude Code, OpenAI Codex, Gemini CLI) that reuse your existing subscription. Choose from [21+ specialized skills](/cli/configuration/ai/skills) and switch between them with `Ctrl+A`. > ⚠️ Experimental **Configure AI** Learn how to configure AI providers, skills, tools, and sessions in your atmos.yaml. Configuration Reference[Read more](/cli/configuration/ai) ## Global `--ai` Flag You can also add `--ai` to **any** Atmos command to get AI-powered analysis of the output — no need to use `atmos ai` subcommands. Pair with `--skill` for domain-specific expertise (the `--skill` flag requires `--ai`). Multiple skills can be combined with commas or repeated flags. ```shell # AI analyzes the terraform plan output atmos terraform plan vpc -s ue1-prod --ai # AI uses Terraform expertise for deeper analysis atmos terraform plan vpc -s ue1-prod --ai --skill atmos-terraform # Combine multiple skills (comma-separated) atmos terraform plan vpc -s ue1-prod --ai --skill atmos-terraform,atmos-stacks # Combine multiple skills (repeated flag) atmos terraform plan vpc -s ue1-prod --ai --skill atmos-terraform --skill atmos-stacks # Enable via environment variables ATMOS_AI=true ATMOS_SKILL=atmos-terraform,atmos-stacks atmos terraform plan vpc -s ue1-prod ``` See [Global Flags](/cli/global-flags) for details. ## Subcommands --- ## atmos ansible playbook Use this command to run an Ansible playbook for an Atmos component in a stack, applying configuration changes to target hosts. ## Usage Execute the `ansible playbook` command like this: ```shell atmos ansible playbook --stack [flags] -- [ansible-options] ``` :::tip For more details on the `ansible-playbook` command and options, refer to [Ansible Playbook Documentation](https://docs.ansible.com/ansible/latest/cli/ansible-playbook.html). ::: ## Arguments - **`component` (required)** Atmos Ansible component name or path. ## Flags - **`--stack` (alias `-s`)(required)** Atmos stack. - **`--playbook` (alias `-p`)(optional)** Ansible playbook file to execute. If not specified, uses the value from `settings.ansible.playbook` in the stack manifest. The command line flag takes precedence over the stack manifest setting. - **`--inventory` (alias `-i`)(optional)** Ansible inventory source. Can be a file, directory, or dynamic inventory script. Can also be specified via `settings.ansible.inventory` in the stack manifest. The command line flag takes precedence. - **`--dry-run`(optional)** Perform a dry run without executing the playbook. Shows what commands would be run. ## Examples ### Basic Usage ```shell # Run playbook with settings from stack manifest atmos ansible playbook webserver --stack prod # Specify playbook explicitly atmos ansible playbook webserver -s prod --playbook site.yml # Specify both playbook and inventory atmos ansible playbook webserver -s prod -p deploy.yml -i inventory/production ``` ### Passing Ansible Options Any arguments after `--` are passed directly to `ansible-playbook`: ```shell # Check mode (dry run at Ansible level) atmos ansible playbook webserver -s prod -- --check # Verbose output atmos ansible playbook webserver -s prod -- -vvv # Limit to specific hosts atmos ansible playbook webserver -s prod -- --limit "web01,web02" # Run specific tags atmos ansible playbook webserver -s prod -- --tags "deploy" # Skip specific tags atmos ansible playbook webserver -s prod -- --skip-tags "slow" # Run with extra variables atmos ansible playbook webserver -s prod -- --extra-vars "version=1.2.3" ``` ### Stack Configuration Example Configure your Ansible component in the stack manifest: When you run `atmos ansible playbook hello-world -s dev`, Atmos: 1. Generates a variables file with the `vars` section 2. Passes it to `ansible-playbook` via `--extra-vars @` 3. Uses the playbook and inventory from settings (or command line flags) 4. Executes the playbook in the component directory ## Variable Handling Atmos automatically generates a YAML variables file containing all variables from the component's `vars` section. This file is passed to Ansible using `--extra-vars @`. The generated file follows the naming convention: `-.ansible.vars.yaml` For example, for a component `webserver` in stack `prod-us-east-1`, the file would be: `prod-us-east-1-webserver.ansible.vars.yaml` The file is automatically cleaned up after the playbook execution completes. ## Example --- ## atmos ansible Use these subcommands to interact with [Ansible](https://docs.ansible.com/ansible/latest/index.html) for automating infrastructure configuration, application deployment, and orchestration. ## Usage ```shell # For playbook subcommand (requires component and stack) atmos ansible playbook --stack [atmos-flags] -- [ansible-options] # For version subcommand (no arguments required) atmos ansible version ``` :::tip For more details on Ansible commands and options, refer to the [Ansible Documentation](https://docs.ansible.com/ansible/latest/cli/index.html). ::: ### Path-Based Component Resolution Atmos supports using filesystem paths instead of component names for convenience. This allows you to navigate to a component directory and use `.` to reference it: ```shell # Navigate to component directory cd components/ansible/webserver # Use . to reference current directory atmos ansible playbook . -s prod ``` This automatically resolves the path to the component name configured in your stack, eliminating the need to remember exact component names. **Supported path formats:** - `.` - Current directory - `./component` - Relative path from current directory - `../other-component` - Relative path to sibling directory - `/absolute/path/to/component` - Absolute path **Requirements:** - Must be inside a component directory under the configured base path - Must specify `--stack` flag - Component must exist in the specified stack configuration - **The component path must resolve to a unique component name** - If multiple components in the stack reference the same component path, you must use the unique component name instead of the path ## Atmos Flags - **`--stack` (alias `-s`)** Atmos stack. - **`--playbook` (alias `-p`)(optional)** Ansible playbook file to execute. Defaults to the playbook specified in `settings.ansible.playbook` in the stack manifest. The command line flag takes precedence over the stack manifest setting. - **`--inventory` (alias `-i`)(optional)** Ansible inventory source. Can be a file path, directory, or dynamic inventory script. Can also be specified via `settings.ansible.inventory` in the stack manifest. The command line flag takes precedence. - **`--dry-run`(optional)** Perform a dry run without making actual changes. Displays the commands that would be executed. ## Examples ### Component Name Examples ```shell atmos ansible version atmos ansible playbook webserver --stack prod atmos ansible playbook webserver -s prod --playbook site.yml atmos ansible playbook webserver -s nonprod -p deploy.yml -i hosts.ini ``` ### Path-Based Examples ```shell # Navigate to component directory and use current directory cd components/ansible/webserver atmos ansible playbook . -s prod # Use relative path from components/ansible directory cd components/ansible atmos ansible playbook ./webserver -s prod # Use from project root with relative path atmos ansible playbook components/ansible/webserver -s prod # Combine with other flags cd components/ansible/webserver atmos ansible playbook . -s prod --playbook deploy.yml --inventory production ``` ### Passing Additional Ansible Options Any flags after `--` are passed directly to the underlying Ansible command: ```shell # Run playbook in check mode (dry run) atmos ansible playbook webserver -s prod -- --check # Run with verbose output atmos ansible playbook webserver -s prod -- -vvv # Limit to specific hosts atmos ansible playbook webserver -s prod -- --limit web01 # Run with specific tags atmos ansible playbook webserver -s prod -- --tags "deploy,config" ``` ## Arguments - **`atmos-component` (required for playbook)** Atmos component name or filesystem path. Supports both: Component names: webserver, database/postgres Filesystem paths: . (current directory), ./webserver, components/ansible/webserver When using paths, Atmos automatically resolves the path to the component name based on your stack configuration. See Path-Based Component Resolution above. ## Stack Configuration Configure Ansible components in your stack manifests: ```yaml components: ansible: webserver: vars: app_name: myapp app_port: 8080 settings: ansible: playbook: site.yml inventory: inventory/production ``` Variables defined in `vars` are passed to Ansible as extra variables via a generated YAML file. ## Subcommands --- ## atmos ansible version Use this command to display the currently installed Ansible version and configuration information. ## Usage Execute the `ansible version` command like this: ```shell atmos ansible version ``` This command runs `ansible --version` and displays: - Ansible version - Configuration file location - Configured module search path - Python version and location ## Arguments This command takes no arguments. ## Flags - **`--help`** Display help for the command. ## Examples ```shell atmos ansible version ``` Output: ``` ansible [core 2.15.0] config file = /etc/ansible/ansible.cfg configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python3/dist-packages/ansible ansible collection location = /home/user/.ansible/collections:/usr/share/ansible/collections executable location = /usr/bin/ansible python version = 3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0] jinja version = 3.1.2 libyaml = True ``` --- ## atmos atlantis generate repo-config Use this command to generate a repository configuration (`atlantis.yaml`) for Atlantis. ```shell atmos atlantis generate repo-config [options] ``` :::tip Run `atmos atlantis generate repo-config --help` to see all the available options ::: ## Examples ```shell atmos atlantis generate repo-config atmos atlantis generate repo-config --output-path /dev/stdout atmos atlantis generate repo-config --config-template config-1 --project-template project-1 atmos atlantis generate repo-config --config-template config-1 --project-template project-1 --stacks atmos atlantis generate repo-config --config-template config-1 --project-template project-1 --components atmos atlantis generate repo-config --config-template config-1 --project-template project-1 --stacks --components atmos atlantis generate repo-config --affected-only=true atmos atlantis generate repo-config --affected-only=true --output-path /dev/stdout atmos atlantis generate repo-config --affected-only=true --verbose=true atmos atlantis generate repo-config --affected-only=true --output-path /dev/stdout --verbose=true atmos atlantis generate repo-config --affected-only=true --repo-path atmos atlantis generate repo-config --affected-only=true --ref refs/heads/main atmos atlantis generate repo-config --affected-only=true --ref refs/tags/v1.1.0 atmos atlantis generate repo-config --affected-only=true --sha 3a5eafeab90426bd82bf5899896b28cc0bab3073 atmos atlantis generate repo-config --affected-only=true --ref refs/tags/v1.2.0 --sha 3a5eafeab90426bd82bf5899896b28cc0bab3073 atmos atlantis generate repo-config --affected-only=true --ssh-key atmos atlantis generate repo-config --affected-only=true --ssh-key --ssh-key-password atmos atlantis generate repo-config --affected-only=true --clone-target-ref=true ``` ## Flags - **`--config-template` (optional)** Atlantis config template name. - **`--project-template` (optional)** Atlantis project template name. - **`--output-path` (optional)** Output path to write `atlantis.yaml` file. - **`--stacks` (optional)** Generate Atlantis projects for the specified stacks only (comma-separated values). - **`--components` (optional)** Generate Atlantis projects for the specified components only (comma-separated values). - **`--affected-only` (optional)** Generate Atlantis projects only for the Atmos components changed between two Git commits. - **`--ref` (optional)** [Git Reference](https://git-scm.com/book/en/v2/Git-Internals-Git-References) with which to compare the current working branch. - **`--sha` (optional)** Git commit SHA with which to compare the current working branch. - **`--ssh-key` (optional)** Path to PEM-encoded private key to clone private repos using SSH. - **`--ssh-key-password` (optional)** Encryption password for the PEM-encoded private key if the key contains a password-encrypted PEM block. - **`--repo-path` (optional)** Path to the already cloned target repository with which to compare the current branch. Conflicts with `--ref` , `--sha` , `--ssh-key` and `--ssh-key-password` . - **`--verbose` (optional)** Print more detailed output when cloning and checking out the target Git repository and processing the result. - **`--clone-target-ref` (optional)** Clone the target reference with which to compare the current branch. `atmos atlantis generate repo-config --affected-only=true --clone-target-ref=true` The flag is only used when `--affected-only=true` If set to `false` (default), the target reference will be checked out instead This requires that the target reference is already cloned by Git, and the information about it exists in the `.git` directory. :::info Refer to [Atlantis Integration](/cli/configuration/integrations/atlantis) for more details on the Atlantis integration in Atmos ::: --- ## atmos atlantis Use these subcommands to execute commands that generate Atlantis configurations. **Configure Atlantis Integration** Learn how to configure Atlantis for Terraform Pull Request Automation in your atmos.yaml. Integration Reference[Read more](/cli/configuration/integrations/atlantis) ## Usage ## Subcommands --- ## atmos auth console Use this command to quickly access your cloud provider's web console (AWS, Azure, GCP) using your authenticated Atmos identity credentials, eliminating the need to manually copy credentials or log in separately. _\[Video: atmos auth console]_ ## Usage ```shell atmos auth console [flags] ``` This command generates a temporary, secure sign-in URL using your authenticated identity's credentials and automatically opens it in your default browser. The URL is valid for a limited time and provides seamless access to the cloud provider's web console. ## Examples ### Basic Usage ```shell # Open console with default identity atmos auth console # Interactively select identity atmos auth console --identity # Open console with specific identity atmos auth console --identity prod-admin # Use short form of identity flag atmos auth console -i prod-admin # Interactive selection with short form atmos auth console -i ``` ### AWS-Specific Examples #### Using Service Aliases (Shorthand) Atmos supports convenient aliases for common AWS services - just use the service name: ```shell # Open AWS S3 console (shorthand) atmos auth console --destination s3 ``` ```shell # Open AWS EC2 console atmos auth console --destination ec2 ``` ```shell # Open AWS Lambda console atmos auth console --destination lambda ``` ```shell # Open AWS CloudFormation console atmos auth console --destination cloudformation ``` ```shell # Open AWS RDS console atmos auth console --destination rds ``` ```shell # Open AWS DynamoDB console atmos auth console --destination dynamodb ``` **Available Aliases:** Atmos supports 100+ AWS service aliases including: `s3`, `ec2`, `lambda`, `dynamodb`, `rds`, `vpc`, `iam`, `cloudformation`, `cloudwatch`, `eks`, `ecs`, `sagemaker`, `bedrock`, and many more. Aliases are case-insensitive. #### Using Full URLs You can also use complete AWS console URLs for specific pages: ```shell # Open AWS S3 console (full URL) atmos auth console --destination https://console.aws.amazon.com/s3 ``` ```shell # Open AWS EC2 console with longer session atmos auth console --destination https://console.aws.amazon.com/ec2 --duration 4h ``` #### Other Options ```shell # Custom issuer name (appears in AWS console URL) atmos auth console --issuer my-organization ``` ### Scripting and Automation ```shell # Print URL to stdout without opening browser atmos auth console --print-only ``` ```shell # Copy URL to clipboard (macOS) atmos auth console --print-only | pbcopy ``` ```shell # Copy URL to clipboard (Linux) atmos auth console --print-only | xclip ``` ```shell # Generate URL but don't auto-open browser atmos auth console --no-open ``` ### Advanced Examples ```shell # Combine options for specific use case (using alias) atmos auth console \ --identity prod-admin \ --destination cloudformation \ --duration 2h \ --issuer devops-team ``` ```shell # Access machine learning services atmos auth console --destination sagemaker atmos auth console --destination bedrock ``` ```shell # Security and compliance services atmos auth console --destination guardduty atmos auth console --destination securityhub atmos auth console --destination iam ``` ## Flags - **`--identity` / `-i`** Specify the Atmos identity to use for console access. This flag has three modes: - **With value** (`--identity admin`): Use the specified identity - **Without value** (`--identity`): Show interactive selector to choose identity - **Omitted**: Use the default identity configured in `atmos.yaml`, or prompt if no default is set **Environment variables:** `ATMOS_IDENTITY` or `IDENTITY` (checked in that order) - **`--destination`** The specific console page or service to navigate to after authentication. Provider-specific URL format. **AWS Examples:** - `https://console.aws.amazon.com/s3` - S3 console - `https://console.aws.amazon.com/ec2` - EC2 console - `https://console.aws.amazon.com/cloudformation` - CloudFormation console **Default:** Provider's main console page - **`--duration`** The requested duration for the console session. Providers may enforce maximum limits. **AWS:** Maximum 12 hours **Default:** 1 hour, or the provider's `console.session_duration` configuration **Format:** Go duration (e.g., `1h`, `2h30m`, `12h`) **Example:** `--duration 4h` **Note:** This flag overrides the provider's `console.session_duration` setting when specified. - **`--issuer`** An identifier that appears in the console URL (AWS only). Useful for tracking or organizational purposes. **Default:** `atmos` **Example:** `--issuer my-team` - **`--print-only`** Print the console URL to stdout instead of opening a browser. Useful for scripting or when you want to manually control when/how the URL is opened. **Example:** `atmos auth console --print-only | pbcopy` - **`--no-open`** Generate the console URL and display it, but don't automatically open the browser. The URL is still shown in the terminal output. **Example:** `atmos auth console --no-open` - **`--isolated`** Open the console in an isolated browser session. Each identity gets its own Chrome browser profile, allowing multiple console sessions to run simultaneously without logout conflicts. Requires Google Chrome or Chromium. If Chrome is not found, falls back to the default browser with a warning. **Default:** `false`, or the value of `auth.console.isolated_sessions` in `atmos.yaml` **Example:** `atmos auth console --identity prod-admin --isolated` ## How It Works ### AWS Console Access For AWS identities, Atmos uses the [AWS Federation Endpoint](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html) to generate temporary console sign-in URLs: 1. **Authentication**: Atmos authenticates using your configured identity (AWS IAM Identity Center, SAML, or an IAM user) to obtain temporary credentials with a session token. 2. **Federation Token**: The temporary credentials are sent to the AWS federation endpoint (`https://signin.aws.amazon.com/federation`) to request a signin token. 3. **Console URL**: Atmos constructs a special URL containing the signin token that automatically logs you into the AWS console. 4. **Browser Launch**: The URL is opened in your default browser, providing instant access to the AWS console. :::tip Security Note Console signin tokens are valid for 15 minutes and should be treated as sensitive. Never share console URLs or paste them in logs or chat applications. ::: ### GCP (Coming Soon) Support for Google Cloud Console is planned for future releases. The command structure will remain the same across all providers. ## Provider Support | Provider | Status | Notes | |----------|--------|-------| | AWS (IAM Identity Center) | ✅ Supported | Full support with federation endpoint | | AWS (SAML) | ✅ Supported | Full support with federation endpoint | | AWS (IAM user) | ✅ Supported | Atmos obtains temporary STS session credentials before console federation | | Azure | ✅ Supported | Opens Azure Portal with configured subscription | | GCP | 🚧 Planned | Coming in future release | ## Common Use Cases ### Quick Access During Incidents ```shell # Rapidly access production AWS console during an incident atmos auth console --identity prod-oncall --duration 2h ``` ### Multi-Account Workflows ```shell # Open multiple account consoles simultaneously with isolated sessions atmos auth console --identity dev-account --isolated atmos auth console --identity staging-account --isolated atmos auth console --identity prod-account --isolated ``` ### CI/CD Integration ```shell # Generate console URL in CI/CD for manual verification CONSOLE_URL=$(atmos auth console --print-only) echo "Deployment complete. Verify at: $CONSOLE_URL" ``` ### Team Collaboration ```shell # Use custom issuer to track which team opened the console atmos auth console --issuer platform-team --duration 4h ``` ## Troubleshooting ### "session token required for console access" **Problem**: You're using permanent IAM user credentials instead of temporary credentials. **Solution**: AWS console access requires temporary credentials with a session token. Atmos obtains these automatically for `aws/user` identities; other identities should use AWS IAM Identity Center or SAML. ### "Failed to open browser automatically" **Problem**: The system couldn't automatically launch your default browser. **Solution**: Use `--print-only` to get the URL and manually paste it into your browser, or copy it to your clipboard: ```shell atmos auth console --print-only | pbcopy # macOS atmos auth console --print-only | xclip # Linux ``` ### "provider does not support web console access" **Problem**: The authenticated identity's provider doesn't support console access yet. **Solution**: Check the Provider Support table above. Azure is supported; GCP support is planned for a future release. ## Configuration ### Global Console Settings Configure console behavior for all identities under `auth.console`: ```yaml auth: console: isolated_sessions: true # Open each identity in its own browser session ``` ### Provider Console Settings Configure per-provider console settings like session duration: ```yaml auth: providers: aws-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://mycompany.awsapps.com/start # Session duration for programmatic credentials (auth shell, auth env) session: duration: 1h # Console session duration for web browser access (auth console) console: session_duration: 12h # Maximum for AWS ``` ### Configuration Options - **`auth.console.isolated_sessions`** Enable isolated browser sessions for all identities. Each identity opens in its own Chrome browser context, allowing multiple console sessions to run simultaneously without logout conflicts. Requires Google Chrome or Chromium. Falls back to the default browser if Chrome is not found. Session data is stored under the platform-specific XDG data directory (e.g., `~/.local/share/atmos/console/sessions/` on Linux, `~/Library/Application Support/atmos/console/sessions/` on macOS, `%APPDATA%\atmos\console\sessions\` on Windows) and is keyed by realm and identity name, so reopening the same identity reuses its browser session. **Type:** Boolean **Default:** `false` **Override:** Use the `--isolated` flag to override per command - **`console.session_duration`** Default session duration for web console access when using this provider. Configured per provider. **Format:** Go duration string (e.g., `1h`, `4h`, `12h`) **AWS Maximum:** 12 hours **Default:** 1 hour if not specified **Override:** Use the `--duration` flag to override this setting per command ### Session Duration vs Signin Token Expiration It's important to understand the difference between two types of timeouts: 1. **Signin Token Expiration (15 minutes, AWS-enforced)**: After generating a console URL, you have 15 minutes to click the link before it expires. This cannot be configured. 2. **Console Session Duration (configurable up to 12 hours)**: Once you're logged into the console, this controls how long you stay authenticated before being logged out. This is configured via `console.session_duration` or the `--duration` flag. ## Related Commands - [`atmos auth login`](/cli/commands/auth/login) - Authenticate with a configured identity - [`atmos auth whoami`](/cli/commands/auth/whoami) - Display current authentication info - [`atmos auth env`](/cli/commands/auth/env) - Export credentials as environment variables ## See Also - [AWS Console Federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html) --- ## atmos auth env Quickly generate temporary cloud credentials as environment variables so you can run tools like Terraform, AWS CLI, or SDKs without manually copying and pasting access keys. This makes it seamless to switch between identities, integrate with scripts, and keep your sessions secure and short-lived. _\[Video: atmos auth env]_ ## Usage ```shell atmos auth env [--identity ] [--format bash|json|dotenv|github] [--output-file ] [--login] ``` ## How It Works `atmos auth env` outputs environment variable exports for a specific cloud identity (e.g., `AWS_PROFILE`, `AWS_CONFIG_FILE`, `AWS_SHARED_CREDENTIALS_FILE`). You use this output to configure your shell environment by evaluating it with `eval $(atmos auth env)`. **Important:** This command **outputs environment variables** and does **not perform authentication by default**. It generates export statements pointing to credential files that are populated during login events. Use the `--login` flag to trigger authentication if credentials are missing or expired. Since commands cannot modify their parent shell's environment, you must use `eval` to apply these variables to your current shell. **Typical Workflow (AWS):** ### Bash/Zsh ```bash # 1. Set environment variables for an identity eval $(atmos auth env --identity prod-admin) # 2. Authenticate (when needed) - opens browser for SSO atmos auth login --identity prod-admin # 3. Use AWS CLI, Terraform, or other tools aws s3 ls terraform plan vpc -s prod ``` ### PowerShell ```powershell # 1. Set environment variables for an identity $envVars = atmos auth env --identity prod-admin --format json | ConvertFrom-Json $envVars.PSObject.Properties | ForEach-Object { Set-Item -Path "Env:$($_.Name)" -Value $_.Value } # 2. Authenticate (when needed) - opens browser for SSO atmos auth login --identity prod-admin # 3. Use AWS CLI, Terraform, or other tools aws s3 ls terraform plan vpc -s prod ``` **Typical Workflow (Azure with device-code):** ### Bash/Zsh ```bash # 1. Set environment variables for an Azure identity eval $(atmos auth env --identity azure-dev) # 2. Authenticate (when needed) - displays device code for browser login atmos auth login --identity azure-dev # 3. Use Azure CLI, Terraform, or other tools az group list terraform plan -var-file=azure.tfvars ``` ### PowerShell ```powershell # 1. Set environment variables for an Azure identity $envVars = atmos auth env --identity azure-dev --format json | ConvertFrom-Json $envVars.PSObject.Properties | ForEach-Object { Set-Item -Path "Env:$($_.Name)" -Value $_.Value } # 2. Authenticate (when needed) - displays device code for browser login atmos auth login --identity azure-dev # 3. Use Azure CLI, Terraform, or other tools az group list terraform plan -var-file=azure.tfvars ``` **CI/CD Workflow (GitHub Actions):** In GitHub Actions, use `--format=github` to export credentials directly to `$GITHUB_ENV`: ```yaml - name: Authenticate run: atmos auth env --identity azure-prod --format=github --login - name: Deploy run: atmos terraform apply mycomponent -s prod ``` The `--format=github` flag writes `KEY=value` pairs directly to the `$GITHUB_ENV` file, making credentials available to subsequent steps in GitHub Actions without shell pipelines. **Minting a GitHub token in one step and consuming it in the next:** With the [`atmos/pro` provider and `github/sts` integration](/cli/configuration/auth#github-sts-atmos-pro), `atmos auth env --format=github` can mint a short-lived, least-privilege GitHub token in one step and hand it to later steps, all built into the Atmos CLI. Set `token_env` on the integration to choose the variable name the raw token is exported under: ```yaml # atmos.yaml auth: providers: atmos-pro: kind: atmos/pro spec: workspace_id: # or ATMOS_PRO_WORKSPACE_ID identities: atmos-pro: kind: atmos/pro via: { provider: atmos-pro } integrations: github-sts: kind: github/sts via: { provider: atmos-pro } spec: token_env: GH_TOKEN # export the minted token as $GH_TOKEN ``` ```yaml # .github/workflows/example.yml permissions: id-token: write # the only permission required steps: - name: Mint a GitHub token # Authenticates to Atmos Pro via OIDC, mints the token, and writes GH_TOKEN to $GITHUB_ENV. run: atmos auth env --identity=atmos-pro --format=github --login - name: Check out a private repo with the minted token uses: actions/checkout@v4 with: repository: acme/private-repo token: ${{ env.GH_TOKEN }} - name: Use the GitHub CLI run: gh repo view acme/private-repo env: GH_TOKEN: ${{ env.GH_TOKEN }} ``` For Atmos's own git operations (vendoring, `source:` components, `terraform init` of private `git::https://…` modules) `github/sts` injects per-owner `GIT_CONFIG_*` rewrites automatically, and the single-owner token is also exported as `ATMOS_PRO_GITHUB_TOKEN` by default. `token_env` lets you export it under a different name for everything else (`gh`, `actions/checkout`, REST API). For multi-org runs, use a Go template over `.owner` (e.g. `token_env: GH_TOKEN_{{ .owner }}`) to export one variable per owner. **The commands fail gracefully:** If you run cloud CLI or Terraform commands before logging in, they will fail with standard credential errors, prompting you to run `atmos auth login`. This separation allows you to add `eval $(atmos auth env)` to your shell profile without triggering login prompts every time you open a terminal. ## When to Use This Command **Use `atmos auth env` when:** - Adding authentication to your shell profile (`.zshrc`, `.bashrc`) for automatic configuration - Integrating with CI/CD pipelines that need environment variables - Writing scripts that source credentials - You need credentials in a specific format (bash, json, dotenv, github) - Working primarily in a single identity and want persistent configuration **Use `atmos auth shell` instead when:** - You need session isolation for security (credentials scoped to subshell) - Working with production or sensitive environments requiring strict boundaries - Running multiple concurrent sessions with different identities in separate terminals - You want credentials to automatically clean up when you exit the shell - You need clear separation between different cloud contexts See the [`atmos auth shell`](/cli/commands/auth/shell) documentation for more details on session isolation. ## Examples ```shell # Configure shell for the default identity (bash format) atmos auth env # Interactively select identity atmos auth env --identity # JSON format for a specific identity atmos auth env --identity prod-admin --format json # Dotenv format atmos auth env --format dotenv # GitHub Actions format (writes to $GITHUB_ENV) atmos auth env --identity prod-admin --format github # GitHub Actions format with explicit output file atmos auth env --identity prod-admin --format github --output-file /path/to/env/file # Automatically login if credentials don't exist or are expired atmos auth env --login # Use with identity selection and auto-login atmos auth env --identity prod-admin --login ``` ## Shell Integration ### Adding to Your Shell Profile A key advantage of `atmos auth env` is that it outputs environment variables for an identity **without requiring immediate authentication**. This makes it safe to add to your shell profile without triggering a login prompt on every new shell session: ### Bash/Zsh Add to `~/.bashrc` or `~/.zshrc`: ```bash eval $(atmos auth env) ``` ### PowerShell Add to `$PROFILE`: ```powershell $envVars = atmos auth env --format json | ConvertFrom-Json $envVars.PSObject.Properties | ForEach-Object { Set-Item -Path "Env:$($_.Name)" -Value $_.Value } ``` Helper Function for Identity Switching For users who prefer a combined authentication and configuration flow, you can create a helper function: ```bash # Helper function for Atmos auth integration # Usage: use-identity [identity-name] [other atmos auth env flags] # This uses Atmos auth to authenticate and set credentials in the environment # If called with no arguments, it brings up the identity selector function use-identity() { if ! command -v atmos >/dev/null 2>&1; then echo "Error: atmos command not found. Please install atmos first." >&2 return 1 fi # Run atmos auth env and evaluate the output to set credentials local auth_output if [ $# -eq 0 ]; then # No arguments: bring up the selector by passing --identity with no value if ! auth_output=$(atmos auth env --identity --login 2>&1); then echo "Error running atmos auth: $auth_output" >&2 return 1 fi else # Arguments provided: pass --identity= with the first argument, then any additional flags if ! auth_output=$(atmos auth env --identity="$1" --login "${@:2}" 2>&1); then echo "Error running atmos auth: $auth_output" >&2 return 1 fi fi # Evaluate the output to set environment variables eval "$auth_output" } ``` **Usage examples:** ```bash # Interactively select identity and login use-identity # Use specific identity with automatic login use-identity prod-admin # Use specific identity with custom format use-identity staging-dev --format bash ``` This helper function: - Combines configuration and authentication in one step using the `--login` flag - Checks if atmos is installed before running - Provides both interactive and non-interactive modes - Evaluates the output to set credentials in your current shell - Works with any cloud provider supported by Atmos This approach: - Sets environment variables for the default identity - Uses cached credentials if they exist and are valid - **Does not trigger login prompts** - you can run `atmos auth login` separately when needed - Tools that require credentials will fail gracefully, prompting you to login when necessary The separation between setting environment variables (`atmos auth env`) and authentication (`atmos auth login`) means you can: 1. Set environment variables once with `eval $(atmos auth env)` in your profile 2. Authenticate when needed with `atmos auth login` 3. Re-authenticate when credentials expire without re-evaluating `atmos auth env` :::info Using with Warp Terminal [Warp](https://warp.dev) is a modern terminal that works well with `atmos auth env`. When working with Warp or other feature-rich terminals, use `atmos auth env` instead of `atmos auth shell` to maintain the terminal's UI features (command palette, AI assistant, blocks, etc.). The `atmos auth shell` command launches a subshell that loses the terminal emulator's feature-rich UIs. Instead, use a helper function like `use-identity()` (shown above) that calls `atmos auth env` to get credentials for AWS CLI or other operations while maintaining your terminal's interface. Adding `eval $(atmos auth env)` to your shell profile (`.zshrc`, `.bashrc`) ensures that new terminal windows and panes automatically have the appropriate environment variables set for your default identity. ::: ## Flags - **`--identity` (alias `-i`)** Specify the identity to use. This flag has three modes: - **With value** (`--identity admin`): Use the specified identity - **Without value** (`--identity`): Show interactive selector to choose identity - **Omitted**: Use the default identity configured in `atmos.yaml` - **`--format` (alias `-f`)** Output format for the environment variables. Default: `bash`. - **`bash`** (default): Prints `export KEY='value'` lines suitable for shell evaluation - **`json`**: Prints a JSON object of environment variables - **`dotenv`**: Prints `KEY='value'` lines in dotenv format - **`github`**: Prints `KEY=value` lines for GitHub Actions `$GITHUB_ENV` file. Multiline values use GitHub's heredoc syntax. - **`--output-file` (alias `-o`)** Write output to a file instead of stdout. When used with `--format=github`, this specifies the target file for environment variables. If `--format=github` is used without `--output-file`, the command automatically writes to the file specified by the `$GITHUB_ENV` environment variable (set automatically in GitHub Actions). - **`--login`** Trigger authentication flow if credentials are missing or expired. When enabled, this flag will automatically invoke the login flow if the specified identity hasn't been authenticated yet. - **Without flag** (default): Outputs environment variables for the identity using cached credentials (if available) - **With flag** (`--login`): Automatically triggers browser-based authentication when credentials are missing or expired This is particularly useful when you want to ensure fresh credentials or when switching to an identity you haven't logged into yet. ## Environment variables ### Input Variables (Configuration) - **`ATMOS_IDENTITY`** Default identity when `--identity` is not provided. - **`ATMOS_AUTH_ENV_FORMAT`** Sets the default output style for exported credentials. Supported values: `bash` , `json` , `dotenv` , `github` . - **`ATMOS_AUTH_ENV_OUTPUT_FILE`** Default output file path when `--output-file` is not provided. For `--format=github` , this defaults to the value of `$GITHUB_ENV` if set. ### Output Variables (Exported by this command) When you run `eval $(atmos auth env)`, Atmos exports provider-specific environment variables to your shell based on the identity's cloud provider. - **`ATMOS_IDENTITY`** The name of the active identity (all providers) **Provider-specific variables:** The exact variables exported depend on your cloud provider. For example: **AWS identities:** ```bash AWS_SHARED_CREDENTIALS_FILE # Path to Atmos-managed credentials file AWS_CONFIG_FILE # Path to Atmos-managed config file AWS_PROFILE # Profile name for the identity AWS_REGION # Default region (if configured) ``` **Azure identities:** ```bash AZURE_SUBSCRIPTION_ID # Azure subscription ID AZURE_TENANT_ID # Azure AD tenant ID AZURE_LOCATION # Azure region (if configured) ARM_SUBSCRIPTION_ID # Terraform provider subscription ID ARM_TENANT_ID # Terraform provider tenant ID ARM_USE_OIDC # Set to "true" for OIDC authentication ARM_USE_CLI # Set to "true" for CLI/device-code authentication ARM_CLIENT_ID # Azure AD application (client) ID for OIDC ``` **GitHub OIDC identities:** ```bash GITHUB_TOKEN # GitHub authentication token GITHUB_APP_ID # GitHub App ID (if applicable) GITHUB_INSTALLATION_ID # GitHub App installation ID (if applicable) ``` These environment variables configure cloud provider SDKs and tools (Terraform, AWS CLI, Azure CLI, GitHub CLI, kubectl) to use the correct credentials without exposing them directly in the environment. ## Notes - `atmos auth env` outputs environment variable export statements. You must use `eval` to apply them to your current shell session, where they persist until you close the shell or override them. - The command does not trigger authentication unless you use the `--login` flag. - Safe to add to shell profiles (`.zshrc`, `.bashrc`) - won't prompt for login on every shell startup. - Tools expecting credentials will fail gracefully if you haven't run `atmos auth login` yet, prompting you to authenticate. - For workflows requiring multiple identities simultaneously, consider using `atmos auth shell` which provides session isolation. ## See Also - [`atmos auth shell`](/cli/commands/auth/shell) - Launch an isolated shell session with automatic credential cleanup - [`atmos auth login`](/cli/commands/auth/login) - Authenticate to a cloud identity - [`atmos auth whoami`](/cli/commands/auth/whoami) - Display information about the current authenticated identity - [Warp Terminal](https://warp.dev) - Modern terminal that works well with persistent environment variables --- ## atmos auth exec Run any tool (Terraform, AWS CLI, kubectl, etc.) with the right cloud identity injected automatically into the environment. Use `exec` when you want a one-off command to inherit secure, temporary credentials without polluting your shell session. _\[Video: atmos auth exec]_ :::tip Exec vs Shell Use `exec` for single commands or automation. Use [`shell`](/cli/commands/auth/shell) for interactive sessions where you'll run multiple commands. ::: ## Usage ```shell atmos auth exec [--identity ] -- [args...] ``` ## Arguments - **command** The program to execute with authentication environment variables set. - **args...** Arguments to pass through to the command. ## Examples ### AWS Examples ```shell # Run terraform with authenticated env (uses default identity) atmos auth exec -- terraform plan -var-file=env.tfvars # Interactively select identity atmos auth exec --identity -- aws sts get-caller-identity # Use a specific identity atmos auth exec --identity prod-admin -- aws sts get-caller-identity # Inspect AWS env vars atmos auth exec -- env | grep AWS ``` ### Azure Examples ```shell # Run Azure CLI with authenticated identity atmos auth exec --identity azure-dev -- az group list # Run Terraform with Azure credentials atmos auth exec --identity azure-prod -- terraform plan -var-file=azure.tfvars # Verify Azure credentials atmos auth exec --identity azure-dev -- az account show # Inspect Azure env vars atmos auth exec -- env | grep -E '^(AZURE_|ARM_)' ``` ### CI/CD Examples (Azure OIDC) In GitHub Actions or other CI/CD environments with OIDC support: ```shell # Run Terraform with Azure OIDC credentials atmos auth exec --identity azure-prod -- terraform apply -auto-approve # Run Azure CLI commands with OIDC identity atmos auth exec --identity azure-prod -- az resource list --resource-group my-rg ``` ## Flags - **`--identity` (alias `-i`)** Specify the identity to use. This flag has three modes: - **With value** (`--identity admin`): Use the specified identity - **Without value** (`--identity`): Show interactive selector to choose identity - **Omitted**: Use the default identity configured in `atmos.yaml` ## Notes - `--` is required to stop Atmos flag parsing; everything after is passed to the subcommand. --- ## atmos auth list Display all authentication providers and identities configured in your Atmos project. View authentication chains showing how identities assume roles through providers or other identities. Supports multiple output formats including interactive tables, hierarchical trees, JSON, and YAML for integration with other tools. _\[Video: atmos auth list]_ ## Usage ```shell atmos auth list [--format ] [--providers [names]] [--identities [names]] [--tags ] ``` ## Examples ```shell # List all providers and identities in table format (default) atmos auth list # Show only providers atmos auth list --providers # Show only specific providers atmos auth list --providers=aws-sso,okta # Show only identities atmos auth list --identities # Show specific identities atmos auth list --identities=admin,developer # Filter by tags (matches providers/identities tagged with any of the given tags) atmos auth list --tags production # Filter by multiple tags atmos auth list --tags production,tier-1 # Display as hierarchical tree atmos auth list --format tree # Export as JSON for programmatic access atmos auth list --format json # Export as YAML atmos auth list --format yaml # Generate Graphviz diagram brew install graphviz atmos auth list --format graphviz > auth.dot dot -Tpng auth.dot -o auth.png # Generate Mermaid diagram atmos auth list --format mermaid # Generate Markdown with embedded Mermaid atmos auth list --format markdown > auth.md ``` ## Output Formats - **`table` (default)** Displays providers and identities in formatted tables with columns for key attributes. Shows authentication chains inline for identities. - **`tree`** Hierarchical tree view showing providers and identities with nested attributes. Visualizes authentication chains clearly with parent-child relationships. - **`json`** Machine-readable JSON output containing the complete provider and identity configurations. Useful for programmatic access and integration with other tools. - **`yaml`** Human-readable YAML output of provider and identity configurations. Good for reviewing configuration or generating documentation. - **`graphviz` / `dot`** Graphviz DOT format for creating visual diagrams. Use with `dot` command to generate PNG, SVG, or PDF diagrams. Requires `brew install graphviz` on macOS. - **`mermaid`** Mermaid diagram syntax for rendering in GitHub, GitLab, Confluence, and other Mermaid-compatible platforms. Shows providers and identities as a flowchart with styled nodes. - **`markdown` / `md`** Markdown document with embedded Mermaid diagram in a code fence. Ready to commit to your repository documentation. ## Flags - **`--format` / `-f`** Output format: `table`, `tree`, `json`, `yaml`, `graphviz`, `mermaid`, or `markdown`. Default: `table`. - **`--providers [names]`** Show only providers. Optionally filter by comma-separated provider names (e.g., `--providers=aws-sso,okta`). Cannot be used with `--identities`. - **`--identities [names]`** Show only identities. Optionally filter by comma-separated identity names (e.g., `--identities=admin,dev`). Cannot be used with `--providers`. - **`--tags `** Filter providers and identities by tags (comma-separated, matches any): `--tags=production,tier-1`. Providers and identities are tagged via an optional `tags: [...]` list on their `atmos.yaml` definitions. ## Understanding Authentication Chains Authentication chains show how identities authenticate through providers or other identities. Chains are displayed in the format: ``` provider → identity1 → identity2 → target ``` For example: - `aws-sso → admin` - Direct authentication through AWS SSO - `aws-sso → base-role → admin-role` - Multi-step authentication with role assumption - `okta → aws-dev → developer` - Authentication through Okta SSO, then assuming an AWS role Chains can be arbitrarily long when using multiple role assumptions or identity federation. ## Table Format Details ### Providers Table - **NAME** - Provider configuration name - **KIND** - Provider type (e.g., `aws-sso`, `okta`) - **REGION** - Cloud region (if applicable) - **START URL / URL** - Authentication endpoint - **DEFAULT** - Marked with ✓ if this is the default provider ### Identities Table - **NAME** - Identity configuration name - **KIND** - Identity type (e.g., `aws/assume-role`, `aws/user`) - **VIA PROVIDER** - Provider used for authentication - **VIA IDENTITY** - Parent identity for multi-step authentication - **DEFAULT** - Marked with ✓ if this is the default identity - **ALIAS** - Short alias for the identity ## Tree Format Details The tree format shows hierarchical relationships with providers as roots and their identities as children: ``` Authentication Configuration ├─ aws-sso (aws-sso) [DEFAULT] │ ├─ Region: us-east-1 │ ├─ Start URL: https://example.awsapps.com/start │ ├─ Session │ │ └─ Duration: 12h │ └─ Identities │ ├─ admin (aws/assume-role) [DEFAULT] │ │ ├─ Principal │ │ │ └─ arn: arn:aws:iam::123456789012:role/AdminRole │ │ └─ ops (aws/assume-role) │ │ └─ Principal │ │ └─ arn: arn:aws:iam::987654321098:role/OpsRole │ └─ developer (aws/assume-role) │ └─ Principal │ └─ arn: arn:aws:iam::123456789012:role/DeveloperRole ``` ## Graphviz Format Example The Graphviz format generates DOT language for creating professional diagrams: ```dot digraph AuthConfig { rankdir=LR; node [shape=box, style=rounded]; "aws-sso" [label="aws-sso\n(aws-sso)", style="rounded,filled", fillcolor=lightblue]; "admin" [label="admin\n(aws/assume-role)", style="rounded,filled", fillcolor=lightgreen]; "developer" [label="developer\n(aws/assume-role)"]; "aws-sso" -> "admin"; "aws-sso" -> "developer"; } ``` Generate visualizations with: ```shell # PNG image atmos auth list --format graphviz | dot -Tpng > auth.png # SVG (scalable) atmos auth list --format graphviz | dot -Tsvg > auth.svg # PDF document atmos auth list --format graphviz | dot -Tpdf > auth.pdf ``` ## Mermaid Format Example The Mermaid format works in GitHub, GitLab, and many documentation platforms: ```mermaid graph LR aws_sso["aws-sso
aws-sso"] admin["admin
aws/assume-role"] developer["developer
aws/assume-role"] aws_sso --> admin aws_sso --> developer classDef provider fill:#e3f2fd,stroke:#1976d2,stroke-width:2px classDef identity fill:#e8f5e9,stroke:#388e3c,stroke-width:2px classDef default stroke:#ff9800,stroke-width:3px class aws_sso provider class aws_sso default class admin identity class admin default class developer identity ``` Use in documentation by adding to Markdown: ````markdown ```mermaid graph LR aws_sso["aws-sso
aws-sso"] admin["admin
aws/assume-role"] aws_sso --> admin classDef provider fill:#e3f2fd,stroke:#1976d2,stroke-width:2px classDef identity fill:#e8f5e9,stroke:#388e3c,stroke-width:2px class aws_sso provider class admin identity ``` ```` Or use the `--format markdown` to generate the complete document automatically. ## Related Commands - [`atmos auth whoami`](/cli/commands/auth/whoami) - Display current authentication status - [`atmos auth login`](/cli/commands/auth/login) - Authenticate with a provider - [`atmos auth validate`](/cli/commands/auth/validate) - Validate authentication configuration - [`atmos auth env`](/cli/commands/auth/env) - Export credentials as environment variables ## Version Support `atmos auth list` is available in Atmos `v1.195.0` and later. To get started: ```shell # Install latest version brew upgrade atmos # Verify version atmos version ``` ## What's Next This is just the beginning. We're continuing to improve authentication management in Atmos with: - Enhanced visualization options for better understanding of authentication flows - Interactive TUI mode for browsing configurations - Better integration with Dev Containers for seamless development environments - Support for additional cloud providers and identity providers (PRs welcome!) Want to contribute? Check out our [GitHub repository](https://github.com/cloudposse/atmos) to submit feature requests or pull requests for new authentication providers. --- ## atmos auth login Authenticate with a configured identity using SSO, SAML, OIDC, or static credentials. Atmos retrieves and caches short-lived credentials so they can be reused until expiration, avoiding repeated logins for each command. _\[Video: atmos auth login]_ ## Usage ```shell atmos auth login [--identity ] [--tags ] [--webflow] ``` ## Examples ```shell # Use default identity (prompts if no default is configured) atmos auth login # Interactively select identity (even if default is configured) atmos auth login --identity # Use specific identity atmos auth login --identity admin # Use short form of identity flag atmos auth login -i admin # Interactive selection with short form atmos auth login -i # Login using tags (auto-selects if exactly one identity matches, otherwise shows a picker) atmos auth login --tags admin,production # Force a fresh browser sign-in for an AWS IAM user without changing stored IAM keys atmos auth login --profile=legacy --identity=cp-root/admin --webflow ``` ## Arguments - **n/a** No positional arguments. ## Flags - **`--identity` (alias `-i`)** Specify the identity to authenticate. This flag has three modes: - **With value** (`--identity admin`): Use the specified identity - **Without value** (`--identity`): Force interactive selector, even if a default identity is configured - **Omitted**: Automatic behavior based on configuration: - **Exactly one default identity**: Use it automatically - **No default identities** (interactive): Show selector with all available identities - **Multiple default identities** (interactive): Show selector with only the default identities - **No/multiple defaults** (CI/non-interactive): Return an error **Environment variables:** `ATMOS_IDENTITY` or `IDENTITY` (checked in that order) - **`--provider` (alias `-p`)** Authenticate directly with a provider (bypassing identity selection). This is useful for: - **First-time login** with `auto_provision_identities: true` when no identities exist yet - **Provider-level authentication** without specifying a particular identity When no identities are configured and no `--provider` flag is specified, Atmos automatically falls back to provider authentication: a single provider is auto-selected, multiple providers prompt for selection (interactive) or require `--provider` flag (non-interactive). - **`--tags`** Filter identities by tags (comma-separated, matches any): `--tags admin,production`. Identities are tagged via an optional `tags: [...]` list in `atmos.yaml`. - **Exactly one match**: Atmos automatically authenticates with that identity - **Multiple matches**: Shows an interactive picker restricted to the matching identities - **No matches**: Returns an error listing the available tags ```shell atmos auth login --tags admin,production ``` - **`--webflow`** Force a fresh browser-based OAuth2/PKCE sign-in for an `aws/user` identity. This bypasses cached temporary credentials and configured IAM access keys for this invocation, while leaving the configured keys unchanged. It cannot be used with `--provider` or any other identity kind. ## Interactive Identity Selection Atmos provides an interactive identity selector in two scenarios: 1. **No default configured**: When no `--identity` flag is provided and no default identity is configured 2. **Explicit request**: When using `--identity` without a value (e.g., `atmos auth login --identity`) This allows you to: - Quickly choose an identity without remembering exact names - Override the default identity temporarily without changing configuration - Browse all available identities and make an informed selection The interactive selector displays all configured identities with arrow key navigation and Enter to confirm. In CI/CD pipelines or non-interactive environments, you must either: - Configure a default identity in your `atmos.yaml` - Explicitly specify the identity using `--identity ` or environment variable ## Integrations (ECR & EKS) When you authenticate with an identity, Atmos automatically triggers any **integrations** linked to that identity (when `auto_provision` is enabled, which is the default). Integrations provide client-only credential materializations for services like ECR and EKS. ```yaml auth: identities: dev-admin: kind: aws/permission-set # ... identity config ... integrations: dev/ecr: kind: aws/ecr via: identity: dev-admin spec: registry: account_id: "123456789012" region: us-east-2 dev/eks: kind: aws/eks via: identity: dev-admin spec: cluster: name: dev-cluster region: us-east-2 alias: dev-eks ``` ```bash $ atmos auth login dev-admin Successfully authenticated as dev-admin ✓ ECR login: 123456789012.dkr.ecr.us-east-2.amazonaws.com (expires in 11h59m) ✓ EKS kubeconfig: dev-eks → ~/.config/atmos/kube/config ``` Integration failures are non-blocking - your identity authentication succeeds even if an integration fails. You can retry integrations separately using [`atmos aws ecr login`](/cli/commands/aws/ecr-login) or [`atmos aws eks update-kubeconfig --integration`](/cli/commands/aws/eks/update-kubeconfig). See [ECR Authentication Tutorial](/tutorials/ecr-authentication) and [EKS Kubeconfig Authentication Tutorial](/tutorials/eks-kubeconfig-authentication) for detailed configuration examples. ## Notes - Prints provider, identity, account, region, and expiration when successful. - Credentials are cached to avoid repeated logins until expiration. - The interactive selector uses arrow keys for navigation and Enter to confirm selection. - Using `--identity` without a value is useful when: - You have a default identity configured but want to temporarily use a different one - You want to browse available identities before selecting - You're working in a team environment with multiple configured identities - For AWS SSO (IAM Identity Center), you will see a **verification code** displayed in the terminal. This is a device authorization user code (e.g., "WDDD-HRQV") that AWS generates for the device flow - **this is NOT an MFA token**. The code is displayed so you can visually verify it matches what AWS shows in the browser. Any MFA prompts will appear in the browser during authentication. - For Azure device code authentication, you will see a similar **verification code** and URL to complete browser-based authentication. Atmos writes credentials to the Azure CLI MSAL cache, ensuring full compatibility with Terraform's Azure providers (azurerm, azuread, azapi). ## Authentication Methods Atmos supports multiple authentication methods for different cloud providers: ### AWS - IAM Identity Center (SSO) - IAM Roles Anywhere (certificate-based) - OIDC (GitHub Actions, GitLab CI) - Static credentials - Browser-based OAuth2 PKCE (automatic fallback for `aws/user` identities) See [Migrating from Leapp](/tutorials/migrating-from-leapp) for AWS examples. ### Azure - Device Code Flow (browser-based) - OIDC (workload identity for CI/CD) - Service Principals (client credentials) See [Azure Authentication](/tutorials/azure-authentication) for detailed Azure configuration. ## See Also - [AWS ECR Login Command](/cli/commands/aws/ecr-login) — Login to AWS ECR registries - [AWS EKS Token Command](/cli/commands/aws/eks-token) — Generate EKS bearer tokens for kubectl - [ECR Authentication Tutorial](/tutorials/ecr-authentication) — Step-by-step ECR setup guide - [EKS Kubeconfig Authentication Tutorial](/tutorials/eks-kubeconfig-authentication) — Step-by-step EKS setup guide - [Auth Providers Configuration](/cli/configuration/auth/providers) — Configure AWS SSO, SAML, and OIDC providers (including IAM permissions for identity provisioning) --- ## atmos auth logout Use this command to clear local session data (tokens, cached credentials) while preserving your keychain credentials for faster re-authentication. This is useful when switching identities, ending work sessions, or troubleshooting authentication issues. :::tip Safe by Default By default, `atmos auth logout` preserves your **keychain credentials** (IAM user access keys, service account credentials) to enable instant re-authentication. It only clears **session data** (AWS SSO tokens, temporary credentials). To also delete keychain credentials, use the `--keychain` flag. This requires interactive confirmation for safety (bypass with `--force` in CI/CD). ::: :::warning Browser Sessions Remain Active This command only removes **local credentials**. It does **not** log you out of web-based sessions with your identity provider (AWS SSO, Okta, etc.). Your browser sessions remain active until you explicitly sign out from the identity provider's website. ::: ## The Problem Most cloud practitioners never log out of their cloud provider identities. Not because they don't want to, but because the tooling doesn't make it easy. When you authenticate with cloud providers, credentials get scattered across your filesystem: - **AWS**: `~/.aws/credentials`, `~/.aws/config`, session tokens - **Azure**: `~/.azure/` directory with multiple authentication artifacts - **Google Cloud**: `~/.config/gcloud/` with various credential files Most cloud provider tools don't provide a simple, comprehensive logout command. You're left to: - Manually hunt down and delete credential files across different locations - Navigate through provider-specific web consoles to revoke tokens - Hope that session expiration handles cleanup for you This leads to **credential sprawl**: old, forgotten credentials littering your system, many still valid and exploitable. The `atmos auth logout` command makes credential cleanup explicit, comprehensive, and easy. ## Usage ```shell atmos auth logout [identity] [options] ``` ## Examples ### Logout from Specific Identity ```shell # Using positional argument atmos auth logout dev-admin # Using --identity flag atmos auth logout --identity dev-admin # Using short form atmos auth logout -i dev-admin ``` This removes only this identity's credentials from the system keyring and removes only this identity's profile from AWS config files. Other identities using the same provider are **not affected** and remain usable. The identity configuration in `atmos.yaml` is preserved and can be re-authenticated by running `atmos auth login`. **Example output:** ``` Logging out from identity: dev-admin Building authentication chain... ✓ Chain: aws-sso → dev-org-admin → dev-admin Removing credentials... ✓ Keyring: aws-sso ✓ Keyring: dev-org-admin ✓ Keyring: dev-admin ✓ Files: ~/.config/atmos/aws/aws-sso/ (XDG-compliant) Successfully logged out from 3 identities ⚠️ Note: This only removes local credentials. Your browser session may still be active. Visit your identity provider to end your browser session. ``` ### Logout from All Identities ```shell atmos auth logout --all ``` This removes all identity credentials from the system keyring and removes all identity profiles from AWS config files for all providers. All identity configurations remain in `atmos.yaml` and can be re-authenticated. This is useful when troubleshooting authentication issues or performing a complete credential cleanup. **Example output:** ``` Logging out from all identities... Removing all credentials... ✓ Keyring: aws-sso ✓ Keyring: dev-org-admin ✓ Keyring: dev-admin ✓ Keyring: prod-admin ✓ Files: ~/.config/atmos/aws/aws-sso/ (XDG-compliant) Successfully logged out from 4 identities ⚠️ Note: This only removes local credentials. Your browser session may still be active. Visit your identity provider to end your browser session. ``` ### Logout by Tags ```shell atmos auth logout --tags production ``` This removes credentials for every identity and provider tagged with any of the given tags, using the same confirmation flow as `--all`. This is useful when you want to log out of an entire environment (e.g. everything tagged `production`) without listing each identity individually. ### Logout from Specific Provider ```shell atmos auth logout --provider aws-sso ``` This removes all credentials from the system keyring for the provider and all identities that use it, and deletes the entire AWS config directory for the provider (all files). This is the most thorough cleanup and is useful when completely switching providers or AWS organizations. **Example output:** ``` Logging out from provider: aws-sso Removing all credentials for provider... ✓ Keyring: aws-sso ✓ Keyring: dev-org-admin (via aws-sso) ✓ Keyring: dev-admin (via aws-sso) ✓ Keyring: prod-admin (via aws-sso) ✓ Files: ~/.config/atmos/aws/aws-sso/ (XDG-compliant) Successfully logged out from 4 identities ``` ### Interactive Mode ```shell atmos auth logout ``` When run without arguments, presents an interactive menu to choose what to logout from: ``` ? Choose what to logout from: ❯ Identity: dev-admin Identity: prod-admin Identity: dev-readonly Provider: aws-sso (removes all identities) All identities (complete logout) ``` ### Dry Run Mode ```shell atmos auth logout dev-admin --dry-run ``` Preview what would be removed without actually deleting anything: ``` Dry run mode: No credentials will be removed Would remove from identity: dev-admin • Keyring: aws-sso • Keyring: dev-org-admin • Keyring: dev-admin • Files: ~/.config/atmos/aws/aws-sso/credentials • Files: ~/.config/atmos/aws/aws-sso/config 3 identities would be logged out ``` You can also use `--dry-run` with `--all` to preview a complete logout: ```shell atmos auth logout --all --dry-run ``` ``` Dry run mode: No credentials will be removed Would remove: • All identity keyring entries • All provider keyring entries • Files: - ~/.config/atmos/aws/aws-sso/ - ~/.config/atmos/aws/backup-provider/ ``` ### Delete Keychain Credentials (Destructive) By default, logout preserves keychain credentials for instant re-authentication. Use `--keychain` to permanently delete them: ```shell # Interactive mode with confirmation atmos auth logout dev-admin --keychain ``` **Interactive confirmation prompt:** ``` Delete keychain credentials for dev-admin? This will permanently remove: • IAM user access keys • Service account credentials • Provider credentials Session data will also be cleared. ? Yes, delete credentials / No, keep credentials ``` **For CI/CD (non-interactive):** ```shell # Bypass confirmation with --force atmos auth logout dev-admin --keychain --force ``` **What happens:** - Deletes credentials from system keychain (IAM keys, service account creds) - Clears session data (AWS SSO tokens, temporary credentials) - Removes AWS config files - Requires re-authentication (`atmos auth login`) to use this identity again **When to use `--keychain`:** - Permanently removing an identity you no longer need - Security incident response (credential rotation) - Switching to different IAM user or service account - Complete credential cleanup before machine decommission **When NOT to use `--keychain`:** - Normal end-of-day logout (preserve keychain for next day) - Switching between identities temporarily - Troubleshooting authentication issues ## Quick Reference Understanding what gets removed: | Command | Keychain Credentials | Session Data | AWS Config Files | Use When | |---------|---------------------|--------------|------------------|----------| | `atmos auth logout ` | **Preserved** | **Cleared** | **Identity profile removed** | End of work session | | `atmos auth logout --keychain` | **Deleted** | **Cleared** | **Identity profile removed** | Permanently remove identity | | `atmos auth logout --provider ` | **Preserved** | **Cleared** | **Entire provider directory** | Switch providers | | `atmos auth logout --provider --keychain` | **Deleted** | **Cleared** | **Entire provider directory** | Permanently remove provider | | `atmos auth logout --all` | **Preserved** | **Cleared** | **All profiles removed** | Clean session data | | `atmos auth logout --all --keychain` | **Deleted** | **Cleared** | **All profiles removed** | Complete cleanup | :::tip Safe by Default Without `--keychain`, logout preserves your stored credentials (IAM user keys, service account creds) for instant re-authentication. It only clears session data (AWS SSO tokens, temporary credentials). ::: :::note Ambient Providers Some providers re-resolve the active principal from ambient state on every login rather than holding credentials of their own: - **`gcp/adc`** Application Default Credentials — `gcloud auth application-default login` , `GOOGLE_APPLICATION_CREDENTIALS` , or the metadata server. - **`azure/cli`** Your Azure CLI session, from `az login` . - **`gcp/workload-identity-federation`, `azure/oidc`, `github/oidc`** The configured OIDC token source. These have nothing to preserve in the keychain, so logout always clears their entries whether or not you pass `--keychain`. Nothing is lost — the next `atmos auth login` re-resolves from the source above. Logout also does not fail when there is no entry to remove, which is the normal state for these providers. ::: :::danger Permanent Deletion Using `--keychain` permanently deletes credentials from your system keychain. You'll need to re-enter IAM user access keys or re-authenticate service accounts when logging in again. ::: :::tip Re-authentication All logout commands preserve your `atmos.yaml` configuration. Run `atmos auth login` to re-authenticate with any configured identity. ::: ## Arguments - **`identity`** Name of the identity to logout from. Must match an identity defined in `atmos.yaml` . If omitted, enters interactive mode. Can also be specified via the `--identity` flag. ## Flags - **`--identity` (alias `-i`)** Specify the identity to logout from. Alternative to using the positional argument. This flag has three modes: - **With value** (`--identity admin`): Logout from the specified identity - **Without value** (`--identity`): Show interactive selector to choose identity (same as omitting both flag and argument) - **Omitted**: Enter interactive mode if no positional argument is provided **Environment variables:** `ATMOS_IDENTITY` or `IDENTITY` (checked in that order) - **`--all`** Logout from all identities and providers. Clears session data for all identities. Combine with `--keychain` to also remove stored credentials. - **`--provider`** Logout from a specific provider instead of an identity. Clears session data for all identities using this provider. Combine with `--keychain` to also remove stored credentials. - **`--tags`** Logout from every provider and identity matching any of the given tags (comma-separated): `--tags=production`. Providers and identities are tagged via an optional `tags: [...]` list in `atmos.yaml`. Uses the same confirmation flow as `--all`. Combine with `--keychain` to also remove stored credentials. - **`--keychain`** **Also delete credentials from system keychain** (destructive operation). By default, logout preserves keychain credentials (IAM user access keys, service account credentials) to enable instant re-authentication. **When specified:** - Requires interactive confirmation (shows what will be deleted) - Use `--force` to bypass confirmation in CI/CD environments - Permanently removes: IAM user access keys, service account credentials, provider credentials - Session data is also cleared (always happens during logout) **Example:** ```shell # Interactive confirmation atmos auth logout dev-admin --keychain # Non-interactive (CI/CD) atmos auth logout dev-admin --keychain --force ``` - **`--force`** Skip interactive confirmation prompts. Required when using `--keychain` in non-interactive environments (CI/CD pipelines, scripts). **Safety note:** Only use with `--keychain` when you're certain you want to delete credentials. This bypasses the confirmation dialog that warns about permanent credential deletion. - **`--dry-run`** Preview what would be removed without actually deleting anything. Shows which session data and (if `--keychain` is used) which keychain entries would be deleted. Useful for understanding the scope of logout. ## How It Works ### Default Behavior (Safe by Default) By default, `atmos auth logout` clears **session data only**: **Example:** For `atmos auth logout dev-admin` (without `--keychain`): 1. Clears session data: - Removes `dev-admin` profile from `~/.config/atmos/aws/aws-sso/credentials` - Removes `dev-admin` profile from `~/.config/atmos/aws/aws-sso/config` - Clears AWS SSO tokens from `~/.aws/sso/cache/` 2. Preserves keychain credentials: - Keyring entry for `dev-admin` is **preserved** - Keyring entries for authentication chain (`aws-sso`, `dev-org-admin`) are **preserved** - Other identity credentials remain intact **Next login** (`atmos auth login --identity dev-admin`): - Uses preserved keychain credentials instantly - No need to re-enter IAM user access keys - No need to re-authenticate service accounts - Faster authentication (skips interactive prompts) ### Destructive Logout with --keychain Adding `--keychain` permanently deletes credentials from system keychain: **Example:** For `atmos auth logout dev-admin --keychain`: 1. Requires interactive confirmation (bypass with `--force` in CI/CD) 2. Deletes keychain credentials: - Removes keyring entry for `dev-admin` - IAM user access keys are permanently deleted - Service account credentials are permanently deleted 3. Clears session data (same as default logout) **Next login** (`atmos auth login --identity dev-admin`): - Requires re-entering IAM user access keys - Requires re-authenticating service accounts - Full authentication flow (no shortcuts) ### Provider Logout When you log out of a provider using `--provider`, Atmos performs **complete cleanup for that provider**: **Example:** For `atmos auth logout --provider aws-sso` (without `--keychain`): 1. Logs out each identity using the provider (clears session data for all) 2. Keychain credentials are **preserved** (unless `--keychain` is specified) 3. Deletes entire provider directory: `~/.config/atmos/aws/aws-sso/` **With `--keychain`:** `atmos auth logout --provider aws-sso --keychain` 1. Deletes provider keyring entry 2. Deletes all identity keyring entries using this provider 3. Deletes entire provider directory This is the nuclear option when you want to completely remove all traces of a provider. ### Credential Storage Locations Atmos stores credentials in two locations: #### 1. System Keyring Credentials are securely stored in your operating system's keyring: - **macOS**: Keychain Access - **Linux**: Secret Service API (GNOME Keyring, KWallet) - **Windows**: Windows Credential Manager **Keyring entries** use the identity or provider name as the key with user `atmos-auth`. #### 2. Provider-Specific Files Some providers (like AWS) also write credential files for compatibility with other tools: - **AWS credentials**: `//credentials` - **AWS config**: `//config` The default base path follows XDG Base Directory Specification (`~/.config/atmos/aws/` on both Linux and macOS), but this can be customized (see [Custom File Paths](#custom-file-paths)). **Identity logout** selectively removes only that identity's profile from the config files using file locking to prevent conflicts. **Provider logout** (`--provider` flag) deletes the entire provider directory. ### Error Handling The logout command uses **best-effort cleanup**: it continues even if individual steps fail and reports all errors at the end. **Example with missing credentials:** ```shell $ atmos auth logout dev-admin Logging out from identity: dev-admin Building authentication chain... ✓ Chain: aws-sso → dev-admin Removing credentials... ✓ Keyring: aws-sso ✗ Keyring: dev-admin (not found - already logged out) ✓ Files: ~/.config/atmos/aws/aws-sso/ (XDG-compliant) Logged out with warnings (2/3 successful) Errors encountered: • dev-admin: credential not found in keyring ``` The command succeeds (exit code 0) as long as at least one credential was removed. ## Security Considerations ### Browser Sessions Remain Active :::danger Web Sessions Not Affected **Important**: The `atmos auth logout` command only removes **locally cached credentials from your filesystem and keychain**. **Your browser session with the identity provider (AWS SSO, Okta, etc.) remains active and logged in.** Anyone with access to your browser can still access authenticated resources through your active web session. ::: To completely end your session and fully logout: 1. Run `atmos auth logout` to remove local credentials 2. Visit your identity provider's website (e.g., `https://mycompany.awsapps.com/start`) 3. **Explicitly sign out** from the browser session 4. Close all browser windows **Why this matters**: If you only run `atmos auth logout` without signing out of your browser, someone using your computer could potentially access your authenticated session through the browser. ### What Gets Removed **Default logout** (without `--keychain`): - ✅ AWS credential files (XDG-compliant: `~/.config/atmos/aws//credentials`) - ✅ AWS config files (XDG-compliant: `~/.config/atmos/aws//config`) - ✅ AWS SSO tokens (`~/.aws/sso/cache/`) - ✅ Empty provider directories **With `--keychain` flag:** - ✅ Everything above, PLUS: - ✅ Credentials stored in system keychain (IAM user keys, service account creds) - ✅ Provider credentials from system keychain **Logout does NOT remove:** - ❌ Browser session cookies - ❌ Identity provider sessions - ❌ Credentials stored outside Atmos (e.g., `~/.aws/credentials`) - ❌ Configuration files (e.g., `atmos.yaml`) ### Audit Trail All logout operations are logged for security auditing: ``` 2025-10-17T10:15:30Z DEBUG Starting logout identity=dev-admin 2025-10-17T10:15:30Z DEBUG Authentication chain built chain=[aws-sso dev-org-admin dev-admin] 2025-10-17T10:15:30Z DEBUG Removing keyring entry alias=aws-sso 2025-10-17T10:15:30Z DEBUG Removing keyring entry alias=dev-org-admin 2025-10-17T10:15:30Z DEBUG Removing keyring entry alias=dev-admin 2025-10-17T10:15:30Z INFO Logout completed identity=dev-admin removed=3 ``` Enable debug logging with `ATMOS_LOGS_LEVEL=Debug` to see detailed audit information. ## Troubleshooting ### Identity Not Found ``` Error: identity "myidentity" not found in configuration Available identities: • dev-admin • prod-admin • dev-readonly Run 'atmos auth logout' without arguments for interactive selection. ``` **Solution**: Check your `atmos.yaml` configuration and ensure the identity name is spelled correctly. ### Already Logged Out ``` Identity 'dev-admin' is already logged out. No credentials found in keyring or file storage. ``` This is informational, not an error. The identity has no cached credentials to remove. ### Permission Denied ``` Error: failed to delete credentials from keyring: access denied ``` **Solution**: The system keyring requires authentication. On macOS, you may need to grant Atmos permission in **System Preferences → Security & Privacy → Privacy → Accessibility**. ### Files Not Removed ``` ✗ Files: ~/.config/atmos/aws/aws-sso/ (permission denied) ``` **Solution**: Ensure you have write permissions to the Atmos config directory. Check file ownership and permissions: ```shell # On Linux: ls -la ~/.config/atmos/ # On macOS: ls -la ~/Library/Application\ Support/atmos/ ``` ## Related Commands - [`atmos auth login`](/cli/commands/auth/login) - Authenticate with an identity - [`atmos auth whoami`](/cli/commands/auth/whoami) - Show current authentication status - [`atmos auth validate`](/cli/commands/auth/validate) - Validate authentication configuration - [`atmos auth env`](/cli/commands/auth/env) - Export authentication environment variables ## Configuration Logout works with identities and providers defined in your `atmos.yaml`: ```yaml auth: providers: aws-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://mycompany.awsapps.com/start identities: dev-admin: kind: aws/permission-set via: provider: aws-sso principal: name: AdminAccess account: name: "dev-account" prod-admin: kind: aws/permission-set via: provider: aws-sso principal: name: AdminAccess account: name: "prod-account" ``` Running `atmos auth logout dev-admin` removes credentials for `dev-admin` and its authentication chain. ## Advanced Configuration ### Custom File Paths AWS providers support configurable file storage locations via `spec.files.base_path`. This is useful for: - **Custom directories**: Store credentials in non-standard locations - **Container environments**: Use volume mounts at custom paths - **Multi-user systems**: Isolate credentials per user or project #### Configuration In your `atmos.yaml`, add `spec.files.base_path` to your AWS provider: ```yaml auth: providers: aws-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://mycompany.awsapps.com/start spec: files: base_path: ~/.custom/aws/credentials # Custom path ``` #### Precedence The file path is resolved using this precedence order: 1. **Provider configuration** (`spec.files.base_path` in `atmos.yaml`) 2. **Default** (XDG-compliant: `~/.config/atmos/aws/` on both Linux and macOS) #### Path Expansion Paths support tilde (`~`) expansion for user home directories: ```yaml spec: files: base_path: ~/custom/path # Expands to /Users/username/custom/path ``` #### Validation The path is validated during `atmos auth validate`: - Must not be empty or whitespace-only - Must not contain null bytes, carriage returns, or newlines - Tilde expansion must succeed ```shell atmos auth validate ``` ## Best Practices ### 1. Logout When Switching Contexts When switching between different identities or environments, logout first to ensure clean state: ```shell atmos auth logout dev-admin atmos auth login --identity prod-admin ``` ### 2. Logout at End of Work Session Remove credentials when ending your work session for security: ```shell # Logout from specific provider atmos auth logout --provider aws-sso # Or logout from all identities atmos auth logout --all ``` ### 3. Use Dry Run for Verification Preview what will be removed before executing: ```shell atmos auth logout dev-admin --dry-run atmos auth logout dev-admin # Proceed after verification ``` ### 4. End Browser Sessions Always sign out of browser sessions after local logout: ```shell atmos auth logout # Then visit your identity provider and sign out ``` ### 5. Regular Credential Cleanup Periodically clean up unused credentials: ```shell atmos auth logout # Interactive mode to review and remove ``` ## See Also - [Atmos Authentication Overview](/cli/commands/auth/usage) - [AWS IAM Identity Center Configuration](https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html) --- ## atmos auth shell Start an authenticated shell session with all the necessary cloud credentials pre-configured. Perfect for interactive work where you need to run multiple commands without re-authenticating each time. The shell respects your `$SHELL` preference and supports custom shell arguments. _\[Video: atmos auth shell]_ :::tip Shell vs Exec Use `shell` for interactive sessions where you'll run multiple commands. Use [`exec`](/cli/commands/auth/exec) for single commands or automation. ::: ## Usage ```shell atmos auth shell [--identity ] [--shell ] [-- ...] ``` ## Examples ```shell # Launch default shell with default identity atmos auth shell # Interactively select identity (even if default is configured) atmos auth shell --identity # Use a specific identity atmos auth shell --identity prod-admin # Use short form of identity flag atmos auth shell -i staging-admin # Interactive selection with short form atmos auth shell -i # Override shell program atmos auth shell --shell /bin/zsh # Pass custom shell arguments atmos auth shell -- -c "env | grep AWS" # Launch without loading shell configs atmos auth shell -- --norc # Combine options atmos auth shell --identity staging-readonly --shell /bin/bash -- -c "terraform plan" ``` ## Flags - **`--identity` (alias `-i`)** Specify the identity to use for authentication. This flag has three modes: - **With value** (`--identity admin`): Use the specified identity - **Without value** (`--identity`): Force interactive selector, even if a default identity is configured - **Omitted**: Use the default identity configured in `atmos.yaml`, or prompt if no default is set **Environment variables:** `ATMOS_IDENTITY` or `IDENTITY` (checked in that order) - **`--shell`** Specify the shell program to use. Defaults to `$SHELL` , then `bash` , then `sh` . On Windows, defaults to `cmd.exe` . ## Arguments - **shell-args...** Optional shell arguments to pass after `--` . If not provided, launches a login shell by default ( `-l` ). ## Environment Variables The shell session will have the following environment variables set: - **`ATMOS_IDENTITY`** - The name of the authenticated identity - **`ATMOS_SHLVL`** - Shell nesting level (increments for nested Atmos shells) - **AWS configuration paths** (for AWS identities): - `AWS_SHARED_CREDENTIALS_FILE` - Path to Atmos-managed credentials file - `AWS_CONFIG_FILE` - Path to Atmos-managed config file - `AWS_PROFILE` - Profile name corresponding to your identity :::info Secure Credential Handling Atmos never exposes sensitive credentials directly in environment variables. Instead, it writes credentials to managed configuration files following XDG Base Directory Specification (e.g., `~/.config/atmos/aws/` on both Linux and macOS) and sets environment variables pointing to these files. This approach follows AWS SDK best practices and works seamlessly with any tool that uses the AWS SDK, including Terraform, AWS CLI, kubectl with AWS authentication, and more. ::: ## Notes - Type `exit` or press `Ctrl+D` to leave the authenticated shell - The shell can be nested; `ATMOS_SHLVL` tracks the nesting depth - Use `--` to separate Atmos flags from shell-specific arguments - Environment variables from authentication take precedence over existing values --- ## atmos auth Atmos Auth gives you a single, consistent way to authenticate with multiple cloud providers. It supports SAML, SSO, OIDC, GitHub Actions, and static user identities. By consolidating these flows into one system, you no longer need to juggle separate tools or browser plugins, just to try to login. And because it's built into Atmos, it works seamlessly with stacks, components, workflows, shells, and even custom commands. **Configure Authentication** Learn how to configure providers, identities, keyring, and credential storage in your atmos.yaml. Configuration Reference[Read more](/cli/configuration/auth) ## Usage ## Examples ```shell # Validate configuration atmos auth validate # Authenticate with the default identity atmos auth login # Authenticate with a specific identity atmos auth login --identity admin # Print environment variables in JSON atmos auth env --format json # Execute a command with authentication context atmos auth exec -- terraform plan # Show current authentication status atmos auth whoami # Open cloud console in browser atmos auth console # Start a shell with authentication atmos auth shell ``` ## Flags - **`--identity` (alias `-i`)** Specify the identity to use for authentication. Can be: An identity name (e.g., --identity admin or --identity=admin) Empty for interactive selection (e.g., --identity) false to disable authentication (e.g., --identity=false) When set to false, Atmos skips identity authentication and uses standard AWS credential resolution. :::tip Flag Placement Best Practice When using `--identity` with a value, place it **before the `--` separator** and before any positional arguments: ```shell # Recommended: --identity before -- separator atmos auth exec --identity admin -- terraform plan # Also recommended: use equals syntax for clarity atmos auth exec --identity=admin -- terraform plan ``` Using the equals syntax (`--identity=admin`) is unambiguous and works in all contexts. ::: ## Subcommands ## Authentication Concepts ### Providers **Providers** are the upstream systems that Atmos Auth uses to obtain initial credentials: **AWS** - **AWS SSO**: `aws/iam-identity-center` - **AWS SAML**: `aws/saml` - **GitHub OIDC**: `github/oidc` **Azure** - **Interactive Browser**: `azure/interactive` - **Device Code**: `azure/device-code` - **OIDC (Workload Identity)**: `azure/oidc` - **CLI**: `azure/cli` **GCP** - **Application Default Credentials**: `gcp/adc` - **Workload Identity Federation**: `gcp/workload-identity-federation` ### Identities **Identities** represent the user accounts or roles available from provider credentials: **AWS** - **Permission Set**: `aws/permission-set` - **Assume Role**: `aws/assume-role` - **Assume Root**: `aws/assume-root` - **User (Break-glass)**: `aws/user` **Azure** - **Subscription**: `azure/subscription` **GCP** - **Service Account**: `gcp/service-account` - **Project**: `gcp/project` ### Identity Chaining Identity chaining (often called _role chaining_) is when one identity is used to obtain another, forming a sequence of temporary credentials. For example, you might: 1. Start with an SSO login to obtain base credentials. 2. Use those credentials to assume a cross-account role. 3. Optionally, chain again into another role with more limited or specialized permissions. This allows you to: - Access multiple accounts or environments without long-lived keys. - Follow least-privilege practices by escalating only as needed. - Automate complex authentication flows while still relying on short-lived credentials. ## Default Identity Handling A **default identity** is the one Atmos Auth will use automatically when no specific identity is requested. - If you configure a single identity and mark it as `default: true`, Atmos will always use it without requiring you to pass `--identity`. - If multiple identities are defined, you can still mark one as default, but you'll need to explicitly choose another when you don't want the default. - If no default is set and multiple identities exist, Atmos will require you to specify which identity to use. ## Configuration Examples ### AWS SSO with Permission Sets AWS Permission Set identities assume roles via AWS IAM Identity Center (SSO). The `account` field specifies which AWS account contains the permission set. **Account Specification Options:** You can specify the account using either: - `account.name` - Account name/alias (resolved via SSO ListAccounts API) - `account.id` - Numeric account ID (used directly, no lookup required) **Using account names is recommended** as it's more readable and maintainable. Account names match the account names/aliases configured in AWS Organizations. ```yaml auth: providers: company-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://company.awsapps.com/start identities: dev-admin: kind: aws/permission-set default: true via: provider: company-sso principal: name: AdminAccess account: name: development # OR use account ID directly: # id: "123456789012" prod-readonly: kind: aws/permission-set via: provider: company-sso principal: name: ReadOnlyAccess account: name: production ``` ### AWS SAML Authentication :::note The `aws/saml` provider requires the next identity to be of kind `aws/assume-role`. This is because the `assume_role` is the chosen role to sign into after the SAML authentication. ::: ```yaml auth: providers: okta-saml: kind: aws/saml region: us-east-1 url: https://company.okta.com/app/amazon_aws/abc123/sso/saml # Optional: Specify SAML driver (Browser, GoogleApps, Okta, ADFS) # If not specified, Atmos automatically selects the best option driver: Browser # Optional: Auto-download browser drivers on first use (recommended for Browser driver) download_browser_driver: true identities: saml-admin: kind: aws/assume-role default: true via: provider: okta-saml principal: assume_role: arn:aws:iam::123456789012:role/AdminRole ``` #### SAML Driver Options The `driver` field controls how Atmos authenticates with your SAML identity provider: - **`Browser`** (recommended): Launches an automated Chromium browser window for interactive SAML authentication. Uses Playwright for browser automation. **How it works:** - Atmos opens a **sandboxed Chromium browser window** (not your regular browser) - You interact with the SAML login page (username, password, MFA) in this window - The browser window is **visible** (not headless) so you can complete authentication - After successful login, Atmos captures the SAML response automatically - Your regular browser's saved passwords and extensions are **not available** (sandboxed instance) **Recommended: Automatic Download** ```yaml providers: my-saml: kind: aws/saml driver: Browser download_browser_driver: true # Auto-downloads Chromium browser (~140 MB) on first use ``` With this configuration, Atmos automatically downloads the Chromium browser binary on your first authentication attempt and stores it in your user cache directory: - macOS: `~/Library/Caches/ms-playwright/` - Linux: `~/.cache/ms-playwright/` - Windows: `%LOCALAPPDATA%\ms-playwright\` :::info Manual Installation Manual Chromium installation is **not recommended** because: - Version compatibility must be carefully managed between Atmos, saml2aws, and playwright-go - Different installation methods use different cache directories which may not be detected - The automatic download option handles everything correctly The `download_browser_driver: true` option is the simplest and most reliable approach. ::: **Note**: The Chromium browser binary does not need to be in your system PATH. Playwright manages the browser binary location automatically. **Advanced: Custom Browser Configuration** You can configure a custom browser type or executable path instead of using the automatically downloaded Chromium: ```yaml providers: my-saml: kind: aws/saml driver: Browser browser_type: msedge # Use Microsoft Edge instead of Chromium browser_executable_path: /usr/bin/microsoft-edge # Path to Edge binary ``` **Supported `browser_type` values:** - `chromium` - Default Chromium browser (default if not specified) - `firefox` - Mozilla Firefox browser - `webkit` - WebKit browser engine - `chrome` - Google Chrome (stable) - `chrome-beta`, `chrome-dev`, `chrome-canary` - Chrome development channels - `msedge` - Microsoft Edge (stable) - `msedge-beta`, `msedge-dev`, `msedge-canary` - Edge development channels **`browser_executable_path`** - Optional path to a browser executable. When specified with `browser_type`, allows using a system-installed browser instead of Playwright's managed version. :::warning Custom Browser Support Using custom browsers requires: - The browser must be installed and at the specified path - Browser version must be compatible with Playwright's automation protocol - `download_browser_driver: true` should still be set to ensure Playwright drivers are available **Most users should use the default Chromium** (with `download_browser_driver: true`) for best reliability. ::: - **`GoogleApps`**: Uses Google Apps SAML API (no browser automation needed) - **`Okta`**: Uses Okta SAML API (no browser automation needed) - **`ADFS`**: Uses Active Directory Federation Services API (no browser automation needed) **Auto-Detection**: If `driver` is not specified, Atmos automatically selects the best option based on your SAML URL and whether browser drivers are available. ### GitHub Actions OIDC ```yaml auth: providers: github-oidc: kind: github/oidc region: us-east-1 # Required spec: audience: sts.us-east-1.amazonaws.com identities: github-deploy: kind: aws/assume-role default: true via: provider: github-oidc principal: assume_role: arn:aws:iam::123456789012:role/GitHubActionsRole ``` ### AWS User (Break-glass) AWS User identities support static IAM user credentials with optional multi-factor authentication (MFA). #### Basic Configuration ```yaml auth: identities: emergency-user: kind: aws/user credentials: access_key_id: !env EMERGENCY_AWS_ACCESS_KEY_ID secret_access_key: !env EMERGENCY_AWS_SECRET_ACCESS_KEY region: us-east-1 ``` Alternatively, store credentials in the system keychain: ```yaml auth: identities: emergency-user: kind: aws/user credentials: region: us-east-1 ``` Then run `atmos auth user configure` to configure the credentials on the keychain. See [user configure](/cli/commands/auth/user/configure) for details. #### Multi-Factor Authentication (MFA) for AWS AWS User identities support MFA devices for enhanced security. When an MFA device ARN is configured, Atmos will prompt for a time-based one-time password (TOTP) during authentication. :::note This section describes MFA implementation for AWS IAM users. Other cloud providers will have their own MFA implementations in future releases. ::: **Configuration with MFA:** ```yaml auth: identities: emergency-user: kind: aws/user credentials: access_key_id: !env EMERGENCY_AWS_ACCESS_KEY_ID secret_access_key: !env EMERGENCY_AWS_SECRET_ACCESS_KEY mfa_arn: arn:aws:iam::123456789012:mfa/username region: us-east-1 ``` The `mfa_arn` can be specified in several ways: ```yaml # Direct ARN (suitable for shared team configurations) mfa_arn: arn:aws:iam::123456789012:mfa/username # Environment variable reference (suitable for personal configurations) mfa_arn: !env AWS_MFA_ARN # Stored in keychain (via atmos auth user configure) # The mfa_arn field can be omitted from YAML if stored in keychain ``` **Finding Your MFA Device ARN:** 1. Log into AWS Console 2. Navigate to IAM → Users → \[Your Username] 3. Click the "Security credentials" tab 4. Find "Assigned MFA device" section 5. Copy the ARN (format: `arn:aws:iam::ACCOUNT_ID:mfa/USERNAME`) **Authentication Flow with MFA:** When you authenticate with an MFA-enabled identity: ```bash $ atmos auth whoami ? Multiple default identities found. Please choose one: ▸ dev-admin prod-admin staging-admin ``` ## Disabling Authentication In CI/CD environments, you may want to disable Atmos-managed authentication and use native cloud provider credentials instead. ```bash # Disable via CLI flag atmos terraform plan mycomponent --stack=dev --identity=false # Disable via environment variable export ATMOS_IDENTITY=false atmos terraform plan mycomponent --stack=dev ``` **Recognized disable values:** `false`, `0`, `no`, `off` (case-insensitive) When disabled, Atmos skips all identity authentication and falls back to standard cloud provider SDK credential resolution (AWS, Azure, or GCP). ## Environment Variable Formats The `atmos auth env` command outputs credentials in multiple formats: ### Bash Format ```bash atmos auth env --format bash # Output: export AWS_ACCESS_KEY_ID="AKIA..." export AWS_SECRET_ACCESS_KEY="..." export AWS_SESSION_TOKEN="..." ``` ### JSON Format ```bash atmos auth env --format json # Output: { "AWS_ACCESS_KEY_ID": "AKIA...", "AWS_SECRET_ACCESS_KEY": "...", "AWS_SESSION_TOKEN": "..." } ``` ### Dotenv Format ```bash atmos auth env --format dotenv # Output: AWS_ACCESS_KEY_ID=AKIA... AWS_SECRET_ACCESS_KEY=... AWS_SESSION_TOKEN=... ``` ## CI/CD Integration ### GitHub Actions ```yaml name: Deploy Infrastructure on: [push] jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@v6 - name: Configure AWS credentials via OIDC uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole aws-region: us-east-1 - name: Deploy with Atmos (using GitHub OIDC credentials) env: ATMOS_IDENTITY: false # Disable Atmos auth, use GitHub-provided credentials run: | atmos terraform apply mycomponent --stack=prod ``` ### GitLab CI ```yaml deploy: script: - atmos auth validate - atmos terraform apply myapp -s prod ``` ## Workflows Integration Use Atmos Auth in workflows: ```yaml # atmos.yaml workflows section workflows: deploy: description: Deploy with authentication steps: - name: validate-auth command: atmos auth validate - name: deploy-dev command: atmos terraform apply myapp -s dev identity: dev-admin - name: deploy-prod command: atmos terraform apply myapp -s prod identity: prod-admin ``` ## Troubleshooting ### Common Issues **Configuration Validation Errors** ```bash atmos auth validate --verbose ``` **Authentication Failures** ```bash # Check current status atmos auth whoami # Re-authenticate atmos auth login --identity # Check with verbose output atmos auth login --identity --verbose ``` **Permission Errors** ```bash # Verify identity configuration atmos auth validate # Check assumed role/permissions atmos auth exec --identity -- aws sts get-caller-identity ``` **Environment Variable Issues** ```bash # Check what variables are set atmos auth env --identity # Test environment atmos auth exec --identity -- env | grep AWS ``` ### Debug Mode Enable debug logging for detailed troubleshooting: ```bash # Verbose CLI output atmos auth validate --verbose atmos auth login --identity --verbose # Set log level explicitly ATMOS_LOG_LEVEL=Debug atmos auth whoami ``` ## Security Best Practices - Never commit credentials to version control - Use environment variables for sensitive data: `!env VAR_NAME` - Regularly rotate credentials - Use least-privilege access - Validate configurations regularly: `atmos auth validate` - Use shorter session durations for high-security environments --- ## atmos auth user Manage static cloud user credentials (currently AWS IAM users) and store them securely in your system keychain. These credentials can then be referenced by other `atmos auth` commands when an identity requires static keys. ## Subcommands --- ## atmos auth user configure Use this command whenever an identity is backed by a **user** credential source and you need to configure those credentials interactively. The values are stored securely in your OS keychain and can be referenced by other `atmos auth` commands (`login`, `env`, `exec`). > **Note** > > Currently, only > > **AWS IAM users** > > ( > > `kind: aws/user` > > ) are supported. You'll be prompted for the AWS Access Key ID, Secret Access Key, and an optional MFA device ARN, which will be stored securely in your system keychain. ## Usage ```shell atmos auth user configure ``` ## Description This command provides an interactive way to configure AWS IAM user credentials and store them securely in your system keychain. **What it prompts for:** - **AWS Access Key ID** (required) - Your IAM user's access key identifier - **AWS Secret Access Key** (required, masked input) - Your IAM user's secret key - **AWS User MFA ARN** (optional) - Your MFA device ARN for enhanced security - **Session Duration** (optional, default: 12h) - How long session tokens remain valid **Storage:** - Credentials are stored in your OS keychain (macOS Keychain, GNOME Keyring, Windows Credential Manager) - The storage key matches your `aws/user` identity name from `atmos.yaml` - Only identities with `kind: aws/user` are selectable ## Examples ```shell # Configure credentials for an aws/user identity atmos auth user configure # Example interactive session: ? Choose an identity to configure: emergency-user ? AWS Access Key ID: AKIAIOSFODNN7EXAMPLE ? AWS Secret Access Key: **************************************** ? AWS User MFA ARN (optional): arn:aws:iam::123456789012:mfa/username ? Session Duration (optional, default: 12h): 24h ✔ Saved credentials to keyring ✔ Session duration configured: 24h ``` ## Multi-Factor Authentication (MFA) for AWS :::note This section describes MFA implementation for AWS IAM users. Other cloud providers will have their own MFA implementations in future releases. ::: ### Why Configure MFA ARN? When you configure an AWS MFA device ARN, Atmos will require a time-based one-time password (TOTP) during authentication. This provides: - **Enhanced security** - Two-factor authentication for privileged access - **Compliance** - Meet security requirements for production access - **Defense-in-depth** - Protection against compromised credentials ### Finding Your MFA Device ARN 1. Log into the AWS Console 2. Navigate to **IAM** → **Users** → **\[Your Username]** 3. Click the **"Security credentials"** tab 4. In the **"Assigned MFA device"** section, copy the ARN 5. The ARN format is: `arn:aws:iam::ACCOUNT_ID:mfa/USERNAME` ### Authentication Flow with MFA After configuring an MFA device ARN, when you authenticate: ```bash $ atmos auth login --identity emergency-user ╭─────────────────────────────────────────────────────╮ │ Enter MFA Token │ ├─────────────────────────────────────────────────────┤ │ MFA Device: arn:aws:iam::123456789012:mfa/user │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ 123456 │ │ │ └──────────────────────────────────────────────┘ │ ╰─────────────────────────────────────────────────────╯ ``` Atmos will: 1. Retrieve your long-lived credentials from the keychain 2. Prompt for a 6-digit TOTP code from your authenticator app (Google Authenticator, Authy, etc.) 3. Call AWS STS `GetSessionToken` with your credentials, MFA ARN, and TOTP code 4. Store temporary session credentials (valid for configured duration, default: 12 hours) ### Security Considerations - **MFA ARN is not a secret** - It's an identifier, not a credential - **TOTP codes are never stored** - You must enter them for each authentication session - **Session tokens are cached** - Valid for configured duration (default: 12h, max: 36h with MFA) - **Long-lived credentials stay in keychain** - Never written to plain text files ### Alternative: MFA ARN in YAML Instead of storing the MFA ARN in the keychain, you can configure it in `atmos.yaml`: ```yaml auth: identities: emergency-user: kind: aws/user credentials: # Omit access_key_id and secret_access_key to use keychain mfa_arn: arn:aws:iam::123456789012:mfa/username # OR use environment variable mfa_arn: !env AWS_MFA_ARN region: us-east-1 ``` This is useful when: - Multiple team members share the same identity configuration - MFA device ARN is organization-standard - You want version-controlled authentication configuration ## Session Duration Configuration The interactive command prompts for session duration, or you can configure it in YAML: ```yaml auth: identities: emergency-user: kind: aws/user session: duration: "24h" # Formats: integers (seconds), Go durations ("1h"), or days ("1d") ``` **AWS limits**: 15m-12h (no MFA) or 15m-36h (with MFA). Default: 12h. YAML configuration takes precedence over keyring. ## Notes - Learn how to [configure an `aws/user` identity](/cli/configuration/auth/identities#user-break-glass) in `atmos.yaml` before running this command - See the [AWS User configuration](/cli/configuration/auth/identities#user-break-glass) for MFA configuration and usage details --- ## atmos auth validate Use this command to validate your `auth` configuration in `atmos.yaml`. ## Usage ```shell atmos auth validate [--verbose] ``` ## Arguments - **n/a** No positional arguments. ## Examples ```shell # Basic validation atmos auth validate # Verbose output atmos auth validate --verbose ``` ## Flags - **`--verbose` (alias `-v`)** Enable verbose output. ## Environment variables - **`ATMOS_AUTH_VALIDATE_VERBOSE`** Set to `true` to enable verbose output by default. --- ## atmos auth whoami Display the current authentication context, including the effective identity and associated credentials. Useful for verifying which account is active and troubleshooting identity or session issues. _\[Video: atmos auth whoami]_ ## Usage ```shell atmos auth whoami [--identity ] [--output json] ``` ## Examples ```shell # Show current identity (human-friendly) atmos auth whoami # Interactively select identity to inspect atmos auth whoami --identity # Show a specific identity atmos auth whoami --identity dev-admin # Use short form of identity flag atmos auth whoami -i dev-admin # Interactive selection with short form atmos auth whoami -i # JSON output (redacts home directory from paths) atmos auth whoami --output json ``` ## Arguments - **n/a** No positional arguments. ## Flags - **`--identity` (alias `-i`)** Specify the target identity to inspect. This flag has three modes: - **With value** (`--identity admin`): Use the specified identity - **Without value** (`--identity`): Show interactive selector to choose identity - **Omitted**: Use the default identity configured in `atmos.yaml`, or prompt if no default is set **Environment variables:** `ATMOS_IDENTITY` or `IDENTITY` (checked in that order) - **`--output` (alias `-o`)** Output format. Supported: `json` . Default is human-readable. ## Environment variables - **`ATMOS_IDENTITY`** Default identity when `--identity` is not provided. - **`ATMOS_AUTH_WHOAMI_OUTPUT`** Set to `json` for JSON output by default. --- ## atmos aws compliance Commands for generating compliance posture reports against industry frameworks. > ⚠️ Experimental ## Usage ```shell atmos aws compliance [flags] ``` ## Subcommands --- ## atmos aws compliance report Generate compliance posture reports against industry frameworks. Retrieves compliance status from AWS Security Hub enabled standards, maps failing controls to Atmos components, and generates reports with remediation guidance. > ⚠️ Experimental ## Description The `atmos aws compliance report` command retrieves compliance status from AWS Security Hub enabled standards, maps failing controls to Atmos components that manage the affected resources, and generates reports. It supports multiple compliance frameworks and produces actionable reports that identify exactly which Terraform components need changes to achieve compliance. Use it for: - **Compliance Audits**: Generate reports showing your posture against specific compliance frameworks - **Remediation Planning**: Identify which Atmos components need changes to fix failing controls - **Multi-Framework Assessment**: Evaluate your infrastructure against multiple standards simultaneously - **Continuous Compliance**: Integrate into CI/CD pipelines to track compliance drift over time ## Usage ```shell atmos aws compliance report [flags] ``` ## Flags - **`--stack, -s`** Filter compliance results to a specific Atmos stack (e.g., `prod-us-east-1` ). - **`--framework`** Compliance framework to evaluate against: `cis-aws` , `pci-dss` , `soc2` , `hipaa` , `nist` . When omitted, reports on all enabled frameworks. - **`--format, -f`** Output format: `markdown` , `json` , `yaml` , `csv` (default: `markdown` ). - **`--file`** Write output to a file instead of stdout. Creates parent directories if they don't exist. - **`--controls`** Comma-separated list of specific control IDs to evaluate (e.g., `CIS.1.1,CIS.1.2` ). - **`--identity, -i`** Atmos Auth identity for AWS credentials (overrides `aws.security.identity` config). - **`--ai`** Enable AI-powered analysis of the compliance report. The global `--ai` flag captures the report output and sends it to the configured AI provider for a summary with remediation guidance for each failing control. ## Examples ### Basic Usage ```shell # CIS AWS Foundations Benchmark report atmos aws compliance report --framework cis-aws --stack prod-us-east-1 # PCI DSS compliance status atmos aws compliance report --framework pci-dss # All frameworks for a stack atmos aws compliance report --stack prod-us-east-1 ``` ### Output Formats ```shell # Output as JSON for automation atmos aws compliance report --framework cis-aws --format json # Markdown report for documentation atmos aws compliance report --framework soc2 --stack prod-us-east-1 --format markdown ``` ### AI-Powered Analysis ```shell # Get AI remediation guidance for failing controls atmos aws compliance report --ai # AI analysis for a specific framework atmos aws compliance report --framework cis-aws --ai ``` ### Saving to a File ```shell # Save markdown report to a file atmos aws compliance report --framework hipaa --file hipaa-report.md # Save JSON report to a file atmos aws compliance report --framework pci-dss --format json --file pci-report.json # Save to a nested directory (created automatically) atmos aws compliance report --framework cis-aws --format json --file reports/compliance/cis.json ``` ### Targeted Evaluation ```shell # Check specific controls atmos aws compliance report --framework cis-aws --controls CIS.1.1,CIS.1.2,CIS.2.1 # NIST framework for production atmos aws compliance report --framework nist --stack prod-us-east-1 # Multiple stacks comparison for stack in dev-us-east-1 staging-us-east-1 prod-us-east-1; do echo "=== $stack ===" atmos aws compliance report --framework cis-aws --stack "$stack" --format json --file "compliance-${stack}.json" done ``` ### CI/CD Integration ```shell # Compliance gate in pipeline atmos aws compliance report --framework pci-dss --stack prod-us-east-1 --format json --file compliance.json if jq -e '.failing_controls | length > 0' compliance.json; then echo "PCI DSS compliance failures detected" exit 1 fi # Generate compliance report as a deployment artifact atmos aws compliance report --stack prod-us-east-1 --file compliance-report.md ``` ## Supported Frameworks - **`cis-aws`** CIS AWS Foundations Benchmark. Industry-standard security configuration guidelines for AWS accounts. - **`pci-dss`** Payment Card Industry Data Security Standard. Required for organizations that handle credit card data. - **`soc2`** SOC 2 (Service Organization Control 2). Trust service criteria for security, availability, processing integrity, confidentiality, and privacy. - **`hipaa`** Health Insurance Portability and Accountability Act. Required for organizations handling protected health information (PHI). - **`nist`** NIST 800-53. Security and privacy controls for federal information systems and organizations. ## Configuration Configure the compliance command in your `atmos.yaml` under the `aws.security` section: ```yaml aws: security: enabled: true identity: "security-readonly" # Atmos Auth identity region: "us-east-2" # Security Hub aggregation region frameworks: - cis-aws - pci-dss ``` ## Related Commands --- ## atmos aws ecr login Login to AWS Elastic Container Registry (ECR) using a named integration, an identity's linked integrations, or explicit registry URLs. Supports both private ECR (`aws/ecr`) and ECR Public (`aws/ecr-public`) registries. This command writes Docker credentials to the standard Docker config location. ## Usage ```shell atmos aws ecr login [integration] [flags] ``` ## Examples ```shell # Login using a named integration (private ECR) atmos aws ecr login dev/ecr/primary # Login to ECR Public (public.ecr.aws) using ambient AWS credentials atmos aws ecr login --public # Login to ECR Public using a specific identity atmos aws ecr login --public --identity dev-admin # Login using an identity's linked integrations atmos aws ecr login --identity dev-admin # Pick an identity interactively (requires a TTY) atmos aws ecr login --identity # Login with explicit registry URL (uses current AWS credentials) atmos aws ecr login --registry 123456789012.dkr.ecr.us-east-1.amazonaws.com # Login to multiple explicit registries atmos aws ecr login \ --registry 123456789012.dkr.ecr.us-east-1.amazonaws.com \ --registry 987654321098.dkr.ecr.us-west-2.amazonaws.com ``` ## Arguments - **`integration`** Name of the integration to use for ECR login. The integration must be configured in `auth.integrations` with `kind: aws/ecr` or `kind: aws/ecr-public`. When provided, Atmos authenticates the integration's linked identity and logs into the configured registry. ## Flags - **`--public`** Log in to AWS ECR Public (`public.ecr.aws`) directly, without a named integration. By default this uses ambient AWS credentials (the AWS SDK default credential chain: environment variables, shared config/profile, SSO, IMDS/IRSA/ECS) — ideal for CI, where the runner already has credentials. Combine with `--identity` to use a specific Atmos identity's credentials instead. Authentication is always performed in `us-east-1`. Cannot be combined with an integration argument or `--registry`. - **`--identity` (alias `-i`)** Identity name whose linked integrations should be executed. All `aws/ecr` and `aws/ecr-public` integrations that reference this identity will be triggered. This authenticates the identity first, then executes all its linked integrations. When combined with `--public`, the identity's credentials are used to log in to ECR Public. Passing `--identity` **without a value** opens an interactive picker to choose an identity (the same selector used by `atmos auth login`). This requires an interactive terminal (TTY); in CI or other non-interactive contexts it errors instead of prompting, so pass an explicit name there (`--identity `). - **`--registry` (alias `-r`)** Explicit ECR registry URL(s) for ad-hoc login. This mode uses the current AWS credentials from the environment (not Atmos identities). Can be specified multiple times for multiple registries. Format: `{account_id}.dkr.ecr.{region}.amazonaws.com` ## Configuration ECR integrations are configured in `atmos.yaml` under `auth.integrations`: ```yaml auth: providers: company-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://company.awsapps.com/start/ identities: dev-admin: kind: aws/permission-set via: provider: company-sso principal: name: AdministratorAccess account: dev # Integrations derive credentials from identities integrations: dev/ecr/primary: kind: aws/ecr via: identity: dev-admin # Which identity provides AWS credentials spec: auto_provision: true # Auto-trigger on identity login (default: true) registry: account_id: "123456789012" region: us-east-2 dev/ecr/secondary: kind: aws/ecr via: identity: dev-admin spec: registry: account_id: "123456789012" region: us-west-2 ``` ### ECR Public Configuration For a one-off ECR Public login, you don't need any configuration — just run `atmos aws ecr login --public`, which uses ambient AWS credentials (or pass `--identity ` to use a specific identity). This is the simplest option for CI. To have ECR Public login happen automatically on `atmos auth login`, configure an `aws/ecr-public` integration kind. No registry block is needed since ECR Public is always `public.ecr.aws`: ```yaml auth: integrations: ecr-public: kind: aws/ecr-public via: identity: dev-admin spec: auto_provision: true ``` ### Integration Configuration Options #### `aws/ecr` (Private ECR) | Field | Required | Default | Description | |-------|----------|---------|-------------| | `kind` | Yes | - | Must be `aws/ecr` for private ECR integrations | | `via.identity` | Yes | - | Name of identity providing AWS credentials | | `spec.auto_provision` | No | `true` | Auto-trigger on identity login | | `spec.registry.account_id` | Yes | - | AWS account ID for the ECR registry | | `spec.registry.region` | Yes | - | AWS region for the ECR registry | #### `aws/ecr-public` (ECR Public) | Field | Required | Default | Description | |-------|----------|---------|-------------| | `kind` | Yes | - | Must be `aws/ecr-public` for ECR Public integrations | | `via.identity` | Yes | - | Name of identity providing AWS credentials | | `spec.auto_provision` | No | `true` | Auto-trigger on identity login | ECR Public does not require a `spec.registry` block. The registry is always `public.ecr.aws` and authentication is always performed in `us-east-1`. ## How It Works ### Named Integration Mode When you specify an integration name: 1. Atmos looks up the integration config from `auth.integrations` 2. Authenticates the linked identity (via `via.identity`) 3. For `aws/ecr`: calls `ecr:GetAuthorizationToken` using the identity's credentials 4. For `aws/ecr-public`: calls `ecr-public:GetAuthorizationToken` in `us-east-1` 5. Writes credentials to Docker config (`~/.docker/config.json`) ### Identity Mode When you use `--identity`: 1. Atmos finds all integrations that reference the specified identity 2. Authenticates the identity 3. Executes each linked integration 4. Each integration writes its credentials to Docker config ### Explicit Registry Mode When you use `--registry`: 1. Atmos uses the current AWS credentials from the environment 2. Parses the registry URL to extract account ID and region 3. Calls `ecr:GetAuthorizationToken` 4. Writes credentials to Docker config (`~/.docker/config.json`) ## Credential Storage ECR credentials are written to `~/.docker/config.json` by default, the standard Docker config location. This means: - Docker commands work immediately after login without additional configuration - Credentials are merged with existing entries in your Docker config - Respects `DOCKER_CONFIG` environment variable if set If you need isolated credentials, set `DOCKER_CONFIG` before running the command: ```bash export DOCKER_CONFIG=~/.config/atmos/docker atmos aws ecr login dev/ecr/primary ``` ## Auto-Provisioning When `auto_provision` is `true` (the default), ECR integrations are automatically triggered when you authenticate with their linked identity: ```shell $ atmos auth login dev-admin Authenticating with identity: dev-admin Opening browser for SSO authentication... Successfully authenticated as dev-admin ✓ ECR login: 123456789012.dkr.ecr.us-east-2.amazonaws.com (expires in 11h59m) ✓ ECR login: 123456789012.dkr.ecr.us-west-2.amazonaws.com (expires in 11h59m) ``` To disable auto-provisioning for an integration, set `auto_provision: false`: ```yaml integrations: dev/ecr/optional: kind: aws/ecr via: identity: dev-admin spec: auto_provision: false # Only triggered via explicit `atmos aws ecr login` command registry: account_id: "123456789012" region: eu-west-1 ``` ## Error Handling - **Named integration failures**: Return error to user (fatal) - **Auto-provisioned integration failures**: Log warning and continue (non-fatal) - **Invalid registry URL**: Return error with supported format ECR integration failures during `atmos auth login` are logged but don't block authentication. Your identity credentials succeed even if ECR login fails. ## Notes - ECR tokens expire after approximately 12 hours (AWS-enforced) - The actual expiration time is displayed when login succeeds - **Private ECR** (`aws/ecr`): requires `ecr:GetAuthorizationToken` IAM permission - **ECR Public** (`aws/ecr-public`): requires `ecr-public:GetAuthorizationToken` and `sts:GetServiceBearerToken` IAM permissions. Authentication is always performed in `us-east-1`. ECR Public is only available in `us-east-1` and `us-west-2`. - China and GovCloud regions are not supported for either integration kind ## See Also - [Auth Login Command](/cli/commands/auth/login) - Authenticate with identities (triggers auto-provisioned integrations) - [ECR Authentication Tutorial](/tutorials/ecr-authentication) - Step-by-step guide - [Auth Configuration](/cli/configuration/auth) - Configure providers, identities, and integrations --- ## atmos aws eks token Generate a short-lived EKS bearer token for kubectl authentication. This command is designed as a kubectl exec credential plugin and is automatically configured in kubeconfig files generated by `atmos auth login`. ## Usage ```shell atmos aws eks token --cluster-name --region [flags] ``` ## Examples ```shell # Generate token for a cluster (typically called by kubectl automatically) atmos aws eks token --cluster-name my-cluster --region us-east-2 # Generate token using a specific identity atmos aws eks token --cluster-name my-cluster --region us-east-2 --identity dev-admin # Test token generation manually atmos aws eks token --cluster-name dev-cluster --region us-west-2 | jq . ``` ## Arguments - **n/a** No positional arguments. ## Flags - **`--cluster-name` (required)** The name of the EKS cluster to generate a token for. This must match the cluster name in AWS. - **`--region` (required)** The AWS region where the EKS cluster is located. - **`--identity` (alias `-i`)** Identity name to authenticate with for token generation. If omitted, Atmos uses the default identity (single identity auto-selected) or the `ATMOS_IDENTITY` environment variable. ## How It Works This command generates an EKS bearer token using the same mechanism as `aws eks get-token`, but without requiring the AWS CLI: 1. Atmos authenticates the specified identity to obtain AWS credentials 2. Creates a pre-signed STS `GetCallerIdentity` URL with the cluster name injected as the `x-k8s-aws-id` header 3. Base64url-encodes the URL and prefixes it with `k8s-aws-v1.` 4. Outputs the token as a Kubernetes `ExecCredential` JSON object to stdout ### ExecCredential Output The command outputs a JSON object that kubectl understands: ```json { "apiVersion": "client.authentication.k8s.io/v1beta1", "kind": "ExecCredential", "status": { "expirationTimestamp": "2025-01-15T12:15:00Z", "token": "k8s-aws-v1.aHR0cHM6Ly9zdHMu..." } } ``` ### Kubeconfig Integration When you authenticate with an identity that has an EKS integration, `atmos auth login` automatically generates a kubeconfig entry that uses this command as an exec credential plugin: ```yaml users: - name: atmos-eks-dev-admin user: exec: apiVersion: client.authentication.k8s.io/v1beta1 command: atmos args: - aws - eks - token - --cluster-name - dev-cluster - --region - us-east-2 - --identity - dev-admin ``` This means kubectl automatically calls `atmos aws eks token` whenever it needs a fresh token, providing seamless authentication without manual token management. ## Notes - Tokens expire after approximately 15 minutes (AWS-enforced STS pre-signed URL expiration) - This command is designed to be called by kubectl, not manually - The command suppresses usage errors since kubectl invokes it automatically - Required IAM permission: `sts:GetCallerIdentity` (typically allowed by default) - The token is generated locally using a pre-signed URL; no EKS API calls are made ## See Also - [Auth Login Command](/cli/commands/auth/login) — Authenticate with identities and auto-provision kubeconfig - [EKS Kubeconfig Authentication Tutorial](/tutorials/eks-kubeconfig-authentication) — Step-by-step EKS setup guide - [AWS EKS Update Kubeconfig](/cli/commands/aws/eks/update-kubeconfig) — Download kubeconfig from EKS clusters - [Auth Configuration](/cli/configuration/auth) — Configure providers, identities, and integrations --- ## atmos aws eks Commands for managing AWS EKS (Elastic Kubernetes Service) clusters. ## Usage ```shell atmos aws eks [flags] ``` ## Subcommands --- ## atmos aws eks update-kubeconfig Use this command to download `kubeconfig` from an EKS cluster and save it to a file. ```shell atmos aws eks update-kubeconfig [options] ``` This command downloads kubeconfig from an EKS cluster and saves it to a file. It supports multiple execution modes: 1. **CLI parameters only**: If all the required parameters (cluster name and AWS profile/role) are provided on the command-line, then Atmos executes the command without requiring the `atmos.yaml` CLI config and context. ```shell atmos aws eks update-kubeconfig --profile= --name= ``` 2. **Component and stack**: If `component` and `stack` are provided on the command-line, then Atmos executes the command using the `atmos.yaml` CLI config and stack's context by searching for the following settings: - `components.helmfile.cluster_name_template` in the `atmos.yaml` CLI config (and calculates the `--name` parameter using the Go template; the deprecated `components.helmfile.cluster_name_pattern` token syntax also works) - `components.helmfile.helm_aws_profile_pattern` in the `atmos.yaml` CLI config (and calculates the `--profile` parameter using the pattern) - `components.helmfile.kubeconfig_path` in the `atmos.yaml` CLI config the variables for the component in the provided stack - `region` from the variables for the component in the stack ```shell atmos aws eks update-kubeconfig -s ``` 3. **Combination**: Provide a component and a stack, and override other parameters on the command line. ```shell atmos aws eks update-kubeconfig -s --kubeconfig= --region=us-east-1 ``` 4. **Integration mode**: Use a named integration from `auth.integrations` to authenticate via the Atmos auth system and generate kubeconfig using the Go SDK (no AWS CLI required). ```shell atmos aws eks update-kubeconfig --integration=dev/eks/primary ``` 5. **Identity mode**: Use an Atmos identity directly with explicit cluster parameters. This authenticates via the auth system and uses the Go SDK. ```shell atmos aws eks update-kubeconfig --name= --region= --identity= ``` :::info Refer to [Update kubeconfig](https://docs.aws.amazon.com/cli/latest/reference/eks/update-kubeconfig.html) for more information ::: :::tip Run `atmos aws eks update-kubeconfig --help` to see all the available options ::: ## Examples ```shell # Using component and stack atmos aws eks update-kubeconfig -s # Using explicit CLI parameters (requires AWS CLI) atmos aws eks update-kubeconfig --profile= --name= # Using a named integration (no AWS CLI required) atmos aws eks update-kubeconfig --integration=dev/eks/primary # Using an identity with explicit parameters (no AWS CLI required) atmos aws eks update-kubeconfig --name=dev-cluster --region=us-east-2 --identity=dev-admin # Override parameters with component and stack atmos aws eks update-kubeconfig -s --kubeconfig= --region= # Additional options atmos aws eks update-kubeconfig --role-arn atmos aws eks update-kubeconfig --alias atmos aws eks update-kubeconfig --dry-run=true atmos aws eks update-kubeconfig --verbose=true ``` ## Arguments - **`component` (optional)** Atmos component. ## Flags - **`--stack` / `-s` (optional)** Atmos stack. - **`--profile` (optional)** AWS profile to use to authenticate to the EKS cluster. - **`--role-arn` (optional)** AWS IAM role ARN to use to authenticate to the EKS cluster. - **`--name` (optional)** EKS cluster name. - **`--region` (optional)** AWS region. - **`--kubeconfig` (optional)** `kubeconfig` filename to append with the configuration. - **`--alias` (optional)** Alias for the cluster context name. Defaults to match cluster ARN. - **`--dry-run` (optional)** Print the merged kubeconfig to stdout instead of writing it to the specified file. - **`--verbose` (optional)** Print more detailed output when writing the kubeconfig file, including the appended entries. - **`--integration` (optional)** Name of a configured integration from `auth.integrations` (must be `kind: aws/eks`). When specified, Atmos authenticates the integration's linked identity and uses the Go SDK to describe the cluster and generate kubeconfig. This does not require the AWS CLI. - **`--identity` (optional)** Atmos identity to authenticate with. When used with `--name` (and without `--profile` or `--role-arn`), Atmos authenticates via the auth system and uses the Go SDK directly. This does not require the AWS CLI. ## Configuration ### Integration Mode EKS integrations are configured in `atmos.yaml` under `auth.integrations`: ```yaml auth: providers: company-sso: kind: aws/iam-identity-center region: us-east-1 start_url: https://company.awsapps.com/start/ identities: dev-admin: kind: aws/permission-set via: provider: company-sso principal: name: AdministratorAccess account: dev integrations: dev/eks/primary: kind: aws/eks via: identity: dev-admin spec: cluster: name: dev-cluster region: us-east-2 alias: dev-eks ``` ```shell atmos aws eks update-kubeconfig --integration=dev/eks/primary ``` See the [EKS Kubeconfig Authentication Tutorial](/tutorials/eks-kubeconfig-authentication) for detailed configuration examples. ## See Also - [AWS EKS Token Command](/cli/commands/aws/eks-token) — Generate EKS bearer tokens for kubectl - [EKS Kubeconfig Authentication Tutorial](/tutorials/eks-kubeconfig-authentication) — Step-by-step EKS setup guide - [Auth Login Command](/cli/commands/auth/login) — Authenticate with identities (triggers auto-provisioned integrations) --- ## atmos aws security Commands for analyzing AWS security findings and mapping them to Atmos components. > ⚠️ Experimental ## Usage ```shell atmos aws security [flags] ``` ## Subcommands --- ## atmos aws security analyze Analyze AWS security findings from Security Hub, Config, Inspector, and GuardDuty, then map them to the Atmos components and stacks that manage the affected resources. Generates remediation reports with optional AI-powered analysis. > ⚠️ Experimental ## Description The `atmos aws security analyze` command connects to AWS security services via Atmos Auth, retrieves security findings, and maps them to the Terraform/Atmos components that manage the affected resources. By default, it works without any AI provider. When the `--ai` flag is passed, it uses the configured AI provider to analyze each finding and generate remediation guidance with concrete code changes. Use it for: - **Security Posture Review**: Get a prioritized view of security findings mapped to your Atmos components and stacks - **Remediation Planning**: Use `--ai` for AI-generated code changes to fix security issues in your Terraform components - **CI/CD Integration**: Export findings as JSON or CSV for automated security gates in deployment pipelines - **Compliance Reporting**: Filter findings by severity, source, or compliance framework for targeted reports ## Usage ```shell atmos aws security analyze [flags] ``` ## Flags - **`--stack, -s`** Filter findings to a specific Atmos stack (e.g., `prod-us-east-1` ). - **`--component, -c`** Filter findings to a specific Atmos component (e.g., `vpc` , `eks` ). - **`--severity`** Comma-separated list of severity levels (case-insensitive): `CRITICAL` , `HIGH` , `MEDIUM` , `LOW` , `INFORMATIONAL` . Default: `critical,high` . - **`--source`** Filter findings by source service: `security-hub` , `config` , `inspector` , `guardduty` , `macie` , `access-analyzer` , `all` (default: `all` ). - **`--format, -f`** Output format: `markdown` , `json` , `yaml` , `csv` , `sarif` , `ocsf` (default: `markdown` ). `sarif` emits a SARIF 2.1.0 document compatible with GitHub code scanning and other SARIF viewers. `ocsf` emits OCSF 1.4.0 Detection Findings (cloud + vulnerability profiles) for ingestion into SIEM and security data lake platforms — Splunk, Elastic, Sumo Logic, Panther, Snowflake. - **`--file`** Write output to a file instead of stdout. Creates parent directories if they don't exist. - **`--max-findings`** Maximum number of findings to retrieve and analyze (default: `500` ). Set to `0` to fetch **all** matching findings — recommended when exporting to `--format json` , `sarif` , or `ocsf` so downstream tooling (SIEM, dashboards) sees complete data. When the limit is reached and more findings exist, a warning is logged so truncation is never silent. - **`--ai`** Enable AI-powered analysis and remediation suggestions. Requires `ai.enabled: true` in your `atmos.yaml` . - **`--region`** AWS region to query for findings (overrides `aws.security.region` config). - **`--identity, -i`** Atmos Auth identity for AWS credentials (overrides `aws.security.identity` config). - **`--framework`** Filter findings by compliance framework (e.g., `cis-aws` , `pci-dss` ). - **`--no-group`** Disable grouping of duplicate findings. By default, findings with the same title are collapsed into a summary table. Use `--no-group` to show each finding individually with full tags — useful for AI pipelines and detailed analysis. ## Examples ### Basic Usage ```shell # Analyze findings for a specific stack atmos aws security analyze --stack prod-us-east-1 # Filter by severity atmos aws security analyze --stack prod-us-east-1 --severity critical,high # Filter by source service atmos aws security analyze --stack prod-us-east-1 --source security-hub ``` ### AI-Powered Analysis ```shell # Enable AI analysis for remediation guidance atmos aws security analyze --stack prod-us-east-1 --ai # AI analysis for critical findings only atmos aws security analyze --stack prod-us-east-1 --severity critical --ai ``` ### Output Formats ```shell # Output as JSON for CI/CD integration atmos aws security analyze --stack prod-us-east-1 --format json # Output as CSV for compliance reporting atmos aws security analyze --format csv > findings.csv # Markdown report for documentation atmos aws security analyze --stack prod-us-east-1 --format markdown --severity critical,high # SARIF 2.1.0 for GitHub code scanning, Defender, and SARIF viewers atmos aws security analyze --stack prod-us-east-1 --format sarif --file findings.sarif # OCSF 1.4.0 Detection Findings for SIEM / security data lake ingestion atmos aws security analyze --stack prod-us-east-1 --format ocsf --file findings.ocsf.json ``` ### Filtering and Targeting ```shell # Findings for a specific component atmos aws security analyze --stack prod-us-east-1 --component vpc # Limit number of findings atmos aws security analyze --stack prod-us-east-1 --max-findings 20 # Filter by compliance framework atmos aws security analyze --stack prod-us-east-1 --framework cis-aws ``` ### Saving to a File ```shell # Save markdown report to a file atmos aws security analyze --stack prod-us-east-1 --file security-report.md # Save JSON findings to a file atmos aws security analyze --stack prod-us-east-1 --format json --file findings.json # Save to a nested directory (created automatically) atmos aws security analyze --stack prod-us-east-1 --format json --file reports/security/findings.json ``` ### CI/CD Integration ```shell # Security gate in pipeline atmos aws security analyze --stack prod-us-east-1 --severity critical --format json --file security.json if jq -e '.findings | length > 0' security.json; then echo "Critical security findings detected" exit 1 fi # Generate security report as a deployment artifact atmos aws security analyze --stack prod-us-east-1 \ --severity critical,high \ --file security-report.md ``` ## Configuration Configure the security command in your `atmos.yaml` under the `aws.security` section: ```yaml aws: security: enabled: true identity: "security-readonly" # Atmos Auth identity region: "us-east-2" # Security Hub aggregation region default_severity: - CRITICAL - HIGH sources: security_hub: true inspector: true guardduty: true max_findings: 50 tag_mapping: stack_tag: "atmos:stack" component_tag: "atmos:component" account_map: # For account-level findings "123456789012": "prod" "234567890123": "security" ``` To enable AI-powered analysis with the `--ai` flag, also configure your AI provider: ```yaml ai: enabled: true default_provider: bedrock providers: bedrock: model: anthropic.claude-sonnet-4-6-20250514-v1:0 base_url: us-east-1 ``` ## Related Commands --- ## atmos aws Commands for working with AWS services — security analysis, compliance reporting, EKS cluster management, and more. ## Usage ## Subcommands --- ## atmos azure acr login Login to Azure Container Registry (ACR) using a named integration, an identity's linked integrations, or explicit registry login server URLs. This command writes Docker credentials to the standard Docker config location. ## Usage ```shell atmos azure acr login [integration] [flags] ``` ## Examples ```shell # Login using a named integration atmos azure acr login dev/acr # Login using an identity's linked integrations atmos azure acr login --identity azure-dev # Pick an identity interactively (requires a TTY) atmos azure acr login --identity # Login with explicit registry login server (uses ambient Azure credentials) atmos azure acr login --registry myregistry.azurecr.io # Login to multiple explicit registries atmos azure acr login \ --registry myregistry.azurecr.io \ --registry otherregistry.azurecr.io ``` ## Arguments - **`integration`** Name of the integration to use for ACR login. The integration must be configured in `auth.integrations` with `kind: azure/acr`. When provided, Atmos authenticates the integration's linked identity and logs into the configured registry. ## Flags - **`--identity` (alias `-i`)** Identity name whose linked integrations should be executed. All `azure/acr` integrations that reference this identity will be triggered. This authenticates the identity first, then executes all its linked integrations. Passing `--identity` **without a value** opens an interactive picker to choose an identity (the same selector used by `atmos auth login`). This requires an interactive terminal (TTY); in CI or other non-interactive contexts it errors instead of prompting, so pass an explicit name there (`--identity `). - **`--registry` (alias `-r`)** Explicit ACR registry login server URL(s) for ad-hoc login. This mode uses ambient Azure credentials (the Azure SDK default credential chain: environment variables, managed identity, workload identity, Azure CLI) — not Atmos identities. Can be specified multiple times for multiple registries. Format: `{name}.azurecr.io` ## Configuration ACR integrations are configured in `atmos.yaml` under `auth.integrations`: ```yaml auth: providers: azure-device-code: kind: azure/device-code spec: tenant_id: 00000000-0000-0000-0000-000000000000 identities: azure-dev: kind: azure/subscription via: provider: azure-device-code principal: subscription_id: 11111111-1111-1111-1111-111111111111 # Integrations derive credentials from identities integrations: dev/acr: kind: azure/acr via: identity: azure-dev # Which identity provides Azure credentials spec: auto_provision: true # Auto-trigger on identity login (default: true) registry: name: myregistry ``` ### Integration Configuration Options | Field | Required | Default | Description | |-------|----------|---------|-------------| | `kind` | Yes | - | Must be `azure/acr` | | `via.identity` | Yes | - | Name of identity providing Azure credentials | | `spec.auto_provision` | No | `true` | Auto-trigger on identity login | | `spec.registry.name` | Yes | - | ACR registry name (login server = `{name}.azurecr.io`) | | `spec.registry.tenant_id` | No | identity's tenant | Override the AAD tenant used for the OAuth2 token exchange | ## How It Works ### Named Integration Mode When you specify an integration name: 1. Atmos looks up the integration config from `auth.integrations`. 2. Authenticates the linked identity (via `via.identity`). 3. Exchanges the identity's AAD access token for an ACR refresh token via the registry's `/oauth2/exchange` endpoint (the same mechanism `az acr login` uses). 4. Writes credentials to Docker config (`~/.docker/config.json`). ### Identity Mode When you use `--identity`: 1. Atmos finds all integrations that reference the specified identity. 2. Authenticates the identity. 3. Executes each linked integration. 4. Each integration writes its credentials to Docker config. ### Explicit Registry Mode When you use `--registry`: 1. Atmos uses ambient Azure credentials (the Azure SDK default credential chain). 2. Exchanges the token for each registry's login server. 3. Writes credentials to Docker config (`~/.docker/config.json`). ## Credential Storage ACR credentials are written to `~/.docker/config.json` by default, the standard Docker config location. This means: - Docker commands work immediately after login without additional configuration - Credentials are merged with existing entries in your Docker config - Respects `DOCKER_CONFIG` environment variable if set ## Auto-Provisioning When `auto_provision` is `true` (the default), ACR integrations are automatically triggered when you authenticate with their linked identity: ```shell $ atmos auth login azure-dev Authenticating with identity: azure-dev Opening browser for device code authentication... Successfully authenticated as azure-dev ✓ ACR login: myregistry.azurecr.io (expires in 2h59m) ``` To disable auto-provisioning for an integration, set `auto_provision: false`: ```yaml integrations: dev/acr: kind: azure/acr via: identity: azure-dev spec: auto_provision: false # Only triggered via explicit `atmos azure acr login` command registry: name: myregistry ``` ## Notes - ACR refresh tokens are typically long-lived; the actual expiration time (decoded from the token) is displayed when login succeeds. - No explicit IAM/RBAC permission is required beyond `AcrPull`/`AcrPush` on the target registry (or a role granting those actions) for the identity's principal. ## See Also - [Auth Login Command](/cli/commands/auth/login) — Authenticate with identities (triggers auto-provisioned integrations) - [Auth Configuration](/cli/configuration/auth) — Configure providers, identities, and integrations --- ## atmos azure aks Commands for managing Azure AKS (Azure Kubernetes Service) clusters. ## Usage ```shell atmos azure aks [flags] ``` ## Subcommands --- ## atmos azure aks update-kubeconfig Download the `kubeconfig` for an AKS cluster and save it to a file, using the Azure Go SDK. No `az` CLI or `kubelogin` binary is required. ## Usage ```shell atmos azure aks update-kubeconfig [flags] ``` This command supports two execution modes: 1. **Integration mode**: Use a named integration from `auth.integrations` to authenticate via the Atmos auth system and generate kubeconfig using the Go SDK. ```shell atmos azure aks update-kubeconfig --integration=dev/aks ``` 2. **Identity mode**: Use an Atmos identity directly with explicit cluster parameters. ```shell atmos azure aks update-kubeconfig --cluster-name= --resource-group= --identity= ``` :::tip Run `atmos azure aks update-kubeconfig --help` to see all the available options ::: ## Examples ```shell # Using a named integration atmos azure aks update-kubeconfig --integration=dev/aks # Using an identity with explicit parameters atmos azure aks update-kubeconfig --cluster-name=dev-cluster --resource-group=dev-rg --identity=azure-dev # Overriding the subscription and kubeconfig path atmos azure aks update-kubeconfig --integration=dev/aks --subscription-id=00000000-0000-0000-0000-000000000000 --kubeconfig=~/.kube/config # Setting a custom context alias atmos azure aks update-kubeconfig --integration=dev/aks --alias=dev-aks ``` ## Arguments - **n/a** No positional arguments. ## Flags - **`--integration` (optional)** Name of a configured integration from `auth.integrations` (must be `kind: azure/aks`). When specified, Atmos authenticates the integration's linked identity and uses the Go SDK to describe the cluster and generate kubeconfig. - **`--cluster-name` (optional)** The name of the AKS cluster. Required (with `--resource-group` and `--identity` ) when not using `--integration` . - **`--resource-group` (optional)** The Azure resource group containing the cluster. Required (with `--cluster-name` and `--identity` ) when not using `--integration` . - **`--subscription-id` (optional)** Azure subscription ID. Falls back to the authenticated identity's subscription when omitted. - **`--identity` (alias `-i`)** Atmos identity to authenticate with. When used with `--cluster-name` and `--resource-group`, Atmos authenticates via the auth system and uses the Go SDK directly. - **`--kubeconfig` (optional)** `kubeconfig` filename to append with the configuration. Defaults to the XDG-compliant path ( `~/.config/atmos/kube/config` ). - **`--alias` (optional)** Alias for the cluster context name. Defaults to the cluster's ARM resource ID. ## Configuration ### Integration Mode AKS integrations are configured in `atmos.yaml` under `auth.integrations`: ```yaml auth: providers: azure-device-code: kind: azure/device-code spec: tenant_id: 00000000-0000-0000-0000-000000000000 identities: azure-dev: kind: azure/subscription via: provider: azure-device-code principal: subscription_id: 11111111-1111-1111-1111-111111111111 integrations: dev/aks: kind: azure/aks via: identity: azure-dev spec: cluster: name: dev-cluster resource_group: dev-rg alias: dev-aks ``` ```shell atmos azure aks update-kubeconfig --integration=dev/aks ``` ## How It Works Unlike `az aks get-credentials`, this command never shells out to `az` or requires the `kubelogin` binary. It: 1. Calls the Azure Resource Manager `ManagedClusters.Get` API for the cluster's ARM resource ID. 2. Calls `ManagedClusters.ListClusterUserCredentials` (format `exec`) to obtain the cluster's server endpoint, CA certificate, and the AAD server application ID the cluster expects tokens to be scoped to. 3. Writes a kubeconfig entry whose exec-credential plugin invokes `atmos azure aks token` instead of `kubelogin`. Only AAD-integrated clusters are supported — clusters using local Kubernetes accounts (no AAD integration) are rejected with a clear error. ## See Also - [Azure AKS Token Command](/cli/commands/azure/azure-aks-token) — Generate AKS bearer tokens for kubectl - [Auth Login Command](/cli/commands/auth/login) — Authenticate with identities (triggers auto-provisioned integrations) --- ## atmos azure aks token Generate a short-lived AKS bearer token for kubectl authentication. This command is designed as a kubectl exec credential plugin and is automatically configured in kubeconfig files generated by `atmos azure aks update-kubeconfig`. ## Usage ```shell atmos azure aks token --cluster-name --resource-group [flags] ``` ## Examples ```shell # Generate token for a cluster (typically called by kubectl automatically) atmos azure aks token --cluster-name my-cluster --resource-group my-rg # Generate token using a specific identity atmos azure aks token --cluster-name my-cluster --resource-group my-rg --identity azure-dev # Test token generation manually atmos azure aks token --cluster-name dev-cluster --resource-group dev-rg | jq . ``` ## Arguments - **n/a** No positional arguments. ## Flags - **`--cluster-name` (required)** The name of the AKS cluster to generate a token for. - **`--resource-group` (required)** The Azure resource group containing the AKS cluster. - **`--subscription-id` (optional)** Azure subscription ID, used for logging/diagnostics. Not required for token generation itself. - **`--identity` (alias `-i`)** Identity name to authenticate with for token generation. If omitted, Atmos uses the default identity (single identity auto-selected) or the `ATMOS_IDENTITY` environment variable. ## How It Works This command outputs an already-acquired AAD access token as a Kubernetes `ExecCredential` JSON object — no external tool (`kubelogin`, `az`) is required: 1. Atmos authenticates the specified identity. 2. Azure AD access tokens are scope-bound at issuance (unlike AWS SigV4 signing), so the identity's provider (device-code, OIDC, or Azure CLI) acquires an additional token scoped to the AKS-managed AAD server application at authentication time, alongside the identity's primary ARM token. 3. This command reads that already-acquired, AKS-scoped token from the credential and returns it. ### ExecCredential Output ```json { "apiVersion": "client.authentication.k8s.io/v1beta1", "kind": "ExecCredential", "status": { "expirationTimestamp": "2026-01-15T12:15:00Z", "token": "eyJ0eXAiOiJKV1Qi..." } } ``` ### Kubeconfig Integration When you run `atmos azure aks update-kubeconfig`, Atmos automatically generates a kubeconfig entry that uses this command as an exec credential plugin: ```yaml users: - name: atmos-aks-dev-cluster-dev-rg user: exec: apiVersion: client.authentication.k8s.io/v1beta1 command: atmos args: - azure - aks - token - --cluster-name - dev-cluster - --resource-group - dev-rg - --identity=azure-dev ``` This means kubectl automatically calls `atmos azure aks token` whenever it needs a fresh token, providing seamless authentication without manual token management. ## Notes - This command is designed to be called by kubectl, not manually. - The command suppresses usage errors since kubectl invokes it automatically. - Only clusters using AKS-managed AAD (the modern default) are supported. Clusters that expect a non-default AAD server application log a warning during `update-kubeconfig` since the token may not be accepted. ## See Also - [Auth Login Command](/cli/commands/auth/login) — Authenticate with identities and auto-provision kubeconfig - [Azure AKS Update Kubeconfig](/cli/commands/azure/aks/update-kubeconfig) — Download kubeconfig from AKS clusters - [Auth Configuration](/cli/configuration/auth) — Configure providers, identities, and integrations --- ## atmos azure Commands for working with Azure services — ACR registry login, AKS cluster management, and more. ## Usage ```shell atmos azure [flags] ``` ## Subcommands --- ## atmos cast play Use this command to replay an asciicast recording in the terminal. ## Usage ```shell atmos cast play ``` ## Arguments - **`input.cast`** **Required.** Path to the asciicast recording to replay. ## Examples ```shell atmos cast play ./artifacts/demo.cast ``` ## See Also - [`atmos cast render`](/cli/commands/cast/render) — Render a cast recording to media output --- ## atmos cast render Use this command to render an asciicast recording to a publishable media file: animated GIF/MP4, a static HTML fragment, plain text with no ANSI codes, or a static PNG/JPEG image. ## Usage ```shell atmos cast render [--gif ] [--mp4 ] [--html ] [--ascii ] [--png ] [--jpg ] ``` At least one output flag is required. The animated formats (GIF, MP4) replay the recording over time. Atmos automatically installs the required managed renderers the first time you use them: `asciinema/agg` for GIF and `asciinema/agg` plus FFmpeg for MP4. The static formats (HTML, ASCII, PNG, JPEG) are rendered natively by Atmos with no external dependencies: the recording's output is laid out on a terminal cell grid, and the final content is emitted in the requested format. MP4 rendering first produces a GIF with `agg`, then converts it with FFmpeg. This preserves `agg`'s terminal-rendering quality, but the MP4 can retain GIF palette constraints such as a 256-color palette. ASCII output is a durable, diffable artifact: it is cheap to commit to git and can be consumed directly by other tooling (such as Atmos Pro) without an asciicast player. ## Arguments - **`input.cast`** **Required.** Path to the asciicast recording to render. ## Flags - **`--gif`** Write animated GIF output to this path. Atmos installs the managed `agg` renderer automatically on first use. - **`--mp4`** Write MP4 output to this path. Atmos installs the managed `agg` and FFmpeg renderers automatically on first use. - **`--html`** Write a static HTML fragment of the final terminal content to this path. The fragment contains inline-styled `` elements suitable for embedding inside a `
`
   block.
- **`--ascii`**
  Write the final terminal content as plain text (no ANSI escape codes) to this path.
- **`--png`**
  Write a static PNG image of the final terminal content to this path.
- **`--jpg`**
  Write a static JPEG image of the final terminal content to this path.

## Examples

```shell
atmos cast render ./artifacts/demo.cast --gif=./artifacts/demo.gif
atmos cast render ./artifacts/demo.cast --gif=./artifacts/demo.gif --mp4=./artifacts/demo.mp4
atmos cast render ./artifacts/demo.cast --html=./artifacts/demo.html --ascii=./artifacts/demo.ascii
atmos cast render ./artifacts/demo.cast --png=./artifacts/demo.png --jpg=./artifacts/demo.jpg
```

You can also record and render in one step with the global `--cast` flag; the format is chosen by the file extension:

```shell
atmos list stacks --cast=stacks.png
atmos about --help --cast=about-help.html
```

## See Also

- [`atmos cast play`](/cli/commands/cast/play) — Replay a cast recording in the terminal

---

## atmos cast

Use the global `--cast` flag to record Atmos command output, then use `atmos cast` commands to replay or render the recording.

## Usage

```shell
atmos  --cast=
atmos  --cast=
atmos  --cast=
atmos  --cast=
atmos  --cast=
atmos  --cast=
atmos  --cast=
atmos cast  [arguments] [flags]
```

When the output extension is a rendered format (anything other than `.cast`), Atmos records to a temporary cast and renders it when the command finishes. The static formats (`.html`, `.ascii`, `.png`, `.jpg`) are rendered natively with no external tools.

## Subcommands

## See Also

- [`--cast`](/cli/global-flags) — Record an Atmos invocation as an asciicast

---

## atmos ci

The `atmos ci` command group provides tools for working with CI/CD systems. Use these commands to check CI status, manage check runs, and integrate with GitHub Actions.

> ⚠️ Experimental

**Configure CI Integration**

Learn how to configure CI/CD integration in your `atmos.yaml`, including setting up providers (like GitHub Actions), managing workflows, environment variables, and secrets for automated infrastructure pipelines.

CI Configuration Reference[Read more](/cli/configuration/ci)

## Subcommands

## Related

- [Native CI Overview](/ci) - Feature overview and quick start
- [CI Configuration](/cli/configuration/ci) - Configure CI integration in `atmos.yaml`
- [Profiles](/cli/configuration/profiles) - Configure CI-specific profiles
- [Auth](/stacks/auth) - Configure OIDC authentication for CI

---

## atmos ci cache

The `atmos ci cache` command group restores a well-known cache directory (the toolchain install path and anything else under the Atmos cache root) at the start of a CI step and saves it back at the end, using the active CI provider's cache store — the same store that `actions/cache` uses. This warm-starts the toolchain and other regenerable artifacts across CI jobs and workflow runs.

> ⚠️ Experimental

**Configure the CI Cache**

Learn how to enable the cache, choose automatic vs. manual behavior, and customize the cache key, paths, and restore-keys in your `atmos.yaml`.

CI Cache Configuration Reference[Read more](/cli/configuration/ci/cache)

## Lifecycle

The cache lifecycle can run **in a single Atmos invocation** (automatic restore-on-start and save-on-end) or be **spread across CI steps** with the explicit subcommands:

**File:** `Spread across steps`

```
- run: atmos ci cache restore   # step 1: warm the cache
- run: atmos toolchain install  # step 2: use it (installs only what's missing)
- run: atmos ci cache save      # step 3: persist it for the next run
```

Both styles share one implementation. "Automatic" is just the same idempotent operations invoked by the process lifecycle, so manual and automatic invocations never double-execute (cache entries are write-once; an exact-key hit at restore time skips the save).

## Requirements

Saving and restoring content require running inside a supported CI provider (GitHub Actions today), which exposes the runtime cache credentials. Outside CI, these commands report that the cache is unavailable. The cache must also be enabled — see the [configuration reference](/cli/configuration/ci/cache).

## Subcommands

## Related

- [CI Cache Configuration](/cli/configuration/ci/cache) - Configure the cache in `atmos.yaml`
- [CI Configuration](/cli/configuration/ci) - Configure CI integration
- [`atmos toolchain`](/cli/commands/toolchain/usage) - Manage the toolchain that the cache warm-starts

---

## atmos ci cache delete

Delete a CI cache entry by its exact key. Deleting a key that does not exist is a no-op.

> ⚠️ Experimental

:::info Runs locally
Like [`list`](/cli/commands/ci/cache/list), `delete` administers the cache over the provider's public API and works from your workstation as well as inside CI. It needs a GitHub token (`GITHUB_TOKEN` / `ATMOS_GITHUB_TOKEN`, or `gh auth login`) and a GitHub repository (resolved from `GITHUB_REPOSITORY` or your local `git` remote).
:::

**Configure the CI Cache**

Configure the cache in your `atmos.yaml`.

CI Cache Configuration Reference[Read more](/cli/configuration/ci/cache)

## Usage

```shell
atmos ci cache delete --key=
```

## Examples

```shell
# Delete a specific cache entry
atmos ci cache delete --key="atmos-cache-linux-amd64-abc123"
```

## Flags

- **`--key` / `-k`**
  Exact cache key to delete. 
  **Required.**

## Environment Variables

- **`ATMOS_CI_CACHE_ENABLED`**
  Must be 
  `true`
   (or 
  `ci.cache.enabled: true`
   in 
  `atmos.yaml`
  ) for the command to run.
- **`ATMOS_CI_CACHE_KEY`**
  Overrides 
  `--key`
  .

## Related

- [`atmos ci cache list`](/cli/commands/ci/cache/list) - List cache entries
- [CI Cache Configuration](/cli/configuration/ci/cache) - Configure the cache

---

## atmos ci cache list

List CI cache entries, optionally filtered by key prefix. Newest entries are listed first. Supports table, JSON, YAML, CSV, and TSV output.

> ⚠️ Experimental

:::info Runs locally
`list` administers the cache over the provider's public API (the GitHub Actions caches REST API), so it works from your workstation as well as inside CI — you do not need to be running in a CI runner. It only needs a GitHub token (`GITHUB_TOKEN` / `ATMOS_GITHUB_TOKEN`, or `gh auth login`) and a GitHub repository (resolved from `GITHUB_REPOSITORY` or your local `git` remote). This is in contrast to [`save`](/cli/commands/ci/cache/save) and [`restore`](/cli/commands/ci/cache/restore), which transfer cache content and run only inside a runner.
:::

**Configure the CI Cache**

Configure the cache in your `atmos.yaml`.

CI Cache Configuration Reference[Read more](/cli/configuration/ci/cache)

## Usage

```shell
atmos ci cache list [flags]
```

## Examples

```shell
# List all cache entries
atmos ci cache list

# Filter by key prefix
atmos ci cache list --key="atmos-cache-linux-"

# JSON output for scripting
atmos ci cache list --format=json
```

## Output

In an interactive terminal, the `table` format shows a human-readable **Size** (e.g. `1.5 GB`) and **Age** (e.g. `2 hours ago`) for each entry. The machine-readable formats (`json`, `yaml`, `csv`, `tsv`) — and the `table` format when piped — keep the raw byte count and an absolute timestamp so the output stays easy to parse in scripts.

## Flags

- **`--key` / `-k`**
  Filter entries by key prefix.
- **`--format`**
  Output format: 
  `table`
   (default), 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , or 
  `tsv`
  .
- **`--delimiter`**
  Delimiter for 
  `csv`
  /
  `tsv`
   output.
- **`--max-columns`**
  Maximum number of columns to display in table format (
  `0`
   \= no limit).

## Environment Variables

- **`ATMOS_CI_CACHE_ENABLED`**
  Must be 
  `true`
   (or 
  `ci.cache.enabled: true`
   in 
  `atmos.yaml`
  ) for the command to run.
- **`ATMOS_CI_CACHE_FORMAT`**
  Overrides 
  `--format`
  .

## Related

- [`atmos ci cache delete`](/cli/commands/ci/cache/delete) - Delete a cache entry
- [CI Cache Configuration](/cli/configuration/ci/cache) - Configure the cache

---

## atmos ci cache paths

Print the resolved cache **key**, **paths**, and **restore-keys** so a native cache (such as GitHub's `actions/cache`) can do the storage while Atmos supplies _what_ to cache from your `ci.cache` configuration. Unlike `restore`/`save`, this needs no CI provider or runtime token, so it works on any OS and CI system.

> ⚠️ Experimental

**Configure the CI Cache**

The key and paths printed by this command come from the `ci.cache` section of your `atmos.yaml`.

CI Cache Configuration Reference[Read more](/cli/configuration/ci/cache)

## Usage

```shell
atmos ci cache paths [flags]
```

With `--format=github`, the values are appended to `$GITHUB_OUTPUT` (as `key`, `path`, and `restore-keys`) so a following `actions/cache` step can reference them as step outputs.

## Examples

```shell
# Emit GitHub Actions step outputs (key, path, restore-keys)
atmos ci cache paths --format=github

# Structured output for any other CI or tooling
atmos ci cache paths --format=json
atmos ci cache paths --format=yaml

# Shell-evaluable ATMOS_CI_CACHE_* variables
eval "$(atmos ci cache paths --format=env)"
```

### Use with `actions/cache`

```yaml
- name: Resolve Atmos cache key & paths
  id: atmos-cache
  run: atmos ci cache paths --format=github

- name: Cache Atmos toolchain
  uses: actions/cache@v4
  with:
    key:          ${{ steps.atmos-cache.outputs.key }}
    path:         ${{ steps.atmos-cache.outputs.path }}
    restore-keys: ${{ steps.atmos-cache.outputs.restore-keys }}
```

:::tip One-line equivalent
The [`cloudposse/atmos/actions/cache`](/cli/configuration/ci/cache#github-actions-integration) composite action bundles this `paths` step and `actions/cache` into a single `uses:`.
:::

## Flags

- **`--format`**
  Output format: 
  `github`
   (default, writes 
  `$GITHUB_OUTPUT`
  ), 
  `json`
  , 
  `yaml`
  , or 
  `env`
  .
- **`--key` / `-k`**
  Exact cache key. Defaults to a key derived from the toolchain lockfile plus OS/arch.
- **`--path` / `-p`**
  Root-relative subpaths to cache. Defaults to the entire cache root. Repeatable.
- **`--root`**
  Override the cache root directory (defaults to the Atmos XDG cache directory, e.g. 
  `~/.cache/atmos`
  ).

## Environment Variables

- **`ATMOS_CI_CACHE_ENABLED`**
  Must be 
  `true`
   (or 
  `ci.cache.enabled: true`
   in 
  `atmos.yaml`
  ) for the command to run.
- **`ATMOS_CI_CACHE_FORMAT`**
  Overrides 
  `--format`
  .
- **`ATMOS_CI_CACHE_KEY`**
  Overrides 
  `--key`
  .

## Related

- [CI Cache Configuration](/cli/configuration/ci/cache) - Configure the cache and the GitHub Actions integration options
- [`atmos ci cache restore`](/cli/commands/ci/cache/restore) - Restore via the Atmos-managed backend
- [`atmos ci cache save`](/cli/commands/ci/cache/save) - Save via the Atmos-managed backend

---

## atmos ci cache restore

Restore the CI cache into the well-known cache directory. The exact key is looked up first, then the configured restore-keys (prefix matches) are tried in order. On a hit, the archive is extracted into the cache root. Restore is idempotent within a lifecycle — once a key has been restored, repeat restores are no-ops.

> ⚠️ Experimental

:::info Runs only inside a CI runner
`restore` transfers cache content through the CI provider's runtime cache API, which requires credentials that exist only inside a runner (on GitHub Actions: `ACTIONS_RUNTIME_TOKEN` and `ACTIONS_RESULTS_URL`). Run outside a runner it reports that the cache is unavailable. To manage the cache from your workstation, use [`list`](/cli/commands/ci/cache/list) and [`delete`](/cli/commands/ci/cache/delete).
:::

**Configure the CI Cache**

Configure the cache key, restore-keys, and paths used by this command in your `atmos.yaml`.

CI Cache Configuration Reference[Read more](/cli/configuration/ci/cache)

## Usage

```shell
atmos ci cache restore [flags]
```

With no flags, the cache key and paths come from the [`ci.cache`](/cli/configuration/ci/cache) configuration (the default key is derived from the toolchain lockfile).

## Examples

```shell
# Restore using the configured (or default) key
atmos ci cache restore

# Restore an explicit key with prefix fallbacks
atmos ci cache restore --key="toolchain-linux-amd64-abc123" \
  --restore-key="toolchain-linux-amd64-"

# Restore only specific root-relative subpaths
atmos ci cache restore --path=toolchain
```

## Flags

- **`--key` / `-k`**
  Exact cache key to restore. Defaults to a key derived from the toolchain lockfile plus OS/arch.
- **`--restore-key`**
  Prefix fallback keys tried in order when the exact key is absent. Repeatable.
- **`--path` / `-p`**
  Root-relative subpaths to restore. Defaults to the entire cache root. Repeatable.
- **`--root`**
  Override the cache root directory (defaults to the Atmos XDG cache directory, e.g. 
  `~/.cache/atmos`
  ).

## Environment Variables

- **`ATMOS_CI_CACHE_ENABLED`**
  Must be 
  `true`
   (or 
  `ci.cache.enabled: true`
   in 
  `atmos.yaml`
  ) for the command to run.
- **`ATMOS_CI_CACHE_KEY`**
  Overrides 
  `--key`
  .
- **`ACTIONS_RUNTIME_TOKEN` / `ACTIONS_RESULTS_URL`**
  Provided automatically inside a GitHub Actions runner; required for the GitHub Actions cache backend.

## Related

- [`atmos ci cache save`](/cli/commands/ci/cache/save) - Save the cache
- [CI Cache Configuration](/cli/configuration/ci/cache) - Configure the cache

---

## atmos ci cache save

Archive the cache root (the toolchain and anything else under it) and upload it under the cache key. Cache entries are write-once: when the exact key was an exact hit at restore time (content unchanged) or has already been saved this lifecycle, the save is skipped.

> ⚠️ Experimental

:::info Runs only inside a CI runner
`save` transfers cache content through the CI provider's runtime cache API, which requires credentials that exist only inside a runner (on GitHub Actions: `ACTIONS_RUNTIME_TOKEN` and `ACTIONS_RESULTS_URL`). Run outside a runner it reports that the cache is unavailable. To manage the cache from your workstation, use [`list`](/cli/commands/ci/cache/list) and [`delete`](/cli/commands/ci/cache/delete).
:::

**Configure the CI Cache**

Configure the cache key and paths used by this command in your `atmos.yaml`.

CI Cache Configuration Reference[Read more](/cli/configuration/ci/cache)

## Usage

```shell
atmos ci cache save [flags]
```

## Examples

```shell
# Save using the configured (or default) key
atmos ci cache save

# Save under an explicit key
atmos ci cache save --key="toolchain-linux-amd64-abc123"

# Save only specific root-relative subpaths
atmos ci cache save --path=toolchain
```

## Flags

- **`--key` / `-k`**
  Exact cache key to save under. Defaults to a key derived from the toolchain lockfile plus OS/arch.
- **`--path` / `-p`**
  Root-relative subpaths to save. Defaults to the entire cache root. Repeatable.
- **`--root`**
  Override the cache root directory (defaults to the Atmos XDG cache directory, e.g. 
  `~/.cache/atmos`
  ).

## Environment Variables

- **`ATMOS_CI_CACHE_ENABLED`**
  Must be 
  `true`
   (or 
  `ci.cache.enabled: true`
   in 
  `atmos.yaml`
  ) for the command to run.
- **`ATMOS_CI_CACHE_KEY`**
  Overrides 
  `--key`
  .
- **`ACTIONS_RUNTIME_TOKEN` / `ACTIONS_RESULTS_URL`**
  Provided automatically inside a GitHub Actions runner; required for the GitHub Actions cache backend.

## Related

- [`atmos ci cache restore`](/cli/commands/ci/cache/restore) - Restore the cache
- [CI Cache Configuration](/cli/configuration/ci/cache) - Configure the cache

---

## atmos ci status

Display CI status for the current branch, including status checks, pull request information, and related PRs. Similar to `gh pr status` but integrated with Atmos CI features.

> ⚠️ Experimental

**Configure CI Integration**

Learn how to configure CI/CD integration in your `atmos.yaml`, including setting up providers (like GitHub Actions), managing workflows, environment variables, and secrets for automated infrastructure pipelines.

CI Configuration Reference[Read more](/cli/configuration/ci)

## Usage

```shell
atmos ci status
```

## Examples

```shell
# Show status for current branch
atmos ci status

# Works in CI or locally (requires GITHUB_TOKEN)
GITHUB_TOKEN=ghp_xxx atmos ci status
```

## Output

When on a branch with an open pull request:

```
Relevant pull requests in cloudposse/infra-live

Current branch
  #123  Add VPC module [feature-branch]
    - ✓ terraform-plan (success)
    - ✓ terraform-validate (success)
    - ○ terraform-apply (pending)
    - ✗ security-scan (failure)

Created by you
  #120  Update EKS cluster [eks-upgrade]
    - All checks passing

Requesting a code review from you
  #118  Refactor networking [net-refactor]
    - All checks passing
```

When not on a PR branch:

```
Relevant pull requests in cloudposse/infra-live

Current branch
  Commit status for abc123d
    - ✓ terraform-validate (success)
    - ○ terraform-plan (pending)
    - ✗ lint (failure)

  No open pull request for current branch.
```

## Status Icons

| Icon | State | Description |
|------|-------|-------------|
| ✓ | Success | Check passed |
| ✗ | Failure | Check failed |
| ○ | Pending | Check in progress |
| ● | Cancelled | Check was cancelled |
| − | Skipped | Check was skipped |

## Requirements

- **GITHUB\_TOKEN** environment variable must be set (automatically available in GitHub Actions)
- Must be in a git repository with a remote configured

## Flags

This command has no additional flags.

## Environment Variables

- **`GITHUB_TOKEN`**

  GitHub personal access token or GitHub Actions token. Required for API access.

  In GitHub Actions, this is automatically available as `${{ secrets.GITHUB_TOKEN }}`.

## Related

- [CI Configuration](/cli/configuration/ci) - Configure CI integration
- [`atmos terraform plan`](/cli/commands/terraform/plan) - Run terraform plan with CI integration

---

## atmos ci validate

Validate GitHub Actions workflow files with Atmos's built-in actionlint integration. Use it locally before pushing a workflow or as a CI gate in GitHub Actions.

> ⚠️ Experimental

`atmos validate ci` is an equivalent, validation-oriented alias.

## Usage

```shell
atmos ci validate [workflow-file ...]

# Alias
atmos validate ci [workflow-file ...]
```

With no arguments, the command checks every `.yml` and `.yaml` file in `.github/workflows` below the current working directory. Pass one or more workflow files to check only those files, or use `--workflow-path` to recursively check a different directory.

Use `--affected` to check only workflows changed since the Git merge-base. On GitHub Actions, Atmos automatically uses the pull request base SHA; locally, use `--base ` to select a comparison point. A change to `.github/actionlint.yaml` or `.github/actionlint.yml` validates all workflows because it can affect every result.

On a clean text-mode run, Atmos prints the number of workflow files checked. It never walks to an ancestor repository to select workflows: a command run with `--chdir` uses that directory's `.github/workflows` by default.

## Examples

```shell
# Check all GitHub Actions workflows in this repository
atmos ci validate

# Check one workflow while editing it
atmos ci validate .github/workflows/test.yml

# Check workflow fixtures or any other workflow directory recursively
atmos ci validate --workflow-path tests/fixtures/scenarios/invalid-github-actions-workflows/.github/workflows

# The fixture above is intentionally invalid and exits with status 1
atmos --chdir tests/fixtures/scenarios/invalid-github-actions-workflows ci validate

# Produce a SARIF file for a separate code-scanning upload step
atmos ci validate --format=sarif > actionlint.sarif

# Print rich, actionlint-compatible multi-line diagnostics
atmos ci validate --format=rich

# Exclude intentionally invalid workflow fixtures from a full or affected run
atmos ci validate --exclude 'tests/fixtures/**'
```

## GitHub Actions annotations

When this command runs in GitHub Actions and `ci.enabled: true` is configured in `atmos.yaml`, findings are emitted as inline workflow annotations by default. Set `ci.annotations.enabled: false` to suppress them.

`--format=sarif` writes SARIF to standard output and deliberately does not publish annotations or upload it to Code Scanning. This keeps SARIF upload explicit and avoids duplicate PR feedback.

```yaml title="atmos.yaml"
ci:
  enabled: true
  annotations:
    enabled: true
```

## Configuration and dependencies

The command respects actionlint configuration at `.github/actionlint.yaml` or `.github/actionlint.yml`. Atmos runs actionlint's built-in checks only; its optional ShellCheck and Pyflakes integrations are disabled so validation does not depend on host-installed tools.

## Flags

- **`--format` (string, default `text`)**
  Output format: 
  `text`
  , 
  `rich`
  , or 
  `sarif`
  . 
  `rich`
   writes Atmos source-context diagnostics to standard output and exits non-zero without an Atmos error box.
- **`--workflow-path` (string) (optional)**
  Directory containing workflow files to recursively validate. Cannot be combined with workflow-file arguments.
- **`--exclude` (string, repeatable) (optional)**
  Repository-relative glob to omit workflow files from full or affected validation.

## Related

- [CI Configuration](/cli/configuration/ci) - Configure native CI integration
- [`atmos ci status`](/cli/commands/ci/status) - Show status for the current branch

---

## atmos completion

Use this command to generate completion scripts for `Bash`, `Zsh`, `Fish` and `PowerShell`.

_\[Video: atmos completion]_

## Usage

Execute the `completion` command like this:

```shell
atmos completion [bash|zsh|fish|powershell]
```

This command generates completion scripts for `Bash`, `Zsh`, `Fish` and `PowerShell`.

When the generated completion script is loaded into the shell, pressing the tab key twice displays the available commands and the help.

:::tip
Run `atmos completion --help` to see all the available options
:::

## Configuring Your Shell

To enable command completion, you need to configure your shell. The setup process depends on which shell you’re using (e.g., `zsh` or `bash`).

Select your shell below for detailed setup instructions.

### Bash

## Bash Completion Setup

To enable tab completion for Atmos in Bash, add the following to your `~/.bashrc` or `~/.bash_profile`:

```bash
# Enable Atmos CLI completion
source <(atmos completion bash)
```

After saving the file, apply the changes by running:

```zsh
source ~/.bashrc
```

Now, you can run any `atmos` command, and pressing `` after typing `atmos` will show the available subcommands. The same applies to `--stack` arguments and commands requiring a component (e.g., `atmos terraform plan`).

### Zsh

## Zsh Completion Setup

To enable tab completion for Atmos in `Zsh`, add the following to your `~/.zshrc`:

```zsh
# Initialize Zsh completion system
autoload -Uz compinit && compinit

# Enable Atmos CLI completion
source <(atmos completion zsh)

# Improve completion behavior
zstyle ':completion:*' menu select      # Enable menu selection
zstyle ':completion:*' force-list always # Force vertical menu listing

# Ensure the Tab key triggers autocompletion
bindkey '\t' expand-or-complete
```

After saving the file, apply the changes by running:

```zsh
source ~/.zshrc
```

Now, you can run any `atmos` command, and pressing `` after typing `atmos` will show the available subcommands. The same applies to `--stack` arguments and commands requiring a component (e.g., `atmos terraform plan`).

If completions do not work, try regenerating the completion cache:

```zsh
rm -f ~/.zcompdump && compinit
```

:::warning
The Atmos completion script statically completes [custom commands](/cli/configuration/commands) based on the Atmos configuration. If completions are generated without this configuration (e.g., outside a project directory), custom commands won’t be included. To ensure accuracy, generate or regenerate the script from the correct working directory. This only affects custom commands. Components, stacks, and built-in commands remain fully dynamic.
:::

### Examples

```shell
atmos completion bash
atmos completion zsh
atmos completion fish
atmos completion powershell
```

You can generate and load the shell completion script for `Bash` by executing the following commands:

```shell
atmos completion bash > /tmp/completion
source /tmp/completion
```

or

```shell
source <(atmos completion bash)
```

## Arguments

- **`shell_name` (required)**
  Shell name. Valid values are 
  `bash`
  , 
  `zsh`
  , 
  `fish`
   and 
  `powershell`
  .

:::info
Refer to [Command-line completion](https://en.wikipedia.org/wiki/Command-line_completion) for more details
:::

---

## atmos composition

Use the `atmos composition` subcommands to list declared compositions, validate stack fulfillment,
and operate every fulfilled member in a composition as one stack-scoped lifecycle.

A composition is declared once under the top-level `compositions` section and fulfilled by components
that opt in with the first-class `composition` field. Lifecycle commands operate only the services
that are fulfilled in the selected stack.

## Usage

```shell
# Discover declared compositions and the stacks where each is available
atmos composition list
# Include fulfillment details for one stack
atmos composition list -s 

# Validate declarations, or validate fulfillment for one stack
atmos composition validate [composition]
atmos composition validate [composition] -s 

# Lifecycle and read commands require a stack
atmos composition up|down|start|stop|restart|rm|ps [composition] -s 
atmos composition logs [composition] -s  [--follow] [--tail[=N]]
```

The `composition` argument is optional. When omitted from a stack-scoped lifecycle or read command,
Atmos selects all compositions that have at least one fulfilled member in the selected stack.

## Subcommands

| Command | Stack required | Description |
|---------|----------------|-------------|
| `list` | Optional | Shows declared compositions and the stacks where each has fulfilled members. With `-s `, also shows which declared services are fulfilled in that stack. |
| `validate` | Optional | Validates one composition or all compositions. With `-s `, includes stack fulfillment details. |
| `up` | Yes | Creates or starts fulfilled members. |
| `down` | Yes | Stops and removes fulfilled members. |
| `start` | Yes | Starts existing fulfilled members. |
| `stop` | Yes | Stops fulfilled members. |
| `restart` | Yes | Restarts fulfilled members. |
| `rm` | Yes | Removes fulfilled members. |
| `ps` | Yes | Shows running state for fulfilled members. |
| `logs` | Yes | Shows logs for fulfilled members. |

Lifecycle and read commands require a stack. Use the `STACKS` column from `atmos composition list` to
discover valid values. In an interactive terminal with `--interactive`, omitting
`--stack` opens a picker containing only stacks that fulfill the requested composition (or any
composition when the argument is omitted). In scripts, CI, and non-interactive terminals, they fail
clearly when `--stack` is omitted. `list` and `validate` are the exceptions: both can run without a
stack, and both accept `-s, --stack` when you want stack-specific fulfillment details.

## Ordering

When a command targets more than one composition or more than one service, Atmos uses deterministic
ordering:

- Startup and read commands (`up`, `start`, `restart`, `ps`, `logs`) process composition names
  alphabetically, then services in the order declared by the composition.
- Teardown commands (`down`, `stop`, `rm`) process composition names in reverse alphabetical order,
  then services in reverse declared order.

This makes startup predictable while tearing down dependents before the earlier services they may
depend on.

## Logs

Use `logs` to read logs from every fulfilled member in a composition, or from every fulfilled
composition in the selected stack when the composition argument is omitted.

```shell
# Show logs for all fulfilled services in the storefront composition
atmos composition logs storefront -s dev

# Follow logs
atmos composition logs storefront -s dev --follow

# Show all available log lines
atmos composition logs storefront -s dev --tail

# Show the last 200 log lines
atmos composition logs storefront -s dev --tail=200
```

A bare `--tail` means all available lines. Use `--tail=N` to limit output to the last `N` lines.

## Examples

```shell
# Show every declared composition and its available stacks
atmos composition list

# Show declared compositions and fulfillment in the local stack
atmos composition list -s local

# Validate one composition in one stack
atmos composition validate storefront -s local

# Validate all compositions in one stack
atmos composition validate -s local

# Start every fulfilled service in storefront for the local stack
atmos composition up storefront -s local

# Show running state for every fulfilled composition in the local stack
atmos composition ps -s local

# Stop and remove every fulfilled composition in the local stack
atmos composition down -s local
```

## Provider support

Composition lifecycle commands dispatch to the provider for each fulfilled member. If a provider does
not support the requested subcommand, Atmos returns a clear error for that member instead of silently
skipping it.

## See Also

- [Container Components](/components/container) - Configure container services and composition membership
- [`atmos container`](/cli/commands/container/usage) - Operate individual container components

---

## atmos config delete

Delete a value from your `atmos.yaml` configuration using a dot-notation path,
preserving the rest of the file's comments, anchors, and formatting.

## Usage

```shell
atmos config delete 
```

The `del` and `unset` aliases are also accepted.

## Examples

```shell
atmos config delete components.terraform.append_user_agent
atmos config del logs.file
```

## Arguments

- **`` (required)**
  Dot-notation path to the value to delete.

## Flags

- **`--config` (string slice, inherited)**
  Target a specific 
  `atmos.yaml`
   file instead of the one discovered in the current directory or git root. Must name exactly one file: 
  `config delete`
   edits a single concrete file on disk, so passing more than one 
  `--config`
   value is rejected as ambiguous.

---

## atmos config format

Format the active `atmos.yaml` file in place, preserving comments, anchors, Atmos YAML
functions, and Go templates.

## Usage

```shell
atmos config format
```

The `fmt` alias is also accepted.

## Examples

```shell
atmos config format
atmos --config ./config/atmos.yaml config format
```

## Flags

- **`--config` (string slice, inherited)**
  Target a specific 
  `atmos.yaml`
   file instead of the one discovered in the current directory or git root. Must name exactly one file: 
  `config format`
   edits a single concrete file on disk, so passing more than one 
  `--config`
   value is rejected as ambiguous.

---

## atmos config get

Read a value from the effective, fully-merged Atmos configuration using a dot-notation path.

`atmos config get` reports the same configuration Atmos actually uses for this invocation —
every `--config` file, `--config-path` directory, and profile merged together in precedence
order — not just what a single physical `atmos.yaml` file declares on its own. If you need to
inspect or edit one file's own declared value directly, use `atmos config format` or open the
file.

## Usage

```shell
atmos config get 
```

## Examples

```shell
atmos config get logs.level
atmos config get components.terraform.base_path
atmos config get logs.level --config ./atmos.yaml
atmos config get stacks.included_paths --config ./main.yaml,./overrides.yaml
```

## Arguments

- **`` (required)**
  Dot-notation path to the value (e.g. 
  `logs.level`
  , 
  `components.terraform.base_path`
  ). Array elements use bracket indices (e.g. 
  `import[0]`
  ).

## Flags

- **`--config` (string slice, inherited)**
  Select one or more 
  `atmos.yaml`
   files instead of the location discovered in the current directory or git root. When multiple files are given, later files override earlier ones and the merged result is what 
  `get`
   reports.

---

## atmos config list

List the physical Atmos config files that contributed settings and the dot-notation
setting paths defined in each file. Optionally filter paths with a glob pattern.

## Usage

```shell
atmos config list [path-pattern] [--format ] [--delimiter ]
```

## Examples

```shell
# List every editable path.
atmos config list

# Filter by a glob pattern.
atmos config list 'toolchain.*'

# Machine-readable output.
atmos config list --format json
```

## Arguments

- **`[path-pattern]` (optional)**
  A glob pattern to filter the listed paths (e.g. 
  `toolchain.*`
  ).

## Flags

- **`--format` / `-f` (string, default `paths`)**
  Output format: 
  `paths`
  , 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , or 
  `tsv`
  .
- **`--delimiter` (string)**
  Delimiter to use for 
  `csv`
  /
  `tsv`
   output.

---

## atmos config schema

Print the embedded JSON Schema for the Atmos CLI configuration (`atmos.yaml`) — the same
schema `atmos validate schema` uses for `atmos.yaml` by default — to stdout, or write it
to a file.

The schema is generated from the Atmos configuration code itself, so it always matches
what the version of Atmos you are running actually reads — including every section,
YAML function alternatives (like `logs: !include shared.yaml`), and descriptions sourced
from the code documentation. It covers `atmos.yaml`, `atmos.d` fragments, and
[profile](/cli/configuration/profiles) files, none of which require any specific fields,
so partial configuration fragments validate standalone.

## Usage

```shell
atmos config schema [output-path]
```

## Examples

```shell
# Print the schema to stdout.
atmos config schema

# Write the schema to a file, creating parent directories as needed.
atmos config schema website/static/schemas/atmos/atmos-config/1.0/atmos-config.json
```

## Arguments

- **`[output-path]` (optional)**
  Write the schema to this file instead of stdout. Parent directories are created as needed.

## Editor Integration

Point your editor's YAML language server at the published schema to get auto-completion
and inline validation while editing `atmos.yaml`:

```yaml
# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-config/1.0/atmos-config.json
base_path: "./"
```

See [CLI Configuration Schemas](/cli/configuration/schemas) for the floating and
version-pinned schema URLs.

---

## atmos config set

Set a value in your `atmos.yaml` configuration using a dot-notation path. The edit
preserves comments, anchors/aliases, Atmos YAML functions, and Go templates.

## Usage

```shell
atmos config set   [--type ]
```

By default (`--type=auto`) the value's type is inferred in order: first from
the Atmos config schema when `` matches a known field (`mcp.enabled` writes
a boolean, `settings.terminal.max_width` writes an int, and so on); otherwise from
the type of the value already at `` (a `true`/`false` already there infers
bool, a bare number infers int/float); otherwise from the new value's own shape
(`5` infers int, `true` infers bool, `3.14` infers float). Only when none of those
have an answer — the value doesn't even look like a bool/int/float, e.g. a plain
word for a brand-new `vars` path — does it fall back to writing a string, and
Atmos prints a warning in that case since the fallback is easy to miss. Pass
`--type` explicitly to skip inference entirely.

## Examples

```shell
# Type inferred from the schema -- writes a boolean, not the string "true".
atmos config set mcp.enabled true
atmos config set components.terraform.apply_auto_approve true

# Type inferred from the existing value at the path (no schema entry needed).
atmos config set vars.replicas 5

# Type inferred from the new value's own shape -- vars.debug_enabled has no
# schema entry and no existing value, but "true" looks like a bool, so it's
# written as one, not the literal string "true".
atmos config set vars.debug_enabled true

# String value (the fallback when nothing above has an answer -- Atmos warns
# here since "nan" looks like it could be numeric but can't be written as a
# float safely, so it's stored as the literal string instead).
atmos config set vars.threshold nan

# Explicit --type overrides inference.
atmos config set --type=string logs.level Debug
atmos config set --type=int settings.terminal.max_width 120

# Raw YAML literal (lists, maps).
atmos config set --type=yaml 'logs.exclude' '["a", "b"]'

# Target a specific file.
atmos config set logs.level Trace --config ./atmos.yaml
```

## Arguments

- **`` (required)**
  Dot-notation path to the value (e.g. 
  `logs.level`
  ).
- **`` (required)**
  The value to set. Interpreted according to 
  `--type`
  .

## Flags

- **`--type` (string, default: `auto`)**
  How to interpret 
  ``
  : 
  `auto`
   (infer from the schema, then from the existing value at the path, then from the new value's own shape, falling back to string), 
  `string`
  , 
  `int`
  , 
  `bool`
  , 
  `float`
  , 
  `null`
  , or 
  `yaml`
   (a raw YAML/yq literal inserted verbatim).
- **`--config` (string slice, inherited)**
  Target a specific 
  `atmos.yaml`
   file instead of the one discovered in the current directory or git root. Must name exactly one file: 
  `config set`
   edits a single concrete file on disk, so passing more than one 
  `--config`
   value is rejected as ambiguous.

:::note
Edits that would alter or expand a YAML anchor or alias (including changing a value
shared by an anchor) are rejected to avoid silently mutating shared data, as are
multi-document YAML files and files that define the same anchor name more than once.
Comments, anchors, indentation width, and line endings (CRLF/LF) are preserved, but
blank lines between entries are dropped and non-indented sequence styles are
normalized when a file is edited.
:::

---

## atmos config validate

Validate the Atmos CLI configuration — `atmos.yaml`, `atmos.d` fragments, and
project-local [profiles](/cli/configuration/profiles) — against the JSON Schema
generated from the Atmos configuration code. This is an alias for
[`atmos validate schema config`](/cli/commands/validate/schema).

The schema is generated from the code that reads the configuration, so it always
matches the Atmos version you are running. YAML functions (like
`logs: !include shared.yaml`) validate cleanly, and fragments require no specific
fields, so partial configs pass standalone. Print the schema itself with
[`atmos config schema`](/cli/commands/config/config-schema).

## Usage

```shell
atmos config validate
```

## Examples

```shell
# Validate atmos.yaml, atmos.d fragments, and project-local profiles.
atmos config validate

# Equivalent long form; validates only the `config` schema entry.
atmos validate schema config

# Validate only changed Atmos configuration files.
atmos config validate --affected --base origin/main
atmos config validate --exclude 'tests/fixtures/**'
```

Exits non-zero when any file fails validation, making it suitable for CI and
pre-commit hooks. Override the schema or matched files with a `schemas.config`
entry — see [CLI Configuration Schemas](/cli/configuration/schemas).

`--affected` compares the current worktree with the Git merge-base and validates
only changed `atmos.yaml` files, fragments, and profiles. On GitHub Actions, the
pull request base SHA is detected automatically; use `--base` locally when the
default baseline is not appropriate.

Use repeatable `--exclude ` to omit matching configuration files from full or affected validation.

---

## atmos config

The `atmos config get|set|delete|list|format` commands read and edit values in your
`atmos.yaml` configuration using dot-notation paths — this is already the canonical
form for the config domain (there's no separate `atmos config config` sub-namespace,
unlike `stack`/`vendor`). Edits preserve comments, anchors/aliases, Atmos YAML
functions, and Go templates, so you can script configuration changes without
`sed`/`yq` and without losing formatting.

## Usage

```shell
atmos config get 
atmos config set   [--type ]
atmos config delete 
atmos config list [path-pattern] [--format ]
atmos config format
atmos config schema [output-path]
atmos config validate
```

By default these commands target the `atmos.yaml` (or `.atmos.yaml`) in the current
directory or git root. Use the global `--config` flag to target a specific file.

## Subcommands

- [`atmos config get`](/cli/commands/config/config-get) — read a value by path.
- [`atmos config set`](/cli/commands/config/config-set) — set a value by path.
- [`atmos config delete`](/cli/commands/config/config-delete) — delete a value by path.
- [`atmos config list`](/cli/commands/config/config-list) — list editable setting paths.
- [`atmos config format`](/cli/commands/config/config-format) — format the active `atmos.yaml` file.
- [`atmos config schema`](/cli/commands/config/config-schema) — print the JSON Schema for `atmos.yaml`.
- [`atmos config validate`](/cli/commands/config/config-validate) — validate `atmos.yaml` against its JSON Schema.

## Examples

```shell
# Read the configured log level.
atmos config get logs.level

# Set a string value.
atmos config set logs.level Debug

# Set a typed (boolean) value.
atmos config set --type=bool components.terraform.apply_auto_approve true

# Delete a value.
atmos config delete logs.file

# List every editable path.
atmos config list

# Format the active atmos.yaml file.
atmos config format

# Target a specific file.
atmos config set logs.level Trace --config ./atmos.yaml
```

---

## atmos container

Use the `atmos container` subcommands to build, run, and operate **container components** — stack-scoped,
Atmos-native, persistent containers. One component is one service. Atmos owns the image artifact
(build/push/pull) and an optional long-running named container lifecycle (`up`/`start`/`ps`/`logs`/`exec`/
`attach`/`restart`/`stop`/`rm`/`down`), discovered by labels derived from the canonical component instance
address — not from local state files.

A container component is the per-service building block. A set of container components grouped by a
[composition](#compositions) is effectively "your own Compose" — Atmos orchestrates a multi-container
local system with no `compose.yaml`. This is distinct from the ephemeral [`type: container`
step](/workflows), which is `docker run --rm` and workflow-scoped; the component is
declarative, addressable infrastructure.

## Usage

```shell
# Image artifact
atmos container build  -s 
atmos container push  -s           # pushes every build tag (→ multiple registries)
atmos container pull  -s 

# Lifecycle
atmos container run  -s            # one-shot foreground process (run)
atmos container up  -s             # create/start the long-running container
atmos container down  -s           # stop + rm
atmos container start  -s          # start an existing stopped container (inverse of stop)
atmos container restart|stop|rm  -s 

# Bulk lifecycle (no component) — see "Bulk operation" below
atmos container up --all                             # all container components in all stacks
atmos container up --all --stack=             # all container components in one stack
atmos container up                                   # interactive picker (stack + components)

# Inspection
atmos container list                                 # all container components + running state
atmos container ps                                   # running state of all components (optionally -s )
atmos container ps  -s             # running state of one component
atmos container logs  -s           # one component
atmos container logs --all --stack=           # all components in a stack
atmos container logs --all --follow                  # tail all components, interleaved + prefixed
atmos container exec  -s  -- sh    # run a command / open a shell (new process)
atmos container attach  -s         # attach to the container's main process (PID 1)
```

:::tip Build before start
`up` and `run` build the image automatically when the component declares `build:` and the image
is not present locally. Components that reference an existing `image:` are pulled on demand.
:::

## Configuration

Define container components under `components.container` in your stack manifests. `image`, `build`, and
`run` are **first-class component sections** (siblings of `composition`/`env`/`metadata`) — NOT nested
under `vars`, consistent with the container workflow step:

```yaml
components:
  container:
    api:
      composition: storefront            # first-class composition membership
      image: "localhost:5001/api:{{ .git.sha }}"
      build:
        context: app
        dockerfile: Dockerfile
        tags:
          - "localhost:5001/api:{{ .git.sha }}"
      run:
        command: ./api
        ports:
          - host: 8080
            container: 8080
        mounts:
          - source: .
            target: /workspace
      secrets:
        vars:
          NPM_TOKEN:
            store: app-secrets
            required: true
      env:                               # component env (resolved with secrets)
        PORT: "8080"
        NPM_TOKEN: !secret NPM_TOKEN
```

Inheritance (`metadata.inherits`), catalogs, and deep-merge apply exactly like other component kinds —
abstract base components can carry shared `build`/`run` defaults.

### Path resolution

Relative `build.context` and `run.mounts[].source` values resolve against the container component's
own directory — the same `components.container.base_path` + `component:`/`metadata.component`
mechanism used by Terraform, Helmfile, Kubernetes, and Helm components, **not** the directory `atmos`
happens to be invoked from. A relative `build.dockerfile` then resolves against the resulting
`build.context` (the Docker/Compose convention), not against the component directory directly:

```yaml
# atmos.yaml
components:
  container:
    base_path: components/container   # default
```

```yaml
components:
  container:
    api:
      component: api   # resolves to /api, independent of the `atmos` invocation's CWD
      build:
        context: app        # /api/app
        dockerfile: docker/Dockerfile.prod  # /api/app/docker/Dockerfile.prod (relative to build.context, not /api)
```

Container components also support the same JIT `source:` provisioning as other component types — a
component that declares `source:` is auto-vendored into a workdir on demand, and that workdir becomes
the anchor for `build.context`/`build.dockerfile`/`run.mounts[].source` instead of the static
`base_path` directory.

`build.tags` is a list — the build applies all of them to the image, and `push` sends **every** tag, so
listing registry-qualified tags there pushes to multiple registries in one operation (see
[Push to multiple registries](#push-to-multiple-registries)).

### Push to multiple registries

`atmos container push ` (and `atmos container push --all`) pushes **every entry in
`build.tags`** — so a single push ships the image to as many registries as you list. The build already
applied each tag to the image locally, so push just sends them in order:

```yaml
components:
  container:
    app:
      image: app:v1                                   # local ref used by run/up
      build:
        context: .
        tags:
          - 1234.dkr.ecr.us-east-1.amazonaws.com/app:v1   # AWS ECR
          - ghcr.io/cloudposse/app:v1                      # GitHub Container Registry
```

```shell
atmos container build app --stack=prod    # builds once, applies both tags
atmos container push app --stack=prod     # pushes to ECR and GHCR
```

Notes:

- Pushes run **in order and fail fast** — the first registry that errors stops the push (fix it and
  re-run; already-pushed registries are simply re-pushed, which is a no-op when the digest is unchanged).
- When a component has **no `build.tags`**, `push` falls back to the single top-level `image` (the
  original behavior).
- Use `--dry-run` to preview exactly which references will be pushed:
  ```shell
  atmos container push app --stack=prod --dry-run
  # ▶ [dry-run] push 1234.dkr.ecr.us-east-1.amazonaws.com/app:v1
  # ▶ [dry-run] push ghcr.io/cloudposse/app:v1
  ```

Authenticate to each registry first (e.g. via `--identity` for cloud registries, or the runtime's own
`docker/podman login`).

### Health checks and restart policies

`run.restart` and `run.healthcheck` are **first-class settings** that mirror Docker Compose, so you no
longer need to hand-write `run.run_args`:

```yaml
components:
  container:
    api:
      image: nginx:alpine
      run:
        # Restart policy → docker/podman --restart
        restart:
          policy: unless-stopped        # no | always | on-failure | unless-stopped
          max_retries: 5                # only used with the `on-failure` policy
        # Health check → docker/podman --health-* (mirrors Compose `healthcheck`)
        healthcheck:
          test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost/ || exit 1"]
          interval: 30s
          timeout: 5s
          retries: 3
          start_period: 10s
          start_interval: 5s
```

**`test`** follows Compose semantics — a string, or a list whose **first element** selects the form:

- **`["CMD", "executable", "arg", …]`**
  Run the command directly. (The CLI runs 
  `--health-cmd`
   through the container's shell, so the args
  are joined into a shell command; for true exec-form, bake a 
  `HEALTHCHECK`
   into the image.)
- **`["CMD-SHELL", "full shell command"]` or a bare string**
  Run the string with the container's default shell (
  `/bin/sh -c`
  ). 
  `test: "curl -f http://localhost || exit 1"`

  is shorthand for 
  `test: ["CMD-SHELL", "curl -f http://localhost || exit 1"]`
  .
- **`["NONE"]` (or `disable: true`)**
  Disable any health check inherited from the image (
  `--no-healthcheck`
  ).

Field-to-flag mapping:

| Field | Flag |
|-------|------|
| `restart.policy` (+ `max_retries`) | `--restart=[:]` |
| `healthcheck.test` | `--health-cmd` (or `--no-healthcheck`) |
| `healthcheck.interval` | `--health-interval` |
| `healthcheck.timeout` | `--health-timeout` |
| `healthcheck.retries` | `--health-retries` |
| `healthcheck.start_period` | `--health-start-period` |
| `healthcheck.start_interval` | `--health-start-interval` |

Once a health check is configured, `atmos container ps` and `atmos container list` show the resulting
state in a **`HEALTH`** column (`healthy` / `unhealthy` / `starting`, or `-` when no check is defined).
Atmos validates the restart policy and health-check durations up front, so a typo surfaces as a clear
error instead of an opaque runtime failure. For anything not modeled here, `run.run_args` remains the
raw passthrough to `docker/podman create`.

### Runtime selection

The container runtime is auto-detected (Docker first, then Podman). Override it globally in `atmos.yaml`:

```yaml
container:
  runtime:
    provider: auto        # or docker/podman; auto is also the default
    # Auto-start is on by default. Set auto_start: false to opt out.
```

It can also be set with the `ATMOS_CONTAINER_RUNTIME` environment variable.

## Component Instance Identity

A container component instance is identified by `//`. Atmos projects
that onto a deterministic runtime name and labels:

| Field | Value (example) |
|-------|-----------------|
| Instance | `dev/container/api` |
| Runtime name | `atmos-dev-container-api` |
| Labels | `tools.atmos.stack=dev`, `tools.atmos.component_type=container`, `tools.atmos.component=api`, `tools.atmos.instance=dev/container/api` |

`start`, `ps`, `logs`, `exec`, `attach`, `restart`, `stop`, `rm`, and `down` discover the container by these
labels — there are no local state files to lose or corrupt.

## Lifecycle verbs: `up`/`down` vs `start`/`stop`

The lifecycle has two complementary pairs, mirroring `docker compose`:

- **`up` ↔ `down`** — the full lifecycle. `up` **creates or starts** the named container (building the
  image first if needed); `down` **stops and removes** it (`stop` + `rm`).
- **`start` ↔ `stop`** — toggle the running state of an **existing** container in place. `start` brings a
  stopped container back without recreating it; `stop` halts it without removing it. `restart` is `stop`
  then `start`.

Use `start` to resume a container you previously `stop`ped; use `up` when the container may not exist yet
(it will be created). `rm` removes a stopped container; `down` is the stop-and-remove shortcut.

## `exec` vs `attach`

Both connect you to a running container, but they are not the same:

- **`exec`** starts a **new process** inside the container. With a command after `--` it runs that
  command; with no command it opens a shell (`/bin/sh`). This is how you "shell in."
- **`attach`** connects your terminal to the container's **existing main process (PID 1)** — the
  process the container runs — mirroring `docker attach` / `docker compose attach`. Use it when PID 1
  is itself interactive (a REPL, a foreground server streaming to stdout). Detach with the runtime's
  detach keys (`Ctrl-P` `Ctrl-Q`), which leaves the container running.

## Running state

`atmos container list` shows the running state of every container component — a green ● dot on a TTY,
and `running`/`stopped`/`unknown` text otherwise. A **`HEALTH`** column reports each container's health
(`healthy` / `unhealthy` / `starting`, or `-` when the component defines no
[health check](#health-checks-and-restart-policies)). When no container runtime is available, rows are
reported as `unknown` rather than failing the listing. (Container running state lives here, not in the
generic `atmos list components`, which treats all component kinds uniformly.)

## Bulk operation

The lifecycle verbs that are safe to batch — `build`, `push`, `pull`, `up`, `start`, `restart`, `stop`,
`rm`, and `down` — can operate on **many components at once**. For these verbs the `` argument
is optional, and there are three ways to select what to operate on:

- **`--all`**
  Operate on 
  **every**
   (non-abstract) container component, in dependency-free sorted order. Scope it
  to a single stack with 
  `--stack=`
  ; without a stack it spans 
  **all**
   stacks.
- **`--all --stack=`**
  Operate on every container component in just that stack.
- **`--tags `**
  Operate on container components matching any of the given tags (comma-separated): 
  `--tags=production,tier-1`
  . Components are tagged via an optional 
  `metadata.tags: [...]`
   list in stack manifests. Composes with 
  `--all`
  ; cannot be combined with a single component argument.
- **`--labels `**
  Operate on container components matching all of the given labels (comma-separated 
  `key=value`
   pairs): 
  `--labels=cost-center=platform,compliance=sox`
  . Components are labeled via an optional 
  `metadata.labels: {...}`
   map in stack manifests. Composes with 
  `--all`
  /
  `--tags`
  ; cannot be combined with a single component argument.
- **no component (interactive)**
  In an interactive terminal, Atmos prompts for a 
  **stack**
   (skipped if only one exists or 
  `--stack`

  is given) and then a 
  **multi-select of components**
   (all pre-selected). Outside a TTY (CI, pipes) this
  is an error — pass 
  `--all`
   or a 
  ``
   instead.

Bulk runs are **continue-on-error**: every selected component is attempted, per-component failures are
reported as they happen, and the command exits non-zero with an aggregated summary if any failed.
Teardown verbs (`down`, `stop`, `rm`) run in **reverse** order of the start verbs so dependents are
removed before what they depend on.

:::note Mutually exclusive
A `` argument and `--all` cannot be combined. `--all` is available on the bulk-capable
lifecycle verbs and on `logs` (see below); `run`, `exec`, and `attach` remain single-component. `ps` and
`list` need no `--all` — omitting the component already shows all (optionally filtered by `--stack`).
:::

### Following logs across components

`logs` supports the same selection (``, `--all`, or interactive) plus `--follow`/`-f` and
`--tail`:

```shell
atmos container logs api --stack=dev --follow          # tail one component
atmos container logs --all --stack=dev                 # all components, printed in turn
atmos container logs --all --follow                    # tail every component, interleaved
```

When following more than one component, the streams run **concurrently** and each line is prefixed with a
colored, width-aligned **component label** — the same badge style as Atmos log levels, with a distinct
color per component — like `docker compose logs -f`. Where color is unavailable (non-TTY, `NO_COLOR`,
CI), the label degrades to a plain `[api]` / `[worker]` prefix. Press `Ctrl-C` to stop following. Without
`--follow`, multiple components are printed sequentially under an `==> stack/component <==` header.

## Compositions

A composition groups components into a system. Components declare membership via the `composition`
field; the top-level `compositions` section declares the closed set of services:

```yaml
compositions:
  storefront:
    description: Storefront system
    services: [api, worker, database]
```

- Declaring `composition: X` for a service not listed in `compositions.X.services` is a **hard error**.
- A declared service with no component in a stack is **allowed** (membership is closed, fulfillment is open).
- `atmos composition validate  -s ` reports fulfilled vs. not-provided-here services.

## Arguments

- **``**
  The container component to operate on. Required for 
  `run`
  , 
  `exec`
  , and 
  `attach`
  ;

  **optional**
   for the bulk-capable verbs (
  `build`
  , 
  `push`
  , 
  `pull`
  , 
  `up`
  , 
  `start`
  , 
  `restart`
  , 
  `stop`
  ,

  `rm`
  , 
  `down`
  ) and 
  `logs`
  , where omitting it selects components via 
  `--all`
   or an interactive picker (see

  [Bulk operation](#bulk-operation)
  ). For 
  `ps`
  , omitting it lists all components' running state (like

  `list`
  , optionally filtered by 
  `--stack`
  ). Not used by 
  `list`
  .

## Flags

- **`--stack` / `-s` (required for single-component subcommands)**
  The stack the component is defined in. For bulk verbs it scopes the operation to one stack.
- **`--all` (bulk verbs only)**
  Operate on all container components instead of a single one — across all stacks, or one stack when
  combined with 
  `--stack=`
  . Cannot be combined with a 
  ``
   argument. See

  [Bulk operation](#bulk-operation)
  .
- **`--tags` (bulk verbs only)**
  Filter by tags (comma-separated, matches any): 
  `--tags=production,tier-1`
  . Composes with 
  `--all`
   to narrow the selected set further; cannot be combined with a 
  ``
   argument.
- **`--labels` (bulk verbs only)**
  Filter by labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--labels=cost-center=platform,compliance=sox`
  . Composes with 
  `--all`
  /
  `--tags`
  ; cannot be combined with a 
  ``
   argument.
- **`--follow` / `-f` (logs)**
  Stream logs continuously. With multiple components the streams are interleaved and each line is
  prefixed with the component name. Press 
  `Ctrl-C`
   to stop.
- **`--tail` (logs)**
  Number of lines to show from the end of the logs, or 
  `all`
   (default).
- **`--identity`**
  Authenticate with the given identity before running (e.g. for registry access on build/push/pull).
- **`--dry-run`**
  Print what would happen without touching the runtime.
- **`--` (exec)**
  Everything after 
  `--`
   is the command run inside the container, e.g. 
  `atmos container exec api -s dev -- sh -c 'env'`
  . 
  `attach`
   takes no command — it connects to the existing main process.

## Examples

```shell
# Build the image, start the long-running container, and confirm it is running.
atmos container build api -s dev
atmos container up api -s dev
atmos container list             # api shows a ● running indicator

# Build once and push the image to every registry in build.tags (e.g. ECR + GHCR).
atmos container build api -s dev
atmos container push api -s dev

# Operate the running container (discovered by label).
atmos container ps                       # running state of every component
atmos container logs api -s dev
atmos container exec api -s dev -- sh    # new shell process inside the container
atmos container attach api -s dev        # connect to the container's main process (Ctrl-P Ctrl-Q to detach)

# Stop and later resume the same container in place (no recreate).
atmos container stop api -s dev
atmos container start api -s dev

# Tear down.
atmos container down api -s dev

# Bulk: bring up every container component in a stack, then everywhere.
atmos container up --all --stack=dev
atmos container up --all

# Bulk: tear down a whole stack (reverse order), continue-on-error.
atmos container down --all --stack=dev

# Bulk: bring up only components tagged "production" and labeled cost-center=platform.
atmos container up --all --tags production --labels cost-center=platform

# Bulk: interactive picker (TTY) — choose a stack, then components.
atmos container up

# Report a composition's fulfilled vs. not-provided services.
atmos composition validate storefront -s dev
```

---

## atmos describe affected

Use this command to show a list of the affected Atmos components and stacks given two Git commits.

_\[Video: atmos describe affected]_

## Description

The command uses two different Git commits to produce a list of affected Atmos components and stacks.

For the first commit, the command assumes that the current repo root is a Git checkout. An error will be thrown if the
current repo is not a Git repository (the `.git/` folder does not exist or is configured incorrectly).

The second commit can be specified on the command line by using the `--base` flag, which accepts both
[Git References](https://git-scm.com/book/en/v2/Git-Internals-Git-References) and commit SHAs (auto-detected by format).
The deprecated `--ref` and `--sha` flags are still supported for backward compatibility.

:::tip Zero-Config CI
When `ci.enabled` is `true` in `atmos.yaml`, the base commit is automatically resolved from the CI environment:

- **Open pull requests**: Compares against the **fork point** of the PR branch (`git merge-base HEAD origin/`). This works correctly even when the PR is out of date with the target branch — only commits the PR introduced are reported as affected, never commits that landed on `` after the PR was forked. In shallow CI checkouts (`actions/checkout@v6` defaults to `fetch-depth: 1`), Atmos transparently fetches the target branch on demand.
- **Merged pull requests**: Atmos first classifies which commit the workflow checked out (the PR head, the merge commit, or GitHub's synthetic `refs/pull//merge` test merge) and picks the strategy that yields the PR's **net** change for that checkout — anchored on `pull_request.merge_commit_sha` from the event payload, never on the moving tip of `` (which already contains the merge). For fast-forward merges (`merge_commit_sha` equals `head.sha`), it anchors on `pull_request.base.sha` instead, since the merge commit's own parent would otherwise be the PR's own previous commit. This keeps multi-commit PRs correct: a final commit that reverts an earlier one contributes nothing to the diff.
- **Push events**: Compares against the previous commit
- **Merge groups**: Compares against the merge queue base

No `--base`, `--ref`, or `--sha` flags needed — just run `atmos describe affected` in your CI workflow.

If no CI environment is detected (or `ci.enabled` is not set), the default is `refs/remotes/origin/HEAD` (usually the `main` branch).
:::

## How does it work?

The command performs the following:

- If the `--repo-path` flag is passed, the command uses it as the path to the already cloned target repo with which to
  compare the current working branch. I this case, the command will not clone and checkout the
  target reference, but instead will use the already cloned one to compare the current branch with. In this case, the
  `--ref`, `--sha`, `--ssh-key` and `--ssh-key-password` flags are not used, and an error will be thrown if the `--repo-path`
  flag and any of the `--ref`, `--sha`, `--ssh-key` or `--ssh-key-password` flags are provided at the same time

- Otherwise, if the `--clone-target-ref=true` flag is specified, the command clones (into a temp directory) the remote
  target with which to compare the current working branch. If the `--ref` flag or the commit SHA flag `--sha` are provided,
  the command uses them to clone and checkout the remote target. Otherwise, the `HEAD` of the remote origin is
  used (`refs/remotes/origin/HEAD` Git ref, usually the `main` branch)

- Otherwise, (if the `--repo-path` and `--clone-target-ref=true` flags are not passed), the command does not clone anything
  from the remote origin, but instead just copies the current repo into a temp directory and checks out the target
  reference with which to compare the current working branch.
  If the `--ref` flag or the commit SHA flag `--sha` are
  provided, the command uses them to check out. Otherwise, the `HEAD` of the remote origin is used
  (`refs/remotes/origin/HEAD` Git ref, usually the `main` branch).
  This requires that the target reference is already cloned by Git, and the information about it exists in
  the `.git` directory (in case of using a non-default branch as the target, Git deep clone needs to be executed instead
  of a shallow clone).
  This is the recommended way to execute the `atmos describe affected` command since it allows
  [working with private repositories](#working-with-private-repositories) without providing the SSH credentials
  (`--ssh-key` and `--ssh-key-password` flags), since in this case Atmos does not access the remote origin and instead
  just checks out the target reference (which is already on the local file system)

- The command deep-merges all stack configurations from both sources: the current working branch and the target reference

- The command searches for changes in the component directories

- The command compares each stack manifest section of the stack configurations from both sources looking for differences

- And finally, the command outputs a JSON or YAML document consisting of a list of the affected components and stacks
  and what caused it to be affected

Since Atmos first checks the component folders for changes, if it finds any affected files, it will mark all related
components and stacks as affected. Atmos will then skip evaluating the stacks for differences since it already
knows that they are affected.

## Evaluated sections

When comparing the deep-merged configuration of each component between the two refs, Atmos evaluates the following
top-level component sections. A change in any of them marks the component as affected (with the corresponding
[`affected`](#output) reason):

`vars`, `env`, `settings`, `metadata`, `providers`, `required_providers`, `required_version`, `generate`,
`backend`, `backend_type`, `remote_state_backend`, `remote_state_backend_type`, `auth`, `command`, `dependencies`,
`source`, `provision`.

In addition, changes to a component's Terraform/OpenTofu files (and any local modules they reference) are detected by
scanning the component folder — reported as `component` and `component.module`.

The following sections are **intentionally not** evaluated:

- **`locals`**
  Used only to render templates, so any effective change already shows up in the rendered 
  `vars`
  /
  `env`
  /etc.
- **`overrides`**
  Folded into the merged 
  `vars`
  /
  `env`
  /
  `settings`
  /etc., so it would double-report.
- **`inheritance`**
  A derived (computed) inheritance chain; any real effect already appears in the other sections.
- **`retry`**
  Operational/execution-time behavior (how a run is retried), not provisioned infrastructure configuration.
- **`hooks`**

  Operational/execution-time behavior (commands that run before/after an operation, e.g. policy or cost checks),
  not provisioned infrastructure — so a hook change does not mark a component as affected by default. If you want
  hook changes to count, add `hooks` to [`describe.affected.sections`](/cli/configuration/describe); it then reports
  as `stack.hooks`.

:::tip Customize the evaluated sections
Use [`describe.affected.sections`](/cli/configuration/describe) in `atmos.yaml` to define your own list of evaluated
sections — for example to add a custom section or to narrow the set. When set, it fully replaces the defaults above
(`metadata` and `settings` are always evaluated regardless).
:::

:::tip Use in GitHub Actions
Run `atmos describe affected --format=matrix` directly in your workflow to fan out across affected components. When `ci.enabled` is `true` in `atmos.yaml`, the matrix is automatically written to `$GITHUB_OUTPUT`. See the [Deploy Affected workflow](/ci#deploy-affected) for the full pattern.
:::

## CI Auto-Detection

When `ci.enabled` is `true` in `atmos.yaml` and no `--base` flag is provided, `describe affected` automatically
resolves the base commit from the CI environment. This is **provider-agnostic** — each CI provider implements its own
resolution logic.

### GitHub Actions

| Event | Base Resolution |
|-------|----------------|
| `pull_request` (opened/synchronize, or closed without merging) | `git merge-base(HEAD, origin/)` — fork point. Falls back to `event.pull_request.base.sha` if merge-base is unavailable. |
| `pull_request` (closed and merged) | Checkout-classified (see below) — anchored on `event.pull_request.merge_commit_sha`, or on `event.pull_request.base.sha` for fast-forward merges, never on the moving tip of `` |
| `push` | Previous HEAD from event payload (`before`) |
| `push` (force-push) | Parent of current commit (`HEAD~1`) |
| `merge_group` | Merge queue base (`event.merge_group.base_sha`) |

The open-PR strategy uses `git merge-base` as the gold standard. When the local repository is a shallow checkout (the default for `actions/checkout@v6`), Atmos automatically runs a targeted `git fetch origin ` and retries — you do not need to set `fetch-depth: 0` on your checkout step. If even the auto-fetch fails (offline runner, deeply orphaned history), Atmos falls back to `event.pull_request.base.sha` from the event payload, which is at worst stale by however many commits have landed on `` since the PR was last synced.

**Merged pull requests** cannot compare against `origin/` — after the merge, the target branch already contains the PR, so that comparison degenerates. Instead, Atmos classifies which commit the workflow actually checked out and resolves the base that yields the PR's **net** change for that checkout:

| Checked-out commit | Base |
|--------------------|------|
| The PR head (`pull_request.head.sha`) | `merge-base(HEAD, merge_commit_sha^1)` — the true fork point, correct for merge, squash, and rebase strategies |
| The PR head, fast-forward merge (`merge_commit_sha` equals `head.sha`) | `merge-base(HEAD, base.sha)` — the fork point via the payload's pre-merge target commit |
| The merge commit (`pull_request.merge_commit_sha`) | Its first parent — the pre-merge target tip |
| GitHub's synthetic `refs/pull//merge` test merge | Its first parent — the target tip the merge was built on |
| Anything else | `event.pull_request.base.sha`, with a warning |

The classification is included in the `Auto-detected CI base` log line (`checkout=head.sha|merge-commit|synthetic-merge|unknown`), so an unexpected affected set can be diagnosed from a single log line.

**Example workflow** — no `--ref` or `--sha` needed:

```yaml
- name: Describe affected
  run: atmos describe affected --format=matrix
```

When `ci.enabled` is `true` in `atmos.yaml` and `GITHUB_OUTPUT` is available (e.g. on GitHub Actions), the matrix output is written there automatically. Outside of GitHub Actions, or when `GITHUB_OUTPUT` is unset, output falls back to stdout.

### Flag Precedence

1. `--base` flag (explicit)
2. `--ref` / `--sha` flags (deprecated, backward compatible)
3. CI auto-detection (when `ci.enabled` is `true`)
4. `refs/remotes/origin/HEAD` (default)

## Usage

```shell
atmos describe affected [options]
```

:::info YAML Functions and Authentication
By default, `atmos describe affected` executes YAML template functions (e.g., `!terraform.state`, `!terraform.output`) and Go templates during stack processing. When these functions access remote resources requiring authentication, use the `--identity` flag to authenticate before execution. You can disable function/template processing with `--process-functions=false` or `--process-templates=false` flags.
:::

:::tip
Run `atmos describe affected --help` to see all the available options
:::

## Examples

```shell
# Zero-config in CI (auto-detects base when ci.enabled is true)
atmos describe affected
# Explicit base commit (ref or SHA)
atmos describe affected --base main
atmos describe affected --base refs/tags/v1.16.0
atmos describe affected --base 3a5eafeab90426bd82bf5899896b28cc0bab3073
atmos describe affected --base refs/heads/main --format json
atmos describe affected --base refs/tags/v1.16.0 --file affected.yaml --format yaml
atmos describe affected --verbose=true
atmos describe affected --ssh-key 
atmos describe affected --ssh-key  --ssh-key-password 
atmos describe affected --repo-path 
atmos describe affected --include-spacelift-admin-stacks=true
atmos describe affected --clone-target-ref=true
atmos describe affected --include-dependents=true
atmos describe affected --include-settings=true
atmos describe affected --stack=plat-ue2-prod
atmos describe affected --upload=true
atmos describe affected --query 
atmos describe affected --process-templates=false
atmos describe affected --process-functions=false
atmos describe affected --skip=terraform.output
atmos describe affected --skip=terraform.output --skip=include
atmos describe affected --skip=include,eval
atmos describe affected --exclude-locked
# Authenticate before describing (when YAML functions require credentials)
atmos describe affected --identity my-aws-identity
atmos describe affected --identity # Interactive selection
# Disable authentication (use AWS SDK defaults)
atmos describe affected --identity=false
atmos describe affected -i my-aws-identity --ref refs/heads/main
# Filter to show only deleted components (for destruction workflows)
atmos describe affected --query '[.[] | select(.deleted == true)]'
# Filter to show only modified components (for apply workflows)
atmos describe affected --query '[.[] | select(.deleted != true)]'
```

## Example Output

```shell
> atmos describe affected --verbose=true

Cloning repo 'https://github.com/cloudposse/atmos' into the temp dir '/var/folders/g5/lbvzy_ld2hx4mgrgyp19bvb00000gn/T/16710736261366892599'

Checking out the HEAD of the default branch ...

Enumerating objects: 4215, done.
Counting objects: 100% (1157/1157), done.
Compressing objects: 100% (576/576), done.
Total 4215 (delta 658), reused 911 (delta 511), pack-reused 3058

Checked out Git ref 'refs/heads/main'

Current HEAD: 7d37c1e890514479fae404d13841a2754be70cbf refs/heads/describe-affected
BASE: 40210e8d365d3d88ac13c0778c0867b679bbba69 refs/heads/main

Changed files:

tests/fixtures/scenarios/complete/components/terraform/infra/vpc/main.tf
internal/exec/describe_affected.go
website/docs/cli/commands/describe/describe-affected.md

Affected components and stacks:

[
   {
      "component": "infra/vpc",
      "component_type": "terraform",
      "component_path": "components/terraform/infra/vpc",
      "stack": "tenant1-ue2-dev",
      "stack_slug": "tenant1-ue2-dev-infra-vpc",
      "spacelift_stack": "tenant1-ue2-dev-infra-vpc",
      "atlantis_project": "tenant1-ue2-dev-infra-vpc",
      "affected": "component"
   },
   {
      "component": "infra/vpc",
      "component_type": "terraform",
      "component_path": "components/terraform/infra/vpc",
      "stack": "tenant1-ue2-prod",
      "stack_slug": "tenant1-ue2-prod-infra-vpc",
      "spacelift_stack": "tenant1-ue2-prod-infra-vpc",
      "atlantis_project": "tenant1-ue2-prod-infra-vpc",
      "affected": "component"
   },
   {
      "component": "infra/vpc",
      "component_type": "terraform",
      "component_path": "components/terraform/infra/vpc",
      "stack": "tenant1-ue2-staging",
      "stack_slug": "tenant1-ue2-staging-infra-vpc",
      "spacelift_stack": "tenant1-ue2-staging-infra-vpc",
      "atlantis_project": "tenant1-ue2-staging-infra-vpc",
      "affected": "component"
   },
     {
    "component": "top-level-component3",
    "component_type": "terraform",
    "component_path": "components/terraform/top-level-component1",
    "stack": "tenant1-ue2-test-1",
    "stack_slug": "tenant1-ue2-test-1-top-level-component3",
    "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
    "affected": "file",
    "file": "tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf"
  },
  {
    "component": "top-level-component3",
    "component_type": "terraform",
    "component_path": "components/terraform/top-level-component1",
    "stack": "tenant1-ue2-test-1",
    "stack_slug": "tenant1-ue2-test-1-top-level-component3",
    "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
    "affected": "folder",
    "folder": "tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server"
  }
]
```

## Flags

- **`--base` (optional)**

  The base commit (ref or SHA) to compare against. Accepts both
  [Git References](https://git-scm.com/book/en/v2/Git-Internals-Git-References) and commit SHAs —
  the format is auto-detected.

  When `ci.enabled` is `true` in `atmos.yaml` and no base is provided, the base is automatically
  resolved from the CI environment (e.g., `GITHUB_BASE_REF` for pull requests).

  `atmos describe affected --base main`

  `atmos describe affected --base 3a5eafeab90426bd82bf5899896b28cc0bab3073`
- **`--ref` (deprecated)**

  **Deprecated: use `--base` instead.**
  [Git Reference](https://git-scm.com/book/en/v2/Git-Internals-Git-References) with which to compare the current working branch.
- **`--sha` (deprecated)**

  **Deprecated: use `--base` instead.**
  Git commit SHA with which to compare the current working branch.
- **`--file` (optional)**

  If specified, write the result to the file
- **`--format` (optional)**

  Specify the output format: `json` or `yaml` (`json` is default)
- **`--ssh-key` (optional)**

  Path to PEM-encoded private key to clone private repos using SSH
- **`--ssh-key-password` (optional)**

  Encryption password for the PEM-encoded private key if the key contains a password-encrypted PEM block
- **`--repo-path` (optional)**

  Path to the already cloned target repository with which to compare the current branch. Conflicts with `--base`, `--ref`, `--sha`, `--ssh-key` and `--ssh-key-password`
- **`--verbose` (optional)**

  Print more detailed output when cloning and checking out the target Git repository and processing the result
- **`--include-spacelift-admin-stacks` (optional)**

  Include the Spacelift admin stack of any stack that is affected by config changes
- **`--clone-target-ref` (optional)**

  Clone the target reference with which to compare the current branch.

  `atmos describe affected --clone-target-ref=true`

  If set to `false` (default), the target reference will be checked out instead.
  This requires that the target reference is already cloned by Git, and the information about it exists in the `.git` directory
- **`--stack` (optional)**

  Only show results for the specific stack.

  `atmos describe affected --stack=plat-ue2-prod`
- **`--include-dependents` (optional)**

  Include the dependent components and stacks.

  `atmos describe affected --include-dependents=true`
- **`--include-settings` (optional)**

  Include the `settings` section for each affected component.

  `atmos describe affected --include-settings=true`
- **`--query` (optional)**

  Query the results of the command using YQ expressions.

  `atmos describe affected --query=`

  For more details, refer to [YQ - a lightweight and portable command-line YAML processor](https://mikefarah.gitbook.io/yq)
- **`--process-templates` (optional)**

  Enable/disable processing of `Go` templates in Atmos stacks manifests when executing the command.
  If the flag is not provided, it's set to `true` by default.

  `atmos describe affected --process-templates=false`
- **`--process-functions` (optional)**

  Enable/disable processing of Atmos YAML functions in Atmos stacks manifests when executing the command.
  If the flag is not provided, it's set to `true` by default.

  `atmos describe affected --process-functions=false`
- **`--skip` (optional)**

  Skip processing a specific Atmos YAML function in Atmos stacks manifests when executing the command.
  To specify more than one function, use multiple `--skip` flags, or separate the functions with a comma:

  `atmos describe affected --skip=terraform.output --skip=include`

  `atmos describe affected --skip=terraform.output,include`
- **`--exclude-locked` (optional)**

  Exclude the locked components (`metadata.locked: true`) from the output.

  Refer to [Locking Components with `metadata.locked`](/stacks/components#locking-components-metadatalocked)

  `atmos describe affected --exclude-locked`
- **`--upload` (optional)**

  Upload the affected components and stacks to a specified HTTP endpoint.

  `atmos describe affected --upload=true`

  Atmos will perform an HTTP POST request to the URL `${ATMOS_PRO_BASE_URL}/${ATMOS_PRO_ENDPOINT}`,
  where the base URL is defined by the `ATMOS_PRO_BASE_URL` environment variable,
  and the URL path is defined by the `ATMOS_PRO_ENDPOINT`environment variable
- **`--identity` / `-i` (optional)**

  Authenticate with a specific identity before describing affected components.
  This is required when YAML template functions (e.g., `!terraform.state`, `!terraform.output`)
  need to access remote resources requiring authentication.
  Use without a value for interactive identity selection:

  `atmos describe affected --identity` (interactive)

  `atmos describe affected --identity my-aws-identity` (specific identity)

  For more details, refer to [Authentication](/cli/commands/auth/usage).

## Output

The command outputs a list of objects (in JSON or YAML format).

Each object has the following schema:

```json
{
  "component": "....",
  "component_type": "....",
  "component_path": "....",
  "stack": "....",
  "stack_slug": "....",
  "spacelift_stack": ".....",
  "atlantis_project": ".....",
  "affected": ".....",
  "affected_all": [],
  "file": ".....",
  "folder": ".....",
  "dependents": [],
  "included_in_dependents": "true | false",
  "settings": {},
  "deleted": "true | false",
  "deletion_type": "component | stack"
}
```

where:

- **`component`**

  The affected Atmos component.
- **`component_type`**

  The type of the component (`terraform` or `helmfile`).
- **`component_path`**

  The filesystem path to the `terraform` or `helmfile` component.
- **`stack`**

  The affected Atmos stack.
- **`stack_slug`**

  The Atmos stack slug (concatenation of the Atmos stack and Atmos component).
- **`spacelift_stack`**

  The affected Spacelift stack. It will be included only if the Spacelift workspace is enabled for the Atmos component in the
  Atmos stack in the `settings.spacelift.workspace_enabled` section (either directly in the component's `settings.spacelift.workspace_enabled` section
  or via inheritance).
- **`atlantis_project`**

  The affected Atlantis project name. It will be included only if the Atlantis integration is configured in
  the `settings.atlantis` section in the stack config. Refer to [Atlantis Integration](/cli/configuration/integrations/atlantis) for more details.
- **`file`**

  If the Atmos component depends on an external file, and the file was changed,
  the `file` attributes shows the modified file.
- **`folder`**

  If the Atmos component depends on an external folder, and any file in the folder was changed,
  the `folder` attributes shows the modified folder.
- **`dependents`**

  A list of components that depend on the current affected component. It will be populated only if the
  command-line flag `--include-dependents=true` is passed (to take dependencies into account) and there are other components
  that depend on the affected component in the stack.
  Refer to [`atmos describe dependents`](/cli/commands/describe/dependents) for more details. The `dependents` property is
  hierarchical - each component in the list will also contain a `dependents` property if that component has dependent
  components as well.
- **`settings`**

  The `settings` section of the component in the stack. It will be included only if the
  command-line flag `--include-settings=true` is passed. The `settings` sections is a free-form map used to pass
  configuration information to [integrations](/cli/configuration/integrations).
- **`deleted`**

  A boolean flag set to `true` when the component exists in the BASE branch but has been deleted in HEAD.
  This enables CI/CD pipelines to identify components that require `terraform destroy`.
- **`deletion_type`**

  The type of deletion when `deleted` is `true`. Possible values:
  - `component`: The component was removed from the stack (the stack still exists).
  - `stack`: The entire stack was deleted (all components in it are marked as deleted).
- **`included_in_dependents`**

  A boolean flag indicating if the affected component in the stack is also present in any of the `dependents`
  properties of the other affected components. It will be included only if the command-line flag `--include-dependents=true`
  is passed. If `included_in_dependents` is set to `true`, it indicates that the affected component in the stack is also
  present in any of the `dependents` lists in the dependency hierarchy of the other affected components.
  This flag can be used to decide whether to plan/apply the affected component - you might skip planning/applying the component
  since it's also a dependency of another affected component and will be triggered in the dependency order of the other
  affected component.
- **`affected`**

  Shows the first (in the processing order) section that was changed. The possible values are:
  - **`stack.vars`**

    The `vars` component section in the stack config has been modified.
  - **`stack.env`**

    The `env` component section in the stack config has been modified.
  - **`stack.settings`**

    The `settings` component section in the stack config has been modified.
  - **`stack.metadata`**

    The `metadata` component section in the stack config has been modified.
  - **`stack.providers`**

    The `providers` component section in the stack config has been modified.
  - **`stack.required_providers`**

    The `required_providers` component section (provider versions) in the stack config has been modified.
  - **`stack.required_version`**

    The `required_version` component section (the required Terraform/OpenTofu version) in the stack config has been modified.
  - **`stack.hooks`**

    The `hooks` component section in the stack config has been modified. Only reported when `hooks` is
    added to [`describe.affected.sections`](/cli/configuration/describe) — it is not evaluated by default.
  - **`stack.generate`**

    The `generate` component section in the stack config has been modified.
  - **`stack.backend`**

    The `backend` component section in the stack config has been modified.
  - **`stack.backend_type`**

    The `backend_type` component section in the stack config has been modified.
  - **`stack.remote_state_backend`**

    The `remote_state_backend` component section in the stack config has been modified.
  - **`stack.remote_state_backend_type`**

    The `remote_state_backend_type` component section in the stack config has been modified.
  - **`stack.auth`**

    The `auth` component section in the stack config has been modified.
  - **`stack.command`**

    The `command` component section (the binary used to provision the component) in the stack config has been modified.
  - **`stack.dependencies`**

    The `dependencies` component section in the stack config has been modified (for example, a component dependency was added or removed).
    This is complementary to the `file` and `folder` reasons, which fire when a _referenced_ file or folder changes.
  - **`stack.source`**

    The `source` component section (vendoring configuration) in the stack config has been modified.
  - **`stack.provision`**

    The `provision` component section (working directory configuration) in the stack config has been modified.
  - **`component`**

    The Terraform or Helmfile component that the Atmos component provisions has been changed.
  - **`component.module`**

    The Terraform component is affected because it uses a local Terraform module (not from the Terraform registry, but from the
    local filesystem), and that local module has been changed.

    For example, let's suppose that we have a catalog of reusable Terraform modules in the `modules` folder (outside the `components` folder), and
    we have defined the following `label` Terraform module in `modules/label`:
    ```hcl title="modules/label"
      module "label" {
        source  = "cloudposse/label/null"
        version = "0.25.0"
        context = module.this.context
      }

      output "label" {
        value       = module.label
        description = "Label outputs"
      }
    ```
    We then use the Terraform module in the `components/terraform/top-level-component1` component:
    ```hcl title="components/terraform/top-level-component1"
      module "service_2_label" {
        source  = "../../../modules/label"
        context = module.this.context
      }

      output "service_2_id" {
        value       = module.service_2_label.label.id
        description = "Service 2 ID"
      }
    ```
    The `label` module is not in the stack config of the `top-level-component1` component (not in the YAML stack config files), but Atmos
    understands Terraform dependencies (using a Terraform parser from HashiCorp), and can automatically detect any changes to the module.

    For example, if you make changes to any files in the folder `modules/label`, Atmos will detect the module changes, and since the module is a
    Terraform dependency of the `top-level-component1` component, Atmos will mark the component as affected with the `affected` attribute
    set to `component.module`:
    ```json
      [
        {
          "component": "top-level-component1",
          "component_type": "terraform",
          "component_path": "tests/fixtures/scenarios/complete/components/terraform/top-level-component1",
          "stack": "tenant1-ue2-staging",
          "stack_slug": "tenant1-ue2-staging-top-level-component1",
          "spacelift_stack": "tenant1-ue2-staging-top-level-component1",
          "atlantis_project": "tenant1-ue2-staging-top-level-component1",
          "affected": "component.module",
          "affected_all": [
            "component.module"
          ]
        },
        {
          "component": "top-level-component1",
          "component_type": "terraform",
          "component_path": "tests/fixtures/scenarios/complete/components/terraform/top-level-component1",
          "stack": "tenant2-ue2-staging",
          "stack_slug": "tenant2-ue2-staging-top-level-component1",
          "spacelift_stack": "tenant2-ue2-staging-top-level-component1",
          "atlantis_project": "tenant2-ue2-staging-top-level-component1",
          "affected": "component.module",
          "affected_all": [
            "component.module"
          ]
        }
      ]
    ```
  - **`stack.settings.spacelift.admin_stack_selector`**

    The Atmos component for the Spacelift admin stack.

    This will be included only if all of the following is true:
    - The `atmos describe affected` is executed with the `--include-spacelift-admin-stacks=true` flag

    - Any of the affected Atmos components has configured the section `settings.spacelift.admin_stack_selector` pointing to the Spacelift admin
      stack that manages the components.

      For example:

      ```yaml title="stacks/orgs/cp/tenant1/_defaults.yaml"
      settings:
        spacelift:
          # All Spacelift child stacks for the `tenant1` tenant are managed by the
          # `tenant1-ue2-prod-infrastructure-tenant1` Spacelift admin stack.
          # The `admin_stack_selector` attribute is used to find the affected Spacelift
          # admin stack for each affected Atmos stack
          # when executing the command
          # `atmos describe affected --include-spacelift-admin-stacks=true`
          admin_stack_selector:
            component: infrastructure-tenant1
            tenant: tenant1
            environment: ue2
            stage: prod
      ```

    - The Spacelift admin stack is enabled by `settings.spacelift.workspace_enabled` set to `true`.

      For example:

      ```yaml title="stacks/catalog/terraform/spacelift/infrastructure-tenant1.yaml"
      components:
        terraform:
          infrastructure-tenant1:
            metadata:
              component: spacelift
              inherits:
                - spacelift-defaults
            settings:
              spacelift:
                workspace_enabled: true
      ```
  - **`file`**

    An external file on the local filesystem that the Atmos component depends on was changed.

    Dependencies on external files (not in the component's folder) are declared with the [`dependencies.files`](/stacks/dependencies/components) sibling key.

    For example:
    ```yaml title="stacks/catalog/terraform/top-level-component3.yaml"
    components:
      terraform:
        top-level-component3:
          metadata:
            component: "top-level-component1"
          dependencies:
            files:
              - "tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf"
    ```
    In the configuration above, we specify that the Atmos component `top-level-component3` depends on the file
    `tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf` (which is not in the component's folder). If the file gets modified,
    the component `top-level-component3` will be included in the `atmos describe affected` command output.

    :::note Legacy formats
    Two older shapes still parse for backward compatibility:
    - The inline form `dependencies.components: [{ kind: file, path: ... }]` (shipped in v1.210.0).
    - The much older [`settings.depends_on`](/stacks/settings/depends_on) map with numeric keys and a `file:` attribute.
    New configurations should use `dependencies.files` instead.
    :::

    For example:
    ```json
      [
        {
          "component": "top-level-component3",
          "component_type": "terraform",
          "component_path": "components/terraform/top-level-component1",
          "stack": "tenant1-ue2-test-1",
          "stack_slug": "tenant1-ue2-test-1-top-level-component3",
          "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
          "affected": "file",
          "affected_all": [
            "file"
          ],
          "file": "tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf"
        }
      ]
    ```
  - **`folder`**

    Any file in an external folder that the Atmos component depends on was changed.

    Dependencies on external folders are declared with the [`dependencies.folders`](/stacks/dependencies/components) sibling key. You can mix `files` and `folders` keys in the same `dependencies` block.

    For example:
    ```yaml title="stacks/catalog/terraform/top-level-component3.yaml"
    components:
      terraform:
        top-level-component3:
          metadata:
            component: "top-level-component1"
          dependencies:
            files:
              - "tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf"
            folders:
              - "tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server"
    ```
    In the configuration above, we specify that the Atmos component `top-level-component3` depends on the folder
    `tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server`. If any file in the folder gets modified,
    the component `top-level-component3` will be included in the `atmos describe affected` command output.

    :::note Legacy formats
    Two older shapes still parse for backward compatibility:
    - The inline form `dependencies.components: [{ kind: folder, path: ... }]` (shipped in v1.210.0).
    - The much older [`settings.depends_on`](/stacks/settings/depends_on) map with numeric keys and a `folder:` attribute.
    New configurations should use `dependencies.folders` instead.
    :::

    For example:
    ```json
      [
        {
          "component": "top-level-component3",
          "component_type": "terraform",
          "component_path": "components/terraform/top-level-component1",
          "stack": "tenant1-ue2-test-1",
          "stack_slug": "tenant1-ue2-test-1-top-level-component3",
          "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
          "affected": "folder",
          "affected_all": [
            "folder"
          ],
          "folder": "tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server"
        }
      ]
    ```
  - **`deleted`**

    The component was deleted (exists in BASE but not in HEAD). This happens when a component is removed from
    a stack configuration. The component's `deleted` field will be set to `true` and `deletion_type` to `component`.

    For example, if you remove the `monitoring` component from the `prod-us-east-1` stack:
    ```json
      [
        {
          "component": "monitoring",
          "component_type": "terraform",
          "component_path": "components/terraform/monitoring",
          "stack": "prod-us-east-1",
          "stack_slug": "prod-us-east-1-monitoring",
          "affected": "deleted",
          "affected_all": [
            "deleted"
          ],
          "deleted": true,
          "deletion_type": "component"
        }
      ]
    ```
    Use this in CI/CD pipelines to trigger `terraform destroy` for removed components.
  - **`deleted.stack`**

    The entire stack was deleted. All components in the stack are marked as deleted with `deletion_type: stack`.

    For example, if you delete the entire `staging-us-west-2` stack:
    ```json
      [
        {
          "component": "vpc",
          "component_type": "terraform",
          "component_path": "components/terraform/vpc",
          "stack": "staging-us-west-2",
          "stack_slug": "staging-us-west-2-vpc",
          "affected": "deleted.stack",
          "affected_all": [
            "deleted.stack"
          ],
          "deleted": true,
          "deletion_type": "stack"
        },
        {
          "component": "eks",
          "component_type": "terraform",
          "component_path": "components/terraform/eks",
          "stack": "staging-us-west-2",
          "stack_slug": "staging-us-west-2-eks",
          "affected": "deleted.stack",
          "affected_all": [
            "deleted.stack"
          ],
          "deleted": true,
          "deletion_type": "stack"
        }
      ]
    ```
- **`affected_all`**

  Shows all component sections and attributes that were changed.

  For example, if you make changes to the `vars` and `settings` sections of the component `component-1` in the
  `nonprod` stack, and execute `atmos describe affected`, you will get the following result:
  ```json
    [
      {
        "component": "component-1",
        "component_type": "terraform",
        "stack": "nonprod",
        "stack_slug": "nonprod-component-1",
        "affected": "stack.vars",
        "affected_all": [
           "stack.vars",
           "stack.settings"
        ]
      }
    ]
  ```
  If you create a new Terraform/Tofu component, configure a new Atmos component `component-1` in the
  `nonprod` stack, and execute `atmos describe affected`, you will get the following result:
  ```json
  [
    {
      "component": "component-1",
      "component_type": "terraform",
      "stack": "nonprod",
      "stack_slug": "nonprod-component-1",
      "affected": "stack.metadata",
      "affected_all": [
        "component",
        "stack.metadata",
        "stack.vars",
        "stack.env",
        "stack.settings"
      ]
    }
  ]
  ```
  where:
  - **`affected`**

    Shows that the Atmos component's `metadata` section was changed
    (since the component is new and the `metadata` section is the first section that Atmos processes).
  - **`affected_all`**

    Shows all the affected sections and attributes:
    - **`component`**

      The Terraform component (Terraform configuration) was affected (since it was just added).
    - **`stack.metadata`**

      The Atmos component's `metadata` section was changed.
    - **`stack.vars`**

      The Atmos component's `vars` section was changed.
    - **`stack.env`**

      The Atmos component's `env` section was changed.
    - **`stack.settings`**

      The Atmos component's `settings` section was changed.

:::note

[Abstract Atmos components](/design-patterns/inheritance-patterns/abstract-component) (`metadata.type` is set to `abstract`)
are not included in the output since they serve as blueprints for other Atmos components and are not meant to be provisioned.

[Disabled Atmos components](/stacks/components#disabling-components-metadataenabled) (`metadata.enabled` is set to `false`)
are also not included in the output since they are explicitly disabled.

:::

## Output Example

```shell
[
  {
    "component": "infrastructure-tenant1",
    "component_type": "terraform",
    "component_path": "tests/fixtures/scenarios/complete/components/terraform/spacelift",
    "stack": "tenant1-ue2-prod",
    "stack_slug": "tenant1-ue2-prod-infrastructure-tenant1",
    "spacelift_stack": "tenant1-ue2-prod-infrastructure-tenant1",
    "atlantis_project": "tenant1-ue2-prod-infrastructure-tenant1",
    "affected": "stack.settings.spacelift.admin_stack_selector",
    "affected_all": [
        "stack.settings.spacelift.admin_stack_selector"
    ]
  },
  {
    "component": "infrastructure-tenant2",
    "component_type": "terraform",
    "component_path": "tests/fixtures/scenarios/complete/components/terraform/spacelift",
    "stack": "tenant2-ue2-prod",
    "stack_slug": "tenant2-ue2-prod-infrastructure-tenant2",
    "spacelift_stack": "tenant2-ue2-prod-infrastructure-tenant2",
    "atlantis_project": "tenant2-ue2-prod-infrastructure-tenant2",
    "affected": "stack.settings.spacelift.admin_stack_selector",
    "affected_all": [
      "stack.settings.spacelift.admin_stack_selector"
    ]
  },
  {
    "component": "test/test-component-override-2",
    "component_type": "terraform",
    "component_path": "components/terraform/test/test-component",
    "stack": "tenant1-ue2-dev",
    "stack_slug": "tenant1-ue2-dev-test-test-component-override-2",
    "spacelift_stack": "tenant1-ue2-dev-new-component",
    "atlantis_project": "tenant1-ue2-dev-new-component",
    "affected": "stack.vars",
    "affected_all": [
      "stack.vars"
    ]
  },
  {
    "component": "infra/vpc",
    "component_type": "terraform",
    "component_path": "components/terraform/infra/vpc",
    "stack": "tenant2-ue2-staging",
    "stack_slug": "tenant1-ue2-staging-infra-vpc",
    "spacelift_stack": "tenant1-ue2-staging-infra-vpc",
    "atlantis_project": "tenant1-ue2-staging-infra-vpc",
    "affected": "component",
    "affected_all": [
      "component"
    ]
  },
  {
    "component": "test/test-component-override-3",
    "component_type": "terraform",
    "component_path": "components/terraform/test/test-component",
    "stack": "tenant1-ue2-prod",
    "stack_slug": "tenant1-ue2-prod-test-test-component-override-3",
    "atlantis_project": "tenant1-ue2-prod-test-test-component-override-3",
    "affected": "stack.env",
    "affected_all": [
      "stack.env"
    ]
  },
  {
    "component": "top-level-component3",
    "component_type": "terraform",
    "component_path": "components/terraform/top-level-component1",
    "stack": "tenant1-ue2-test-1",
    "stack_slug": "tenant1-ue2-test-1-top-level-component3",
    "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
    "affected": "file",
    "affected_all": [
      "file",
      "folder"
    ]
    "file": "tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf"
  },
  {
    "component": "top-level-component3",
    "component_type": "terraform",
    "component_path": "components/terraform/top-level-component1",
    "stack": "tenant1-ue2-test-1",
    "stack_slug": "tenant1-ue2-test-1-top-level-component3",
    "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
    "affected": "folder",
    "affected_all": [
      "file",
      "folder"
    ]
    "folder": "tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server"
  }
]
```

## Affected Components with Dependencies

The output of the `atmos describe affected` command can include dependencies for the affected components.

If the command-line flag `--include-dependents=true` is passed to the `atmos describe affected` command, and there are
other components that depend on the affected components in the stack, the command will include a `dependents`
property (list) for each affected component. The `dependents` property is hierarchical - each component in the list will
also contain a `dependents` property if that component has dependent components as well.

For example, suppose that we have the following configuration for the Atmos components `component-1`, `component-2` and
`component-3` in the stack `plat-ue2-dev`:

**File:** `stacks/orgs/acme/plat/dev/us-east-2.yaml`

```yaml
components:
    terraform:
      component-1:
        metadata:
          component: "terraform-component-1"
        vars: {}

      component-2:
        metadata:
          component: "terraform-component-2"
        vars: {}
        dependencies:
          components:
            - name: "component-1"

      component-3:
        metadata:
          component: "terraform-component-3"
        vars: {}
        dependencies:
          components:
            - name: "component-2"
```

:::tip
For more details on how to configure component dependencies, refer to [`atmos describe dependents`](/cli/commands/describe/dependents)
:::

In the above configuration, `component-3` depends on `component-2`, whereas `component-2` depends on `component-1`.

If all the components are affected (modified) in the current working branch,
the `atmos describe affected --include-dependents=true` command will produce the following result:

```shell
[
   {
     "component": "component-1",
     "stack": "plat-ue2-dev",
     "stack_slug": "plat-ue2-dev-component-1",
     "included_in_dependents": false,
     "dependents": [
       {
         "component": "component-2",
         "stack": "plat-ue2-dev",
         "stack_slug": "plat-ue2-dev-component-2",
         "dependents": [
           {
             "component": "component-3",
             "stack": "plat-ue2-dev",
             "stack_slug": "plat-ue2-dev-component-3"
           }
         ]
       }
     ]
   },
   {
     "component": "component-2",
     "stack": "plat-ue2-dev",
     "stack_slug": "plat-ue2-dev-component-2",
     "included_in_dependents": true,
     "dependents": [
       {
         "component": "component-3",
         "stack": "plat-ue2-dev",
         "stack_slug": "plat-ue2-dev-component-3"
       }
     ]
   },
   {
     "component": "component-3",
     "stack": "plat-ue2-dev",
     "stack_slug": "plat-ue2-dev-component-3",
     "included_in_dependents": true
   }
 ]
```

The `component-1` component does not depend on any other component, and therefore it has the `included_in_dependents`
attribute set to `false`. The `component-2` and `component-3` components depend on other components and are included in
the `dependents` property of the other components, and hence the `included_in_dependents` attribute is set to `true`.

When processing the above output, you might decide to not plan/apply the `component-2` and `component-3` components
since they are in the `dependents` property of the `component-1` component. Instead, you might just
trigger `component-1` and then `component-2` and `component-3` in the order of dependencies.

## Detecting Deleted Components in Affected Stacks

The `atmos describe affected` command automatically detects components and stacks that exist in the BASE branch
but have been deleted in HEAD. This enables CI/CD pipelines to trigger `terraform destroy` for removed infrastructure.

### How it Works

When comparing HEAD (current branch) with BASE (target branch), Atmos not only detects modifications but also:

1. **Deleted components**: Components that exist in BASE but not in HEAD (within the same stack)
2. **Deleted stacks**: Entire stacks that exist in BASE but not in HEAD

Deleted components are marked with:

- `deleted: true` - Indicates the component was deleted
- `deletion_type: component` or `deletion_type: stack` - Specifies whether just the component or the entire stack was removed
- `affected: deleted` or `affected: deleted.stack` - The affected reason

### Filtering Deleted vs. Modified Components

Use `--query` or `jq` to separate deleted components from modified ones:

```shell
# Get only deleted components (for destruction)
atmos describe affected --query '[.[] | select(.deleted == true)]'

# Get only modified components (for apply)
atmos describe affected --query '[.[] | select(.deleted != true)]'

# Get deleted components in a specific stack
atmos describe affected --query '[.[] | select(.deleted == true and .stack == "prod-us-east-1")]'
```

### CI/CD Pipeline Example

Here's a GitHub Actions workflow that separates apply and destroy operations:

```yaml
jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      modified: ${{ steps.affected.outputs.modified }}
      deleted: ${{ steps.affected.outputs.deleted }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - uses: cloudposse/github-action-setup-atmos@v2

      - name: Detect affected
        id: affected
        run: |
          # Get all affected components
          atmos describe affected --format json > affected.json

          # Filter modified components (for apply)
          jq '[.[] | select(.deleted != true)]' affected.json > modified.json
          echo "modified=$(cat modified.json | jq -c)" >> $GITHUB_OUTPUT

          # Filter deleted components (for destroy)
          jq '[.[] | select(.deleted == true)]' affected.json > deleted.json
          echo "deleted=$(cat deleted.json | jq -c)" >> $GITHUB_OUTPUT

  apply:
    needs: detect-changes
    if: needs.detect-changes.outputs.modified != '[]'
    strategy:
      matrix:
        include: ${{ fromJson(needs.detect-changes.outputs.modified) }}
    steps:
      - uses: actions/checkout@v6
      - uses: cloudposse/github-action-setup-atmos@v2
      - run: atmos terraform apply ${{ matrix.component }} -s ${{ matrix.stack }}

  destroy:
    needs: detect-changes
    if: needs.detect-changes.outputs.deleted != '[]'
    environment: production  # Requires manual approval via GitHub Environment protection rules
    strategy:
      matrix:
        include: ${{ fromJson(needs.detect-changes.outputs.deleted) }}
    steps:
      # IMPORTANT: Check out the BASE branch (target branch), not HEAD.
      # The deleted component's configuration only exists in BASE.
      - uses: actions/checkout@v6
        with:
          ref: ${{ github.base_ref }}
      - uses: cloudposse/github-action-setup-atmos@v2
      - run: atmos terraform destroy ${{ matrix.component }} -s ${{ matrix.stack }} --auto-approve
```

:::warning Destroy Operations
Destruction of cloud resources is irreversible. The example above uses `--auto-approve` for automation but
includes `environment: production` to require manual approval via
[GitHub Environment protection rules](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#environment-protection-rules).
Always review the list of deleted components before running `terraform destroy`.

**Important:** The destroy job must check out the **BASE branch** (target branch, e.g., `main`) rather than HEAD.
This is because `terraform destroy` needs access to the component's stack configuration and Terraform files,
which only exist in BASE — they've been deleted in HEAD.
:::

:::note Abstract Components
[Abstract components](/design-patterns/inheritance-patterns/abstract-component) (`metadata.type: abstract`)
are not reported as deleted since they are blueprints and are not provisioned.
:::

## Working with Private Repositories

There are a few ways to work with private repositories with which the current local branch is compared to detect the changed files and affected Atmos
stacks and components:

- Using the `--ssh-key` flag to specify the filesystem path to a PEM-encoded private key to clone private repos using SSH, and
  the `--ssh-key-password` flag to provide the encryption password for the PEM-encoded private key if the key contains a password-encrypted PEM block

- Execute the `atmos describe affected --repo-path ` command in a [GitHub Action](https://docs.github.com/en/actions).
  For this to work, clone the remote private repository using the [checkout](https://github.com/actions/checkout) GitHub action. Then use
  the `--repo-path` flag to specify the path to the already cloned target repository with which to compare the current branch

- It should just also work with whatever SSH config/context has been already set up, for example, when
  using [SSH agents](https://www.ssh.com/academy/ssh/agent). In this case, you don't need to use the `--ssh-key`, `--ssh-key-password`
  and `--repo-path` flags to clone private repositories

## Using with GitHub Actions

If the `atmos describe affected` command is executed in a [GitHub Action](https://docs.github.com/en/actions), and you don't want to store or
generate a long-lived SSH private key on the server, you can do the following (**NOTE:** This is only required if the action is attempting to clone a
private repo which is not itself):

- Create a GitHub
  [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)
  with scope permissions to clone private repos

- Add the created PAT as a repository or GitHub organization [secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets)

- In your GitHub action, clone the remote repository using the [checkout](https://github.com/actions/checkout) GitHub action

- Execute `atmos describe affected` command with the `--repo-path` flag set to the cloned repository path using
  the [`GITHUB_WORKSPACE`](https://docs.github.com/en/actions/learn-github-actions/variables) ENV variable (which points to the default working
  directory on the GitHub runner for steps, and the default location of the repository when using the [checkout](https://github.com/actions/checkout)
  action). For example:

  ```shell
  atmos describe affected --repo-path $GITHUB_WORKSPACE
  ```

## Upload the affected components and stacks to an HTTP endpoint

If the `--upload=true` command-line flag is passed, Atmos will upload the affected components and stacks to a
specified HTTP endpoint.

The endpoint can process the affected components and their dependencies in a CI/CD pipeline (e.g. execute
`terraform apply` on all the affected components in the stacks and all the dependencies).

Atmos will perform an HTTP POST request to the URL `${ATMOS_PRO_BASE_URL}/${ATMOS_PRO_ENDPOINT}`, where the base URL
is defined by the `ATMOS_PRO_BASE_URL` environment variable, and the URL path is defined by the `ATMOS_PRO_ENDPOINT`
environment variable.

An [Authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) header
`Authorization: Bearer $ATMOS_PRO_TOKEN` will be added to the HTTP request (if the `ATMOS_PRO_TOKEN` environment
variable is set) to provide credentials to authenticate with the server.

:::note
If the `--upload=true` command-line flag is passed, the `--include-dependencies` and `--include-settings` flags are
automatically set to `true`, so the affected components will be uploaded with their dependencies and settings
(if they are configured in Atmos stack manifests).
:::

The payload of the HTTP POST request will be a JSON object with the following schema:

```shell
{
     "base_sha": "6746ba4df9e87690c33297fe740011e5ccefc1f9",
     "head_sha": "5360d911d9bac669095eee1ca1888c3ef5291084",
     "repo_url": "https://github.com/cloudposse/atmos",
     "repo_host": "github.com",
     "repo_name": "atmos",
     "repo_owner": "cloudposse",
     "stacks": [
        {
          "component": "vpc",
          "component_type": "terraform",
          "component_path": "examples/quick-start-advanced/components/terraform/vpc",
          "stack": "plat-ue2-dev",
          "stack_slug": "plat-ue2-dev-vpc",
          "affected": "stack.vars",
          "included_in_dependents": false,
          "dependents": [],
          "settings": {}
        }
    ]
 }
```

where:

- **`base_sha`**

  the Git commit SHA of the base branch against which the changes in the current commit are compared
- **`head_sha`**

  the SHA of the current Git commit
- **`repo_url`**

  the URL of the current repository
- **`repo_name`**

  the name of the current repository
- **`repo_owner`**

  the owner of the current repository
- **`repo_host`**

  the host of the current repository
- **`stacks`**

  a list of affected components and stacks with their dependencies and settings

---

## atmos describe component

Use this command to describe the complete configuration for an [Atmos component](/components) in
an [Atmos stack](/learn/stacks).

## Usage

Execute the `atmos describe component` command like this:

```shell
atmos describe component  -s 
```

### Path-Based Component Resolution

Atmos supports using filesystem paths instead of component names for convenience. This allows you to navigate to a component directory and use `.` to reference it:

```shell
# Navigate to component directory
cd components/terraform/vpc

# Use . to reference current directory
atmos describe component . --stack dev
```

This automatically resolves the path to the component name configured in your stack, eliminating the need to remember exact component names.

**Supported path formats:**

- `.` - Current directory
- `./component` - Relative path from current directory
- `../other-component` - Relative path to sibling directory
- `/absolute/path/to/component` - Absolute path

**Requirements:**

- Must be inside a component directory under the configured base path
- Must specify `--stack` flag
- Component must exist in the specified stack configuration
- **The component path must resolve to a unique component name** - If multiple components in the stack reference the same component path, you must use the unique component name instead of the path

**Error handling:**
If the path cannot be resolved, Atmos will provide a clear error message explaining whether the path is within component directories and whether the component exists in the stack.

:::warning Path Resolution Limitation
Path-based resolution only works when the component path resolves to a **single unique component** in the stack.

For example, if both `station/1` and `station/2` reference `components/terraform/weather`:

```bash
cd components/terraform/weather
atmos describe component . --stack dev  # ❌ Error: ambiguous - which component?
```

Instead, you must use the unique component names:

```bash
atmos describe component station/1 --stack dev  # ✓ Explicit and unambiguous
atmos describe component station/2 --stack dev  # ✓ Explicit and unambiguous
```

:::

:::info YAML Functions and Authentication
By default, `atmos describe component` executes YAML template functions (e.g., `!terraform.state`, `!terraform.output`) and Go templates during component processing. When these functions access remote resources requiring authentication, use the `--identity` flag to authenticate before execution. You can disable function/template processing with `--process-functions=false` or `--process-templates=false` flags.
:::

:::tip
Run `atmos describe component --help` to see all the available options
:::

## Examples

### Component Name Examples

```shell
atmos describe component infra/vpc -s tenant1-ue2-dev

atmos describe component infra/vpc -s tenant1-ue2-dev --format json

atmos describe component infra/vpc -s tenant1-ue2-dev -f yaml

atmos describe component infra/vpc -s tenant1-ue2-dev --file component.yaml

atmos describe component echo-server -s tenant1-ue2-staging

atmos describe component test/test-component-override -s tenant2-ue2-prod

atmos describe component vpc -s tenant1-ue2-dev --process-templates=false

atmos describe component vpc -s tenant1-ue2-dev --process-functions=false

atmos describe component vpc -s tenant1-ue2-dev --skip=terraform.output

atmos describe component vpc -s tenant1-ue2-dev --skip=terraform.output --skip=include

atmos describe component vpc -s tenant1-ue2-dev --skip=include,eval

atmos describe component vpc -s plat-ue2-prod --query .vars.tags

atmos describe component vpc -s plat-ue2-prod -q .settings

atmos describe component vpc -s plat-ue2-prod --pager=more

atmos describe component vpc -s tenant1-ue2-dev --provenance

# Provenance annotations are on by default; disable them for plain output
atmos describe component vpc -s tenant1-ue2-dev --provenance=false

# Authenticate before describing (when YAML functions require credentials)
atmos describe component vpc -s tenant1-ue2-dev --identity my-aws-identity

atmos describe component vpc -s tenant1-ue2-dev --identity # Interactive selection

# Disable authentication (use AWS SDK defaults)
atmos describe component vpc -s tenant1-ue2-dev --identity=false

atmos describe component vpc -s tenant1-ue2-dev -i my-aws-identity
```

### Path-Based Examples

```shell
# Navigate to component directory and use current directory
cd components/terraform/vpc
atmos describe component . --stack dev

# Use relative path
cd components/terraform
atmos describe component ./vpc --stack dev

# Use from project root with relative path
atmos describe component components/terraform/vpc --stack dev

# Describe nested component
cd components/terraform/infra/vpc
atmos describe component . --stack dev

# Combine with other flags
cd components/terraform/vpc
atmos describe component . --stack dev --format json
atmos describe component . --stack dev --query .vars.tags
atmos describe component . --stack dev --provenance
```

## Arguments

- **`component` (required)**

  Atmos component name or filesystem path.
  Supports both:

  Component names: vpc, infra/vpc, test/test-component
  Filesystem paths: . (current directory), ./vpc, components/terraform/vpc

  When using paths, Atmos automatically resolves the path to the component name based on your stack configuration.

## Flags

- **`--use-mocks` (optional)**
  Resolve Terraform YAML lookups from the producer’s literal

  [component mocks](/stacks/components/mocks)
   instead of its remote state.
  Default: 
  `false`
  . Requires YAML function processing to remain enabled.
- **`--stack` / `-s` (required)**
  Atmos stack.
- **`--format` / `-f` (optional)**
  Output format: 
  `yaml`
   or 
  `json`
   (
  `yaml`
   is default).
- **`--file` (optional)**
  If specified, write the result to the file.
- **`--process-templates` (optional)**
  Enable/disable processing of all 
  `Go`
   templates
  in Atmos stacks manifests when executing the command.
  Use the flag to see the component configuration
  before and after the templates are processed.
  If the flag is not provided, it's set to 
  `true`
   by default.
  `atmos describe component  -s  --process-templates=false`
  .
- **`--process-functions` (optional)**
  Enable/disable processing of all Atmos YAML functions
  in Atmos stacks manifests when executing the command.
  Use the flag to see the component configuration
  before and after the functions are processed.
  If the flag is not provided, it's set to 
  `true`
   by default.
  `atmos describe component  -s  --process-functions=false`
  .
- **`--skip` (optional)**
  Skip processing a specific Atmos YAML function
  in Atmos stacks manifests when executing the command.
  To specify more than one function,
  use multiple 
  `--skip`
   flags, or separate the functions with a comma:
  `atmos describe component  -s  --skip=terraform.output --skip=include`
  `atmos describe component  -s  --skip=terraform.output,include`
  .
- **`--query` / `-q` (optional)**
  Query the results of the command using 
  `yq`
   expressions.
  `atmos describe component  -s  --query .vars.tags`
  For more details, refer to https://mikefarah.gitbook.io/yq.
- **`describe.component.filter` (atmos.yaml setting)**

  Controls the output scope: schema (default) limits the output to the sections a stack manifest can define (vars, settings, env, backend, metadata, overrides, providers, imports, dependencies, component, hooks); full includes every internal field Atmos computes during processing.

  Precedence (highest wins): ATMOS\_DESCRIBE\_COMPONENT\_FILTER environment variable > describe.component.filter in atmos.yaml > default (schema).

  Queries via --query always evaluate against the full data regardless of this setting. The output was unfiltered before the 2026-07-17 edition; a project pinned to an earlier edition keeps the full output.
- **`--provenance` (optional)**

  Enable provenance tracking to show where configuration values originated.

  Enabled by default. Component descriptions include provenance annotations unless disabled. Disable per-invocation with --provenance=false, per-environment with the ATMOS\_DESCRIBE\_PROVENANCE environment variable, or per-project with describe.provenance: false in atmos.yaml.

  Precedence (highest wins): --provenance flag > ATMOS\_DESCRIBE\_PROVENANCE environment variable > describe.provenance in atmos.yaml > default (true).

  Provenance was off by default before the 2026-07-16 edition; a project pinned to an earlier edition keeps it off.

  YAML format on TTY:

  Inline comments with file path, line number, and column
  Symbol-based inheritance indicators: ● (defined/overridden), ○ (inherited), ∴ (computed/templated)
  Depth tracking with \[N] notation: \[1] for parent stack, \[2+] for deeper imports
  Color-coded depth indicators: cyan (depth 1), green (depth 2), orange (depth 3), red (depth 4+)
  Two-column side-by-side layout (Configuration │ Provenance)

  YAML format non-TTY (pipes):

  Inline comments without color codes
  Single-column layout (preserves valid YAML)

  JSON format:

  Provenance embedded as \_\_atmos\_provenance metadata fields
  Structure: 
  No inline comments (JSON doesn't support comments)

  atmos describe component \ -s \ --provenance
- **`--pager` (optional)**
  Disable/Enable the paging user experience.
- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before describing the component.
  This is required when YAML template functions (e.g., 
  `!terraform.state`
  , 
  `!terraform.output`
  )
  need to access remote resources requiring authentication.
  Use without a value for interactive identity selection:
  `atmos describe component  -s  --identity`
   (interactive)
  `atmos describe component  -s  --identity my-aws-identity`
   (specific identity)
  For more details, refer to 
  [Authentication](/cli/commands/auth/usage)
  .

## Output

The command outputs the final deep-merged component configuration.

The output contains the following sections:

- `atlantis_project` - Atlantis project name (if [Atlantis Integration](/cli/configuration/integrations/atlantis) is configured for the component in the stack)

- `atmos_cli_config` - information about Atmos CLI configuration from `atmos.yaml`

- `atmos_component` - [Atmos component](/components) name

- `atmos_stack` - [Atmos stack](/learn/stacks) name

- `stack` - same as `atmos_stack`

- `atmos_stack_file` - the stack manifest where the Atmos stack is defined

- `atmos_manifest` - same as `atmos_stack_file`

- `backend` - Terraform/OpenTofu backend configuration

- `backend_type` - Terraform/OpenTofu backend type

- `command` - the binary to execute when provisioning the component (e.g. `terraform`, `terraform-1`, `tofu`, `helmfile`)

- `component` - the Terraform/OpenTofu component for which the Atmos component provides configuration

- `component_type` - the type of the component (`terraform` or `helmfile`)

- `component_info` - a block describing the Terraform or Helmfile components that the Atmos component manages. The `component_info` block has the
  following sections:
  - `component_path` - the filesystem path to the Terraform/OpenTofu or Helmfile component

  - `component_type` - the type of the component (`terraform` or `helmfile`)

  - `terraform_config` - if the component type is `terraform`, this sections describes the high-level metadata about the Terraform component from its
    source code, including variables, outputs and child Terraform modules (using a Terraform parser from HashiCorp). The file names and line numbers
    where the variables, outputs and child modules are defined are also included. Invalid Terraform configurations are also detected, and in case of
    any issues, the warnings and errors are shows in the `terraform_config.diagnostics` section

- `env` - a map of ENV variables defined for the Atmos component

- `inheritance` - component's [inheritance chain](/howto/inheritance)

- `metadata` - component's metadata config

- `remote_state_backend` - Terraform/OpenTofu backend config for remote state

- `remote_state_backend_type` - Terraform/OpenTofu backend type for remote state

- `settings` - component settings (free-form map)

- `sources` - sources of the values from the component's sections (`vars`, `env`, `settings`)

- `spacelift_stack` - legacy Spacelift stack name (if the [legacy Spacelift integration](/deprecated/spacelift) is configured for the component in the stack
  and `settings.spacelift.workspace_enabled` is set to `true`)

- `vars` - the final deep-merged component variables that are provided to Terraform/OpenTofu and Helmfile when executing
  `atmos terraform` and `atmos helmfile` commands

- `workspace` - Terraform/OpenTofu workspace for the Atmos component

- `imports` - a list of all imports in the Atmos stack (this shows all imports in the stack, related to the component and not)

- `deps_all` - a list of all component stack dependencies (stack manifests where the component settings are defined, either inline or via imports)

- `deps` - a list of component stack dependencies where the _final_ values of all component configurations are defined
  (after the deep-merging and processing all the inheritance chains and all the base components)

- `overrides` - a map of overrides for the component. Refer to [Component Overrides](/stacks/overrides) for more details

- `providers` - a map of provider configurations for the component

## Difference between `imports`, `deps_all` and `deps` outputs

The difference between the `imports`, `deps_all` and `deps` outputs is as follows:

- `imports` shows all imports in the stack for all components. This can be useful in GitHub actions and
  in [OPA validation policies](/validation/opa) to check whether an import is allowed in the stack or not

- `deps_all` shows all component stack dependencies (imports and root-level stacks) where any configuration for the component is present.
  This also can be useful in GitHub Actions and [OPA validation policies](/validation/opa) to check whether a user or a team
  is allowed to import a particular config file for the component in the stack

- `deps` shows all the component stack dependencies where the **FINAL** values from all the component sections are defined
  (after the deep-merging and processing all the inheritance chains and all the base components). This is useful in CI/CD systems (e.g. Spacelift)
  to detect only the affected files that the component depends on. `deps` is usually a much smaller list than `deps_all` and can
  differ from it in the following ways:

  - An Atmos component can inherit configurations from many base components, see [Component Inheritance](/howto/inheritance), and
    import those base component configurations

  - The component can override all the default variables from the base components, and the final values are not dependent on the base component
    configs anymore. For example, `derived-component-3` import the base component `base-component-4`, inherits from it, and overrides all
    the variables:

  ```yaml
  # Import the base component config
  import:
    - catalog/terraform/base-component-4

  components:
    terraform:
      derived-component-3:
        metadata:
          component: "test/test-component"  # Point to the Terraform/OpenTofu component
          inherits:
            # Inherit all the values from the base component
            - base-component-4
        vars:
          # Override all the variables from the base component
  ```

  - Atmos detects that and does not include the base component `base-component-4` config file into the `deps` output since the `derived-component-3`
    does not directly depend on `base-component-4` (all values are coming from the `derived-component-3`). This will help, for example,
    prevent unrelated Spacelift stack triggering

  - In the above case, the `deps_all` output will include both `derived-component-3` and `base-component-4`, but the `deps` output will not include
    `base-component-4`

## Command example

```shell
atlantis_project: tenant1-ue2-dev-test-test-component-override-3
atmos_cli_config:
  base_path: ./tests/fixtures/scenarios/complete
  components:
    terraform:
      base_path: components/terraform
      apply_auto_approve: false
      deploy_run_init: true
      init_run_reconfigure: true
      auto_generate_backend_file: false
  stacks:
    base_path: stacks
    included_paths:
      - orgs/**/*
    excluded_paths:
      - '**/_defaults.yaml'
    name_pattern: '{tenant}-{environment}-{stage}'
  workflows:
    base_path: stacks/workflows
atmos_component: test/test-component-override-3
atmos_stack: tenant1-ue2-dev
atmos_stack_file: orgs/cp/tenant1/dev/us-east-2
backend:
  bucket: cp-ue2-root-tfstate
  dynamodb_table: cp-ue2-root-tfstate-lock
  key: terraform.tfstate
  region: us-east-2
  workspace_key_prefix: test-test-component
backend_type: s3
command: terraform
component: test/test-component
component_info:
  component_path: tests/fixtures/scenarios/complete/components/terraform/test/test-component
  component_type: terraform
  terraform_config:
    path: tests/fixtures/scenarios/complete/components/terraform/test/test-component
    variables:
      enabled:
        name: enabled
        type: bool
        description: Set to false to prevent the module from creating any resources
        default: null
        required: false
        sensitive: false
        pos:
          filename: tests/fixtures/scenarios/complete/components/terraform/test/test-component/context.tf
          line: 97
      name:
        name: name
        type: string
        description: |
          ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
          This is the only ID element not also included as a `tag`.
          The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input.
        default: null
        required: false
        sensitive: false
        pos:
          filename: tests/fixtures/scenarios/complete/components/terraform/test/test-component/context.tf
          line: 127
      service_1_name:
        name: service_1_name
        type: string
        description: Service 1 name
        default: null
        required: true
        sensitive: false
        pos:
          filename: tests/fixtures/scenarios/complete/components/terraform/test/test-component/variables.tf
          line: 6
    outputs:
      service_1_id:
        name: service_1_id
        description: Service 1 ID
        sensitive: false
        pos:
          filename: tests/fixtures/scenarios/complete/components/terraform/test/test-component/outputs.tf
          line: 1
      service_2_id:
        name: service_2_id
        description: Service 2 ID
        sensitive: false
        pos:
          filename: tests/fixtures/scenarios/complete/components/terraform/test/test-component/outputs.tf
          line: 6
    modulecalls:
      service_1_label:
        name: service_1_label
        source: cloudposse/label/null
        version: 0.25.0
        pos:
          filename: tests/fixtures/scenarios/complete/components/terraform/test/test-component/main.tf
          line: 1
    diagnostics: []
deps:
  - catalog/terraform/mixins/test-2
  - catalog/terraform/services/service-1-override-2
  - catalog/terraform/services/service-2-override-2
  - catalog/terraform/spacelift-and-backend-override-1
  - catalog/terraform/test-component
  - catalog/terraform/test-component-override-3
  - mixins/region/us-east-2
  - mixins/stage/dev
  - orgs/cp/_defaults
  - orgs/cp/tenant1/_defaults
  - orgs/cp/tenant1/dev/us-east-2
deps_all:
  - catalog/terraform/mixins/test-1
  - catalog/terraform/mixins/test-2
  - catalog/terraform/services/service-1
  - catalog/terraform/services/service-1-override
  - catalog/terraform/services/service-1-override-2
  - catalog/terraform/services/service-2
  - catalog/terraform/services/service-2-override
  - catalog/terraform/services/service-2-override-2
  - catalog/terraform/spacelift-and-backend-override-1
  - catalog/terraform/tenant1-ue2-dev
  - catalog/terraform/test-component
  - catalog/terraform/test-component-override
  - catalog/terraform/test-component-override-2
  - catalog/terraform/test-component-override-3
  - mixins/region/us-east-2
  - mixins/stage/dev
  - orgs/cp/_defaults
  - orgs/cp/tenant1/_defaults
  - orgs/cp/tenant1/dev/us-east-2
env:
  TEST_ENV_VAR1: val1-override-3
  TEST_ENV_VAR2: val2-override-3
  TEST_ENV_VAR3: val3-override-3
  TEST_ENV_VAR4: null
imports:
  - catalog/terraform/mixins/test-1
  - catalog/terraform/mixins/test-2
  - catalog/terraform/services/service-1
  - catalog/terraform/services/service-1-override
  - catalog/terraform/services/service-1-override-2
  - catalog/terraform/services/service-2
  - catalog/terraform/services/service-2-override
  - catalog/terraform/services/service-2-override-2
  - catalog/terraform/services/top-level-service-1
  - catalog/terraform/services/top-level-service-2
  - catalog/terraform/spacelift-and-backend-override-1
  - catalog/terraform/tenant1-ue2-dev
  - catalog/terraform/test-component
  - catalog/terraform/test-component-override
  - catalog/terraform/test-component-override-2
  - catalog/terraform/test-component-override-3
  - catalog/terraform/top-level-component1
  - catalog/terraform/vpc
  - mixins/region/us-east-2
  - mixins/stage/dev
  - orgs/cp/_defaults
  - orgs/cp/tenant1/_defaults
  - orgs/cp/tenant1/dev/_defaults
inheritance:
  - mixin/test-2
  - mixin/test-1
  - test/test-component-override-2
  - test/test-component-override
  - test/test-component
metadata:
  component: test/test-component
  inherits:
    - test/test-component-override
    - test/test-component-override-2
    - mixin/test-1
    - mixin/test-2
  terraform_workspace: test-component-override-3-workspace
remote_state_backend:
  bucket: cp-ue2-root-tfstate
  dynamodb_table: cp-ue2-root-tfstate-lock
  region: us-east-2
  workspace_key_prefix: test-test-component
remote_state_backend_type: s3
settings:
  config:
    is_prod: false
  spacelift:
    protect_from_deletion: true
    stack_destructor_enabled: false
    stack_name_pattern: '{tenant}-{environment}-{stage}-new-component'
    workspace_enabled: false
sources:
  backend:
    bucket:
      final_value: cp-ue2-root-tfstate
      name: bucket
      stack_dependencies:
        - stack_file: catalog/terraform/spacelift-and-backend-override-1
          stack_file_section: terraform.backend.s3
          dependency_type: import
          variable_value: cp-ue2-root-tfstate
        - stack_file: orgs/cp/_defaults
          stack_file_section: terraform.backend.s3
          dependency_type: import
          variable_value: cp-ue2-root-tfstate
    dynamodb_table:
      final_value: cp-ue2-root-tfstate-lock
      name: dynamodb_table
      stack_dependencies:
        - stack_file: catalog/terraform/spacelift-and-backend-override-1
          stack_file_section: terraform.backend.s3
          dependency_type: import
          variable_value: cp-ue2-root-tfstate-lock
        - stack_file: orgs/cp/_defaults
          stack_file_section: terraform.backend.s3
          dependency_type: import
          variable_value: cp-ue2-root-tfstate-lock
  env:
    TEST_ENV_VAR1:
      final_value: val1-override-3
      name: TEST_ENV_VAR1
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-3
          stack_file_section: components.terraform.env
          variable_value: val1-override-3
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-2
          stack_file_section: components.terraform.env
          variable_value: val1-override-2
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override
          stack_file_section: components.terraform.env
          variable_value: val1-override
        - dependency_type: import
          stack_file: catalog/terraform/test-component
          stack_file_section: components.terraform.env
          variable_value: val1
  settings:
    spacelift:
      final_value:
        protect_from_deletion: true
        stack_destructor_enabled: false
        stack_name_pattern: '{tenant}-{environment}-{stage}-new-component'
        workspace_enabled: false
      name: spacelift
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-3
          stack_file_section: components.terraform.settings
          variable_value:
            workspace_enabled: false
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-2
          stack_file_section: components.terraform.settings
          variable_value:
            stack_name_pattern: '{tenant}-{environment}-{stage}-new-component'
            workspace_enabled: true
        - dependency_type: import
          stack_file: catalog/terraform/test-component
          stack_file_section: components.terraform.settings
          variable_value:
            workspace_enabled: true
        - dependency_type: import
          stack_file: catalog/terraform/spacelift-and-backend-override-1
          stack_file_section: settings
          variable_value:
            protect_from_deletion: true
            stack_destructor_enabled: false
            workspace_enabled: true
  vars:
    enabled:
      final_value: true
      name: enabled
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/test-component
          stack_file_section: components.terraform.vars
          variable_value: true
        - dependency_type: inline
          stack_file: orgs/cp/tenant1/dev/us-east-2
          stack_file_section: terraform.vars
          variable_value: false
    # Other variables are omitted for clarity
vars:
  enabled: true
  environment: ue2
  namespace: cp
  region: us-east-2
  service_1_map:
    a: 1
    b: 6
    c: 7
    d: 8
  service_1_name: mixin-2
  stage: dev
  tenant: tenant1
workspace: test-component-override-3-workspace
```

## Sources of Component Variables

The `sources.vars` section of the output shows the final deep-merged component's variables and their inheritance chain.

Each variable descriptor has the following schema:

- `final_value` - the final value of the variable after Atmos processes and deep-merges all values from all stack manifests
- `name` - the variable name
- `stack_dependencies` - the variable's inheritance chain (stack manifests where the values for the variable were provided). It has the following
  schema:

  - `stack_file` - the stack manifest where the value for the variable was provided
  - `stack_file_section` - the section of the stack manifest where the value for the variable was provided
  - `variable_value` - the variable's value
  - `dependency_type` - how the variable was defined (`inline` or `import`). `inline` means the variable was defined in one of the sections
    in the stack manifest. `import` means the stack manifest where the variable is defined was imported into the parent Atmos stack

For example:

```shell
sources:
  vars:
    enabled:
      final_value: true
      name: enabled
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/test-component
          stack_file_section: components.terraform.vars
          variable_value: true
        - dependency_type: inline
          stack_file: orgs/cp/tenant1/dev/us-east-2
          stack_file_section: terraform.vars
          variable_value: false
        - dependency_type: inline
          stack_file: orgs/cp/tenant1/dev/us-east-2
          stack_file_section: vars
          variable_value: true
    environment:
      final_value: ue2
      name: environment
      stack_dependencies:
        - dependency_type: import
          stack_file: mixins/region/us-east-2
          stack_file_section: vars
          variable_value: ue2
    namespace:
      final_value: cp
      name: namespace
      stack_dependencies:
        - dependency_type: import
          stack_file: orgs/cp/_defaults
          stack_file_section: vars
          variable_value: cp
    region:
      final_value: us-east-2
      name: region
      stack_dependencies:
        - dependency_type: import
          stack_file: mixins/region/us-east-2
          stack_file_section: vars
          variable_value: us-east-2
    service_1_map:
      final_value:
        a: 1
        b: 6
        c: 7
        d: 8
      name: service_1_map
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/services/service-1-override-2
          stack_file_section: components.terraform.vars
          variable_value:
            b: 6
            c: 7
            d: 8
        - dependency_type: import
          stack_file: catalog/terraform/services/service-1-override
          stack_file_section: components.terraform.vars
          variable_value:
            a: 1
            b: 2
            c: 3
    service_1_name:
      final_value: mixin-2
      name: service_1_name
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/mixins/test-2
          stack_file_section: components.terraform.vars
          variable_value: mixin-2
        - dependency_type: import
          stack_file: catalog/terraform/mixins/test-1
          stack_file_section: components.terraform.vars
          variable_value: mixin-1
        - dependency_type: import
          stack_file: catalog/terraform/services/service-1-override-2
          stack_file_section: components.terraform.vars
          variable_value: service-1-override-2
        - dependency_type: import
          stack_file: catalog/terraform/tenant1-ue2-dev
          stack_file_section: components.terraform.vars
          variable_value: service-1-override-2
        - dependency_type: import
          stack_file: catalog/terraform/services/service-1-override
          stack_file_section: components.terraform.vars
          variable_value: service-1-override
        - dependency_type: import
          stack_file: catalog/terraform/services/service-1
          stack_file_section: components.terraform.vars
          variable_value: service-1
    stage:
      final_value: dev
      name: stage
      stack_dependencies:
        - dependency_type: import
          stack_file: mixins/stage/dev
          stack_file_section: vars
          variable_value: dev
```

:::info

The `stack_dependencies` inheritance chain shows the variable sources in the reverse order the sources were processed.
The first item in the list was processed the last and its `variable_value` overrode all the previous values of the variable.

:::

For example, the component's `enabled` variable has the following inheritance chain:

```yaml
sources:
  vars:
    enabled:
      final_value: true
      name: enabled
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/test-component
          stack_file_section: components.terraform.vars
          variable_value: true
        - dependency_type: inline
          stack_file: orgs/cp/tenant1/dev/us-east-2
          stack_file_section: terraform.vars
          variable_value: false
        - dependency_type: inline
          stack_file: orgs/cp/tenant1/dev/us-east-2
          stack_file_section: vars
          variable_value: true
```

Which we can interpret as follows (reading from the last to the first item in the `stack_dependencies` list):

- In the `orgs/cp/tenant1/dev/us-east-2` stack manifest (the last item in the list), the value for `enabled` was set to `true` in the global `vars`
  section (inline)

- Then in the same `orgs/cp/tenant1/dev/us-east-2` stack manifest, the value for `enabled` was set to `false` in the `terraform.vars`
  section (inline). This value overrode the value set in the global `vars` section

- Finally, in the `catalog/terraform/test-component` stack manifest (which was imported into the parent Atmos stack
  via [`import`](/stacks/imports)), the value for `enabled` was set to `true` in the `components.terraform.vars` section of
  the `test/test-component-override-3` Atmos component. This value overrode all the previous values arriving at the `final_value: true` for the
  variable. This final value is then set for the `enabled` variable of the Terraform component `test/test-component` when Atmos
  executes `atmos terraform apply test/test-component-override-3 -s ` command

## Sources of Component ENV Variables

The `sources.env` section of the output shows the final deep-merged component's environment variables and their inheritance chain.

Each variable descriptor has the following schema:

- `final_value` - the final value of the variable after Atmos processes and deep-merges all values from all stack manifests
- `name` - the variable name
- `stack_dependencies` - the variable's inheritance chain (stack manifests where the values for the variable were provided). It has the following
  schema:

  - `stack_file` - the stack manifest where the value for the variable was provided
  - `stack_file_section` - the section of the stack manifest where the value for the variable was provided
  - `variable_value` - the variable's value
  - `dependency_type` - how the variable was defined (`inline` or `import`). `inline` means the variable was defined in one of the sections
    in the stack manifest. `import` means the stack manifest where the variable is defined was imported into the parent Atmos stack

For example:

```shell
sources:
  env:
    TEST_ENV_VAR1:
      final_value: val1-override-3
      name: TEST_ENV_VAR1
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-3
          stack_file_section: components.terraform.env
          variable_value: val1-override-3
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-2
          stack_file_section: components.terraform.env
          variable_value: val1-override-2
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override
          stack_file_section: components.terraform.env
          variable_value: val1-override
        - dependency_type: import
          stack_file: catalog/terraform/test-component
          stack_file_section: components.terraform.env
          variable_value: val1
    TEST_ENV_VAR2:
      final_value: val2-override-3
      name: TEST_ENV_VAR2
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-3
          stack_file_section: components.terraform.env
          variable_value: val2-override-3
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-2
          stack_file_section: components.terraform.env
          variable_value: val2-override-2
        - dependency_type: import
          stack_file: catalog/terraform/test-component
          stack_file_section: components.terraform.env
          variable_value: val2
    TEST_ENV_VAR3:
      final_value: val3-override-3
      name: TEST_ENV_VAR3
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-3
          stack_file_section: components.terraform.env
          variable_value: val3-override-3
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override
          stack_file_section: components.terraform.env
          variable_value: val3-override
        - dependency_type: import
          stack_file: catalog/terraform/test-component
          stack_file_section: components.terraform.env
          variable_value: val3
```

:::info

The `stack_dependencies` inheritance chain shows the ENV variable sources in the reverse order the sources were processed.
The first item in the list was processed the last and its `variable_value` overrode all the previous values of the variable.

:::

For example, the component's `TEST_ENV_VAR1` ENV variable has the following inheritance chain:

```yaml
sources:
  env:
    TEST_ENV_VAR1:
      final_value: val1-override-3
      name: TEST_ENV_VAR1
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-3
          stack_file_section: components.terraform.env
          variable_value: val1-override-3
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-2
          stack_file_section: components.terraform.env
          variable_value: val1-override-2
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override
          stack_file_section: components.terraform.env
          variable_value: val1-override
        - dependency_type: import
          stack_file: catalog/terraform/test-component
          stack_file_section: components.terraform.env
          variable_value: val1
```

Which we can interpret as follows (reading from the last to the first item in the `stack_dependencies` list):

- In the `catalog/terraform/test-component` stack manifest (the last item in the list), the value for the `TEST_ENV_VAR1` ENV variable was set
  to `val1` in the `components.terraform.env` section

- Then the value was set to `val1-override` in the `catalog/terraform/test-component-override` stack manifest. This value overrides the value set
  in the `catalog/terraform/test-component` stack manifest

- Then the value was set to `val1-override-2` in the `catalog/terraform/test-component-override-2` stack manifest. This value overrides the values
  set in the `catalog/terraform/test-component` and `catalog/terraform/test-component-override` stack manifests

- Finally, in the `catalog/terraform/test-component-override-3` stack manifest (which was imported into the parent Atmos stack
  via [`import`](/stacks/imports)), the value was set to `val1-override-3` in the `components.terraform.env` section of
  the `test/test-component-override-3` Atmos component. This value overrode all the previous values arriving at the `final_value: val1-override-3` for
  the ENV variable

## Sources of Component Settings

The `sources.settings` section of the output shows the final deep-merged component's settings and their inheritance chain.

Each setting descriptor has the following schema:

- `final_value` - the final value of the setting after Atmos processes and deep-merges all values from all stack manifests
- `name` - the setting name
- `stack_dependencies` - the setting's inheritance chain (stack manifests where the values for the variable were provided). It has the following
  schema:

  - `stack_file` - the stack manifest where the value for the setting was provided
  - `stack_file_section` - the section of the stack manifest where the value for the setting was provided
  - `variable_value` - the setting's value
  - `dependency_type` - how the setting was defined (`inline` or `import`). `inline` means the setting was defined in one of the sections
    in the stack manifest. `import` means the stack config file where the setting is defined was imported into the parent Atmos stack

For example:

```shell
sources:
  settings:
    spacelift:
      final_value:
        protect_from_deletion: true
        stack_destructor_enabled: false
        stack_name_pattern: '{tenant}-{environment}-{stage}-new-component'
        workspace_enabled: false
      name: spacelift
      stack_dependencies:
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-3
          stack_file_section: components.terraform.settings
          variable_value:
            workspace_enabled: false
        - dependency_type: import
          stack_file: catalog/terraform/test-component-override-2
          stack_file_section: components.terraform.settings
          variable_value:
            stack_name_pattern: '{tenant}-{environment}-{stage}-new-component'
            workspace_enabled: true
        - dependency_type: import
          stack_file: catalog/terraform/test-component
          stack_file_section: components.terraform.settings
          variable_value:
            workspace_enabled: true
        - dependency_type: import
          stack_file: catalog/terraform/spacelift-and-backend-override-1
          stack_file_section: settings
          variable_value:
            protect_from_deletion: true
            stack_destructor_enabled: false
            workspace_enabled: true
```

:::info

The `stack_dependencies` inheritance chain shows the sources of the setting in the reverse order the sources were processed.
The first item in the list was processed the last and its `variable_value` overrode all the previous values of the setting.

:::

---

## atmos describe config

Use this command to show the final (deep-merged) [CLI configuration](/cli/configuration) of all `atmos.yaml` file(s).

## Usage

Execute the `describe config` command like this:

```shell
atmos describe config [options]
```

This command shows the final (deep-merged) [CLI configuration](/cli/configuration) (from `atmos.yaml` file(s)).

:::tip
Run `atmos describe config --help` to see all the available options
:::

## Examples

```shell
atmos describe config
atmos describe config -f yaml
atmos describe config --format yaml
atmos describe config -f json
atmos describe config --query 
```

## Flags

- **`--format` / `-f` (optional)**
  Output format: 
  `json`
   or 
  `yaml`
   (
  `json`
   is default).
- **`--query` / `-q` (optional)**
  Query the results of the command using 
  `yq`
   expressions.
  `atmos describe config --query `
  .
  For more details, refer to https://mikefarah.gitbook.io/yq.

---

## atmos describe dependents

Use this command to show a list of Atmos components in Atmos stacks that depend on the provided Atmos component.

## Description

Declare component relationships with [`dependencies.components`](/stacks/dependencies/components). The command reads that graph to list the components that depend on the requested component.

Use `name` for the dependency instance, `stack` when it is provisioned in another stack, and `kind` for a cross-type dependency. Use the sibling `dependencies.files` and `dependencies.folders` lists for external paths.

```yaml title="stacks/catalog/terraform/top-level-component1.yaml"
components:
  terraform:
    top-level-component1:
      dependencies:
        components:
          # Same stack
          - name: test/test-component-override
          # Explicit cross-stack dependency
          - name: test/test-component
            stack: tenant1-ue2-dev
          - name: my-component
            stack: tenant1-ue2-staging
        files:
          - tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf
        folders:
          - tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server
      vars:
        enabled: true
```

In this configuration, `top-level-component1` depends on components in the current and named stacks, and is affected when either external path changes.

```yaml title="stacks/catalog/terraform/top-level-component2.yaml"
components:
  terraform:
    top-level-component2:
      metadata:
        component: "top-level-component1"
      dependencies:
        components:
          - name: test/test-component
          - name: test/test2/test-component-2
      vars:
        enabled: true
```

Having the `top-level-component` and `top-level-component2` components configured as shown above, we can now execute the following Atmos command
to show all the components that depend on the `test/test-component` component in the `tenant1-ue2-dev` stack:

```shell
[
    {
        "component": "top-level-component1",
        "component_type": "terraform",
        "component_path": "tests/fixtures/scenarios/complete/components/terraform/top-level-component1",
        "namespace": "cp",
        "tenant": "tenant1",
        "environment": "ue2",
        "stage": "dev",
        "stack": "tenant1-ue2-dev",
        "stack_slug": "tenant1-ue2-dev-top-level-component1",
        "spacelift_stack": "tenant1-ue2-dev-top-level-component1",
        "atlantis_project": "tenant1-ue2-dev-top-level-component1"
    }
]
```

Similarly, the following Atmos command shows all the components that depend on the `test/test-component` component in
the `tenant1-ue2-test-1` stack:

```shell
[
    {
        "component": "top-level-component1",
        "component_type": "terraform",
        "component_path": "tests/fixtures/scenarios/complete/components/terraform/top-level-component1",
        "namespace": "cp",
        "tenant": "tenant1",
        "environment": "ue2",
        "stage": "test-1",
        "stack": "tenant1-ue2-test-1",
        "stack_slug": "tenant1-ue2-dev-top-level-component1",
        "spacelift_stack": "tenant1-ue2-test-1-top-level-component1",
        "atlantis_project": "tenant1-ue2-test-1-top-level-component1"
    },
    {
        "component": "top-level-component2",
        "component_type": "terraform",
        "component_path": "tests/fixtures/scenarios/complete/components/terraform/top-level-component1",
        "namespace": "cp",
        "tenant": "tenant1",
        "environment": "ue2",
        "stage": "test-1",
        "stack": "tenant1-ue2-test-1",
        "stack_slug": "tenant1-ue2-test-1-top-level-component2",
        "atlantis_project": "tenant1-ue2-test-1-top-level-component2"
    }
]
```

After the `test/test-component` has been provisioned, you can use the outputs to perform the following actions:

- Provision the dependent components by executing the Atmos commands `atmos terraform apply top-level-component1 -s tenant1-ue2-test-1` and
  `atmos terraform apply top-level-component2 -s tenant1-ue2-test-1` (on the command line or from a GitHub Action)

- Trigger the dependent Atlantis project

## Usage

```shell
atmos describe dependents [options]
```

:::info YAML Functions and Authentication
By default, `atmos describe dependents` executes YAML template functions (e.g., `!terraform.state`, `!terraform.output`) and Go templates during stack processing. When these functions access remote resources requiring authentication, use the `--identity` flag to authenticate before execution. You can disable function/template processing with `--process-functions=false` or `--process-templates=false` flags.
:::

:::tip
Run `atmos describe dependents --help` to see all the available options
:::

## Examples

```shell
atmos describe dependents test/test-component -s tenant1-ue2-test-1
atmos describe dependents test/test-component -s tenant1-ue2-dev --format yaml
atmos describe dependents test/test-component -s tenant1-ue2-test-1 -f yaml
atmos describe dependents test/test-component -s tenant1-ue2-test-1 --file dependents.json
atmos describe dependents test/test-component -s tenant1-ue2-test-1 --format yaml --file dependents.yaml
atmos describe dependents test/test-component -s tenant1-ue2-test-1 --query 
# Authenticate before describing (when YAML functions require credentials)
atmos describe dependents test/test-component -s tenant1-ue2-test-1 --identity my-aws-identity
atmos describe dependents test/test-component -s tenant1-ue2-test-1 --identity # Interactive selection
# Disable authentication (use AWS SDK defaults)
atmos describe dependents test/test-component -s tenant1-ue2-test-1 --identity=false
atmos describe dependents test/test-component -s tenant1-ue2-test-1 -i my-aws-identity
```

## Arguments

- **`component` (required)**

  Atmos component.

## Flags

- **`--stack` (alias `-s`)(required)**

  Atmos stack.
- **`--format` (alias `-f`)(optional)**

  Output format: `json` or `yaml` (`json` is default).
- **`--file` (optional)**

  If specified, write the result to the file.
- **`--query` (alias `-q`)(optional)**

  Query the results of the command using YQ expressions.

  `atmos describe dependents  -s  --query `.

  For more details, refer to https://mikefarah.gitbook.io/yq.
- **`--process-templates` (optional)**

  Enable/disable processing of `Go` templates in Atmos stacks manifests when executing the command.
  If the flag is not provided, it's set to `true` by default.

  `atmos describe dependents  -s  --process-templates=false`
- **`--process-functions` (optional)**

  Enable/disable processing of Atmos YAML functions in Atmos stacks manifests when executing the command.
  If the flag is not provided, it's set to `true` by default.

  `atmos describe dependents  -s  --process-functions=false`
- **`--skip` (optional)**

  Skip processing a specific Atmos YAML function in Atmos stacks manifests when executing the command.
  To specify more than one function, use multiple `--skip` flags, or separate the functions with a comma:

  `atmos describe dependents  -s  --skip=terraform.output --skip=include`

  `atmos describe dependents  -s  --skip=terraform.output,include`
- **`--identity` / `-i` (optional)**

  Authenticate with a specific identity before describing dependents.
  This is required when YAML template functions (e.g., `!terraform.state`, `!terraform.output`)
  need to access remote resources requiring authentication.
  Use without a value for interactive identity selection:

  `atmos describe dependents  -s  --identity` (interactive)

  `atmos describe dependents  -s  --identity my-aws-identity` (specific identity)

  For more details, refer to [Authentication](/cli/commands/auth/usage).

## Output

The command outputs a list of objects (in JSON or YAML format).

Each object has the following schema:

```json
{
  "component": "....",
  "component_type": "....",
  "component_path": "....",
  "namespace": "....",
  "tenant": "....",
  "environment": "....",
  "stage": "....",
  "stack": "....",
  "stack_slug": "",
  "spacelift_stack": ".....",
  "atlantis_project": "....."
}
```

where:

- `component` - the dependent Atmos component

- `component_type` - the type of the dependent component (`terraform` or `helmfile`)

- `component_path` - the filesystem path to the `terraform` or `helmfile` component

- `namespace` - the `namespace` where the dependent Atmos component is provisioned

- `tenant` - the `tenant` where the dependent Atmos component is provisioned

- `environment` - the `environment` where the dependent Atmos component is provisioned

- `stage` - the `stage` where the dependent Atmos component is provisioned

- `stack` - the Atmos stack where the dependent Atmos component is provisioned

- `stack_slug` - the Atmos stack slug (concatenation of the Atmos stack and Atmos component)

- `spacelift_stack` - the dependent Spacelift stack. It will be included only if the Spacelift workspace is enabled for the dependent Atmos component
  in the Atmos stack in the `settings.spacelift.workspace_enabled` section (either directly in the component's `settings.spacelift.workspace_enabled`
  section or via inheritance)

- `atlantis_project` - the dependent Atlantis project name. It will be included only if the Atlantis integration is configured in
  the `settings.atlantis` section in the stack manifest. Refer to [Atlantis Integration](/cli/configuration/integrations/atlantis) for more details.

:::note

Abstract Atmos components (`metadata.type` is set to `abstract`) are not included in the output since they serve as blueprints for other
Atmos components and are not meant to be provisioned.

Disabled components (`metadata.enabled: false`) are also excluded from dependents.
:::

## Output Example

```shell
[
{
    "component": "top-level-component2",
    "component_type": "terraform",
    "component_path": "tests/fixtures/scenarios/complete/components/terraform/top-level-component1",
    "namespace": "cp",
    "tenant": "tenant1",
    "environment": "ue2",
    "stage": "test-1",
    "stack": "tenant1-ue2-test-1",
    "stack_slug": "tenant1-ue2-dev-top-level-component2",
    "atlantis_project": "tenant1-ue2-test-1-top-level-component2"
},
{
    "component": "top-level-component1",
    "component_type": "terraform",
    "component_path": "tests/fixtures/scenarios/complete/components/terraform/top-level-component1",
    "namespace": "cp",
    "tenant": "tenant1",
    "environment": "ue2",
    "stage": "dev",
    "stack": "tenant1-ue2-dev",
    "stack_slug": "tenant1-ue2-test-1-top-level-component1",
    "spacelift_stack": "tenant1-ue2-dev-top-level-component1",
    "atlantis_project": "tenant1-ue2-dev-top-level-component1"
}
]
```

---

## atmos describe edition

Use this command to show whether the project is pinned to an edition (a date anchor for defaults), where the pin came from, the resolved anchor date, and every default the pin keeps at its pre-change value.

> ⚠️ Experimental

**Pin Your Project to an Edition**

Learn how to pin your project's defaults to a date anchor with the top-level `edition` setting in your atmos.yaml.

Configuration Reference[Read more](/cli/configuration/edition)

## Usage

```shell
atmos describe edition [flags]
```

## Description

The `atmos describe edition` command reports the effect of the active edition pin:

- **pinned** — whether an edition is set at all
- **edition** — the raw pin as you wrote it (e.g. `2025-09`)
- **resolved\_date** — the fully resolved anchor date (partial dates round to the end of the period they name)
- **granularity** — how much of the date the pin specified: `year`, `month`, or `day`
- **source** — where the pin came from: `flag` (`--edition`), `env` (`ATMOS_EDITION`), or `config` (`atmos.yaml`)
- **overrides** — every default the pin rolls back, with the pinned (effective) value, the latest default you would get by unpinning, and the journal entries behind the change

Without a pin, it reports `pinned: false` with no overrides — the project follows the latest defaults.

## Flags

- **`--format, -f string`**
  Output format: 
  `yaml`
   or 
  `json`
   (default: 
  `yaml`
  )

## Examples

Show the active pin and its effect:

```shell
atmos describe edition
```

Output as JSON:

```shell
atmos describe edition --format=json
```

Preview the effect of a pin without changing your configuration, using the `--edition` global flag:

```shell
atmos --edition=2026-01 describe edition
```

## Example Output

```shell
pinned: true
edition: 2026-06
resolved_date: "2026-06-30"
granularity: month
source: config
overrides:
    - key: describe.component.filter
      kind: value
      from_value: full
      to_value: schema
      entries:
        - date: "2026-07-17"
          key: describe.component.filter
          kind: value
          old: full
          new: schema
          description: Component descriptions show only stack-manifest sections; set the filter to full for computed internals.
          ref: https://atmos.tools/changelog/config-editions
    - key: describe.error_mode
      kind: value
      from_value: strict
      to_value: warn
      entries:
        - date: "2026-07-13"
          key: describe.error_mode
          kind: value
          old: strict
          new: warn
          description: Describe commands substitute (computed) for unresolved YAML function values and continue instead of aborting.
          ref: https://atmos.tools/changelog/list-describe-graceful-degradation
    - key: describe.provenance
      kind: value
      from_value: false
      to_value: true
      entries:
        - date: "2026-07-16"
          key: describe.provenance
          kind: value
          old: false
          new: true
          description: Component descriptions include provenance annotations (which stack file set each value) by default.
          ref: https://atmos.tools/changelog/config-editions
    - key: list.error_mode
      kind: value
      from_value: strict
      to_value: warn
      entries:
        - date: "2026-07-13"
          key: list.error_mode
          kind: value
          old: strict
          new: warn
          description: List commands substitute (computed) for unresolved YAML function values and continue instead of aborting.
          ref: https://atmos.tools/changelog/list-describe-graceful-degradation
    - key: settings.terminal.help.filter
      kind: value
      from_value: false
      to_value: true
      entries:
        - date: "2026-07-06"
          key: settings.terminal.help.filter
          kind: value
          old: false
          new: true
          description: Bare --help shows a focused view without the GLOBAL FLAGS section; --help=all prints the full output.
          ref: https://github.com/cloudposse/atmos/pull/2696
```

In each override, `from_value` is the default your pinned project actually uses, and `to_value` is the latest default the project would get by unpinning.

:::tip
Use `atmos list editions` to browse the full journal of default changes, or to diff two editions with `--from` and `--to`.
:::

## See Also

- [`edition` configuration](/cli/configuration/edition) — Pin your project's defaults to a date anchor
- [`atmos list editions`](/cli/commands/list/editions) — Browse the journal of default changes, or diff two editions

---

## atmos describe locals

Use this command to display the [locals](/stacks/locals) defined in Atmos stack manifests.
This is useful for debugging and understanding how locals are configured in a specific stack.

:::info Stack Flag Required
The `--stack` flag is **required**. Atmos resolves it to a stack manifest file and returns **only the locals defined in that file** (not inherited from imports). The `--stack` flag accepts either:

- A **logical stack name** derived from your `atmos.yaml` naming pattern (e.g., `prod-us-east-1`)
- A **stack manifest file path** (e.g., `deploy/prod`)

Both resolve to the same underlying file. Locals are file-scoped, so the output reflects what's defined in that specific manifest.
:::

## Usage

Execute the `describe locals` command like this:

```shell
atmos describe locals [component] -s  [options]
```

The `--stack` flag is required. When called with just `--stack`, it shows the locals defined in that stack manifest file.
When a component is also specified, it shows the merged locals that would be **available to** that component. This includes:

- **Global locals** from the stack manifest file
- **Section-specific locals** (e.g., `terraform:` locals for a Terraform component)
- **Component-level locals** defined in the component itself (including inherited from base components)

:::tip
Run `atmos describe locals --help` to see all the available options
:::

## Examples

```shell
# Show locals for a specific stack (using file path)
atmos describe locals --stack deploy/dev

# Show locals for a specific stack (using logical stack name derived from atmos.yaml)
atmos describe locals -s prod-us-east-1

# Show locals available to a specific component in a stack
# The component determines which section-specific locals to merge (terraform/helmfile/packer)
atmos describe locals vpc -s prod
atmos describe locals eks --stack prod-us-east-1

# Output as JSON
atmos describe locals -s dev --format json
atmos describe locals vpc -s prod -f json

# Write to file
atmos describe locals -s dev --file locals.yaml

# Query specific values
atmos describe locals -s deploy/dev --query '.locals.namespace'
```

## Arguments

- **`component` (optional)**
  The name of a component. When specified with 
  `--stack`
  , shows the merged locals that would be 
  available to
   that component. Atmos determines the component's type (terraform, helmfile, or packer) and merges: (1) global locals, (2) section-specific locals from the stack manifest, and (3) component-level locals defined in the component itself (including those inherited from base components via 
  metadata.inherits
  ).

## Flags

- **`--stack` / `-s` (required)**
  Specify the stack to show locals for. Accepts two formats: (1) 
  Stack manifest file path
   \- direct path relative to your stacks directory (e.g., 
  deploy/dev
  , 
  prod
  ), or (2) 
  Logical stack name
   \- the derived name based on your 
  atmos.yaml
   naming pattern (e.g., 
  prod-us-east-1
  ). Atmos resolves either format to the underlying stack manifest file and returns only the locals defined in that file.
- **`--format` / `-f` (optional)**
  Output format: 
  `yaml`
   or 
  `json`
   (
  `yaml`
   is default).
- **`--file` (optional)**
  If specified, write the result to the file.
- **`--query` / `-q` (optional)**
  Query the results of the command using 
  `yq`
   expressions.
  `atmos describe locals --query `
  For more details, refer to https://mikefarah.gitbook.io/yq.

## Output

The command outputs locals in **Atmos schema format**, matching the structure of stack manifest files. Each stack contains:

- **`locals`**
  Root-level locals defined at the top of the stack manifest file.
- **`terraform.locals`**
  Locals defined within the 
  `terraform:`
   section (only shown if explicitly defined). Contains only section-specific locals, not merged with global.
- **`helmfile.locals`**
  Locals defined within the 
  `helmfile:`
   section (only shown if explicitly defined). Contains only section-specific locals, not merged with global.
- **`packer.locals`**
  Locals defined within the 
  `packer:`
   section (only shown if explicitly defined). Contains only section-specific locals, not merged with global.

This schema-compliant format makes it easy to compare with your source stack manifests and use the output programmatically.

## Example Output

```shell
locals:
  environment: dev
  namespace: acme
  name_prefix: acme-dev
  full_name: acme-dev-us-east-1
  tags:
    Environment: dev
    Namespace: acme
terraform:
  locals:
    backend_bucket: acme-dev-tfstate
    tf_specific: terraform-only
```

The output follows the same structure as stack manifest files, making it easy to understand which locals are defined where. This format can be directly used as a valid stack manifest file (e.g., `atmos describe locals -s dev --file locals.yaml`).

### Component-Specific Output

When a component is specified with `--stack`, the output shows the merged locals that would be **available to** that component, using Atmos schema format:

```shell
components:
  terraform:
    vpc:
      locals:
        backend_bucket: acme-prod-tfstate
        environment: prod
        full_name: acme-prod-us-east-1
        name_prefix: acme-prod
        namespace: acme
        tf_specific: terraform-only
        vpc_type: production
```

The output shows the merged locals from all sources:

1. Global locals from the stack manifest (`locals:`)
2. Section-specific locals from the stack manifest (`terraform.locals:`)
3. Component-level locals from the component definition (including inherited from base components)

:::tip Component-Level Locals
If the component defines its own `locals:` section (or inherits locals from a base component via `metadata.inherits`), those are included in the output and take precedence over stack-level locals.
:::

## How Locals Work

Locals are file-scoped variables that can reference each other and are resolved before template processing. They provide a way to define computed values that can be used throughout the stack manifest.

### Defining Locals

```yaml
# stacks/deploy/dev.yaml
locals:
  namespace: acme
  environment: dev
  # Locals can reference other locals
  name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}"
  backend_bucket: "{{ .locals.name_prefix }}-tfstate"

components:
  terraform:
    vpc:
      vars:
        # Use locals in component vars
        name: "{{ .locals.name_prefix }}-vpc"
        bucket: "{{ .locals.backend_bucket }}"
```

### Section-Specific Locals

Locals can be defined at section level (terraform, helmfile, packer) to override global locals:

```yaml
locals:
  namespace: global-acme

terraform:
  locals:
    # Overrides global namespace for terraform components
    namespace: terraform-acme
    backend_bucket: "{{ .locals.namespace }}-tfstate"
```

### Component-Level Locals

Components can define their own `locals:` section. Component-level locals are merged with stack-level locals (global + section-specific) and take precedence. Component-level locals also support inheritance from base components via `metadata.inherits`:

```yaml
components:
  terraform:
    # Base component with component-level locals
    vpc/base:
      metadata:
        type: abstract
      locals:
        vpc_type: standard
        cidr_prefix: "10.0"

    # Component inheriting locals from base
    vpc/prod:
      metadata:
        inherits:
          - vpc/base
      locals:
        # Overrides vpc_type from base component
        vpc_type: production
      vars:
        # Uses inherited cidr_prefix from base
        cidr: "{{ .locals.cidr_prefix }}.0.0/16"
```

The full locals resolution order for a component is:

```
Global Locals → Section Locals → Base Component Locals → Component Locals
```

Later values override earlier ones. When you run `atmos describe locals vpc/prod -s dev`, the output shows the final merged result.

:::note
Component-level locals appear in the final component output but are NOT available during `{{ .locals.* }}` template processing in the same stack manifest file. Only file-level locals (global + section) are available during template resolution within the file.
:::

### File-Scoped Behavior

Locals are **file-scoped** and are NOT inherited across imports. Each stack manifest file can only access its own locally defined `locals` section. This prevents unintended side effects from imported files.

This is a key design principle: when you run `atmos describe locals --stack deploy/dev`, you get **only** the locals defined in the `deploy/dev.yaml` file itself, regardless of what files it imports. The `--stack` flag accepts either:

- The file path (`deploy/dev`)
- The logical stack name derived from your naming pattern (e.g., `dev-us-east-1`)

Both resolve to the same file and return the same locals.

```yaml
# mixins/base.yaml
locals:
  mixin_value: "from-mixin"  # NOT available to importing files

# stacks/deploy/dev.yaml
import:
  - mixins/base

locals:
  file_value: "from-file"  # Available in this file

components:
  terraform:
    myapp:
      vars:
        value: "{{ .locals.file_value }}"       # Works: "from-file"
        mixin: "{{ .locals.mixin_value }}"      # Does NOT work: ""
```

:::note
Regular `vars` ARE inherited across imports (normal Atmos behavior). Only `locals` are file-scoped.
:::

## Related Commands

- [`atmos describe component`](/cli/commands/describe/component) - Describe a component's full configuration including resolved locals
- [`atmos describe stacks`](/cli/commands/describe/stacks) - Describe all stacks and their configurations

## Related Documentation

- [File-Scoped Locals](/stacks/locals) - Learn more about defining and using locals in stack manifests

---

## atmos describe stacks

Use this command to show the fully deep-merged configuration for all stacks and the components in the stacks.

## Usage

Execute the `describe stacks` command like this:

```shell
atmos describe stacks [options]
```

This command shows configuration for stacks and components in the stacks.

:::info YAML Functions and Authentication
By default, `atmos describe stacks` executes YAML template functions (e.g., `!terraform.state`, `!terraform.output`) and Go templates during stack processing. When these functions access remote resources requiring authentication, use the `--identity` flag to authenticate before execution. You can disable function/template processing with `--process-functions=false` or `--process-templates=false` flags.
:::

:::tip
Run `atmos describe stacks --help` to see all the available options
:::

## Examples

```shell
atmos describe stacks
atmos describe stacks -s tenant1-ue2-dev
atmos describe stacks --file=stacks.yaml
atmos describe stacks --file=stacks.json --format=json
atmos describe stacks --components=infra/vpc
atmos describe stacks --components=echo-server,infra/vpc
atmos describe stacks --components=echo-server,infra/vpc --sections=none
atmos describe stacks --components=echo-server,infra/vpc --sections=none
atmos describe stacks --components=none --sections=metadata
atmos describe stacks --components=echo-server,infra/vpc --sections=vars,settings,metadata
atmos describe stacks --components=test/test-component-override-3 --sections=vars,settings,component,deps,inheritance --file=stacks.yaml
atmos describe stacks --components=test/test-component-override-3 --sections=vars,settings --format=json --file=stacks.json
atmos describe stacks --components=test/test-component-override-3 --sections=deps,vars -s=tenant2-ue2-staging
atmos describe stacks --process-templates=false
atmos describe stacks --process-functions=false
atmos describe stacks --skip=terraform.output
atmos describe stacks --skip=terraform.output --skip=include
atmos describe stacks --skip=include,eval
atmos describe stacks --query 
# Authenticate before describing (when YAML functions require credentials)
atmos describe stacks --identity my-aws-identity
atmos describe stacks --identity # Interactive selection
# Disable authentication (use AWS SDK defaults)
atmos describe stacks --identity=false
atmos describe stacks -i my-aws-identity -s tenant1-ue2-dev
```

:::tip
Use the `--query` flag (shorthand `-q`) to filter the output.
:::

## Flags

- **`--stack` / `-s` (optional)**
  Filter by a specific stack.
  Supports names of the top-level stack manifests
  (including subfolder paths),
  and Atmos stack names (derived from the context vars).
- **`--file` (optional)**
  If specified, write the result to the file.
- **`--format` (optional)**
  Specify the output format: 
  `yaml`
   or 
  `json`
   (
  `yaml`
   is default).
- **`--components` (optional)**
  Filter by specific Atmos components
  (comma-separated string of component names).
- **`--component-types` (optional)**
  Filter by specific component types: 
  `terraform`
   or 
  `helmfile`
  .
- **`--sections` (optional)**
  Output only the specified component sections.
  Available component sections: 
  `backend`
  , 
  `backend_type`
  , 
  `component`
  , 
  `deps`
  ,
  `env`
  , 
  `inheritance`
  , 
  `metadata`
  , 
  `remote_state_backend`
  ,
  `remote_state_backend_type`
  , 
  `settings`
  , 
  `vars`
  .
- **`--process-templates` (optional)**
  Enable/disable processing of all 
  `Go`
   templates
  in Atmos stacks manifests when executing the command.
  Use the flag to see the stack configurations
  before and after the templates are processed.
  If the flag is not provided, it's set to 
  `true`
   by default.
  `atmos describe stacks --process-templates=false`
  .
- **`--process-functions` (optional)**
  Enable/disable processing of all Atmos YAML functions
  in Atmos stacks manifests when executing the command.
  Use the flag to see the stack configurations
  before and after the functions are processed.
  If the flag is not provided, it's set to 
  `true`
   by default.
  `atmos describe stacks --process-functions=false`
  .
- **`--skip` (optional)**
  Skip processing a specific Atmos YAML function
  in Atmos stacks manifests when executing the command.
  To specify more than one function,
  use multiple 
  `--skip`
   flags, or separate the functions with a comma:
  `atmos describe stacks --skip=terraform.output --skip=include`
  `atmos describe stacks --skip=terraform.output,include`
  .
- **`--query` / `-q` (optional)**
  Query the results of the command using 
  `yq`
   expressions.
  `atmos describe stacks --query `
  .
  For more details, refer to https://mikefarah.gitbook.io/yq.
- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before describing stacks.
  This is required when YAML template functions (e.g., 
  `!terraform.state`
  , 
  `!terraform.output`
  )
  need to access remote resources requiring authentication.
  Use without a value for interactive identity selection:
  `atmos describe stacks --identity`
   (interactive)
  `atmos describe stacks --identity my-aws-identity`
   (specific identity)
  For more details, refer to 
  [Authentication](/cli/commands/auth/usage)
  .

:::note Provenance Tracking
To see provenance information (where configuration values originated with file:line:column details), use `atmos describe component  -s  --provenance` for individual components. The `describe stacks` command does not currently support provenance display.
:::

---

## atmos describe

Use these subcommands to inspect and describe your Atmos configurations, stacks, components, and workflows.

**Configure Stacks**

Learn how to configure stacks, components, and inheritance in your atmos.yaml.

Configuration Reference[Read more](/cli/configuration/stacks)

## Usage

## Subcommands

---

## atmos describe workflows

Use this command to show all configured Atmos workflows.

## Usage

Execute the `describe workflows` command like this:

```shell
atmos describe workflows [options]
```

:::tip
Run `atmos describe workflows --help` to see all the available options
:::

## Examples

```shell
atmos describe workflows
atmos describe workflows --output map
atmos describe workflows -o list
atmos describe workflows -o all
atmos describe workflows -o list --format json
atmos describe workflows -o all -f yaml
atmos describe workflows -f json
atmos describe workflows --query 
```

## Flags

- **`--format` / `-f` (optional)**
  Specify the output format: 
  `yaml`
   or 
  `json`
   (
  `yaml`
   is default).
- **`--output` / `-o` (optional)**
  Specify the output type: 
  `list`
  , 
  `map`
   or 
  `all`
   (
  `list`
   is default).
- **`--query` / `-q` (optional)**
  Query the results of the command using 
  `yq`
   expressions.
  `atmos describe workflows --query `
  .
  For more details, refer to https://mikefarah.gitbook.io/yq.

When the `--output list` flag is passed (default), the output of the command is a list of objects. Each object has the
following schema:

- `file` - the workflow manifest file name
- `workflow` - the name of the workflow defined in the workflow manifest file

For example:

```shell
atmos describe workflows
atmos describe workflows -o list
```

```yaml
- file: compliance.yaml
  workflow: deploy/aws-config/global-collector
- file: compliance.yaml
  workflow: deploy/aws-config/superadmin
- file: compliance.yaml
  workflow: destroy/aws-config/global-collector
- file: compliance.yaml
  workflow: destroy/aws-config/superadmin
- file: datadog.yaml
  workflow: deploy/datadog-integration
- file: helpers.yaml
  workflow: save/docker-config-json
- file: networking.yaml
  workflow: apply-all-components
- file: networking.yaml
  workflow: plan-all-vpc
- file: networking.yaml
  workflow: plan-all-vpc-flow-logs
```

When the `--output map` flag is passed, the output of the command is a map of workflow manifests to the lists of
workflows defined in each manifest.
For example:

```shell
atmos describe workflows -o map
```

```yaml
compliance.yaml:
  - deploy/aws-config/global-collector
  - deploy/aws-config/superadmin
  - destroy/aws-config/global-collector
  - destroy/aws-config/superadmin
datadog.yaml:
  - deploy/datadog-integration
helpers.yaml:
  - save/docker-config-json
networking.yaml:
  - apply-all-components
  - plan-all-vpc
  - plan-all-vpc-flow-logs
```

When the `--output all` flag is passed, the output of the command is a map of workflow manifests to the maps of all
workflow definitions. For example:

```shell
atmos describe workflows -o all
```

```yaml
networking.yaml:
  name: Networking & Logging
  description: Atmos workflows for managing VPCs and VPC Flow Logs
  workflows:
    apply-all-components:
      description: |
        Run 'terraform apply' on all components in all stacks
      steps:
        - command: terraform apply vpc-flow-logs-bucket -s plat-ue2-dev -auto-approve
        - command: terraform apply vpc -s plat-ue2-dev -auto-approve
        - command: terraform apply vpc-flow-logs-bucket -s plat-uw2-dev -auto-approve
        - command: terraform apply vpc -s plat-uw2-dev -auto-approve
        - command: terraform apply vpc-flow-logs-bucket -s plat-ue2-staging -auto-approve
        - command: terraform apply vpc -s plat-ue2-staging -auto-approve
        - command: terraform apply vpc-flow-logs-bucket -s plat-uw2-staging -auto-approve
        - command: terraform apply vpc -s plat-uw2-staging -auto-approve
        - command: terraform apply vpc-flow-logs-bucket -s plat-ue2-prod -auto-approve
        - command: terraform apply vpc -s plat-ue2-prod -auto-approve
        - command: terraform apply vpc-flow-logs-bucket -s plat-uw2-prod -auto-approve
        - command: terraform apply vpc -s plat-uw2-prod -auto-approve
    plan-all-vpc:
      description: |
        Run 'terraform plan' on all 'vpc' components in all stacks
      steps:
        - command: terraform plan vpc -s plat-ue2-dev
        - command: terraform plan vpc -s plat-uw2-dev
        - command: terraform plan vpc -s plat-ue2-staging
        - command: terraform plan vpc -s plat-uw2-staging
        - command: terraform plan vpc -s plat-ue2-prod
        - command: terraform plan vpc -s plat-uw2-prod
    plan-all-vpc-flow-logs:
      description: |
        Run 'terraform plan' on all 'vpc-flow-logs-bucket' components in all stacks
      steps:
        - command: terraform plan vpc-flow-logs-bucket -s plat-ue2-dev
        - command: terraform plan vpc-flow-logs-bucket -s plat-uw2-dev
        - command: terraform plan vpc-flow-logs-bucket -s plat-ue2-staging
        - command: terraform plan vpc-flow-logs-bucket -s plat-uw2-staging
        - command: terraform plan vpc-flow-logs-bucket -s plat-ue2-prod
        - command: terraform plan vpc-flow-logs-bucket -s plat-uw2-prod
validation.yaml:
  name: Validation
  description: Atmos workflows for VPCs and VPC Flow Logs validation
  workflows:
    validate-all-vpc:
      description: Validate all VPC components in all stacks
      steps:
        - command: validate component vpc -s plat-ue2-dev
        - command: validate component vpc -s plat-uw2-dev
        - command: validate component vpc -s plat-ue2-staging
        - command: validate component vpc -s plat-uw2-staging
        - command: validate component vpc -s plat-ue2-prod
        - command: validate component vpc -s plat-uw2-prod
    validate-all-vpc-flow-logs:
      description: Validate all VPC Flow Logs bucket components in all stacks
      steps:
        - command: validate component vpc-flow-logs-bucket -s plat-ue2-dev
        - command: validate component vpc-flow-logs-bucket -s plat-uw2-dev
        - command: validate component vpc-flow-logs-bucket -s plat-ue2-staging
        - command: validate component vpc-flow-logs-bucket -s plat-uw2-staging
        - command: validate component vpc-flow-logs-bucket -s plat-ue2-prod
        - command: validate component vpc-flow-logs-bucket -s plat-uw2-prod
```

:::tip
Use the [atmos workflow](/cli/commands/workflow) CLI command to execute an Atmos workflow
:::

---

## atmos devcontainer

Use this command to manage development containers (devcontainers) for your Atmos workflows.

Devcontainers provide isolated, reproducible development environments with all required tools and dependencies pre-configured.

> ⚠️ Experimental

_\[Video: atmos devcontainer]_

**Configure Devcontainers**

Learn how to configure devcontainers in your `atmos.yaml`, including runtime selection, mounts, environment variables, and container specifications.

Configuration Reference[Read more](/cli/configuration/devcontainer)

## Prerequisites

### Container Runtime Required

Atmos devcontainers require either **Docker** or **Podman** to be installed and running on your system.

### Docker

#### Installation

### macOS

Install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/)

```bash
# Or via Homebrew
brew install --cask docker
```

**Start Docker Desktop** from Applications or Launchpad.

### Windows

Install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)

**Start Docker Desktop** from the Start menu.

### Linux

Install [Docker Engine](https://docs.docker.com/engine/install/) for your distribution.

```bash
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

# Fedora/RHEL
sudo dnf install docker-ce docker-ce-cli containerd.io

# Start and enable Docker
sudo systemctl start docker
sudo systemctl enable docker
```

#### Verification

```bash
docker --version
docker ps
```

### Podman

#### Installation

### macOS

```bash
brew install podman
```

Or install [Podman Desktop](https://podman-desktop.io/) for GUI management.

**Initialize and start Podman machine:**

```bash
podman machine init
podman machine start
```

### Windows

Install [Podman Desktop for Windows](https://podman.io/docs/installation#windows)

**Initialize and start Podman machine:**

```bash
podman machine init
podman machine start
```

### Linux

```bash
# Ubuntu/Debian
sudo apt-get install podman

# Fedora/RHEL
sudo dnf install podman

# Arch
sudo pacman -S podman
```

**Note:** Podman runs rootless on Linux - no daemon required.

#### Verification

```bash
podman --version
podman ps
```

### Runtime Selection

Atmos automatically detects the available container runtime:

1. Checks for Docker first
2. Falls back to Podman if Docker is unavailable
3. You can override with `ATMOS_CONTAINER_RUNTIME=auto`, `ATMOS_CONTAINER_RUNTIME=docker`, or `ATMOS_CONTAINER_RUNTIME=podman`

## Usage

```shell
atmos devcontainer  [options]
```

## Subcommands

## Container Runtimes

Atmos supports both Docker and Podman as container runtimes:

- **Auto-detection**: Atmos automatically detects the available runtime (Docker first, then Podman)
- **Explicit selection**: Set `ATMOS_CONTAINER_RUNTIME=auto`, `ATMOS_CONTAINER_RUNTIME=docker`, or `ATMOS_CONTAINER_RUNTIME=podman`
- **Per-devcontainer**: Configure runtime in `atmos.yaml` under `devcontainer..settings.runtime`

## Configuration

Devcontainers are configured in `atmos.yaml` under the `devcontainer` section:

```yaml
devcontainer:
  :  # Choose any name for your devcontainer (e.g., geodesic, terraform, python)
    settings:
      runtime: auto  # Optional: auto (default), docker, podman, or omit
    spec: !include devcontainer.json
```

**Example with `geodesic`:**

```yaml
devcontainer:
  geodesic:
    spec: !include devcontainer.json
```

The `` is a user-defined identifier you choose for your devcontainer. Use it with commands like `atmos devcontainer start `.

The `spec` field follows the [VS Code Dev Container specification](https://containers.dev/implementors/json_reference/).

## Output Masking

Devcontainer commands support Atmos's automatic secret masking system with some limitations:

**Masking Support:**

- ✅ **`logs`** - Masking enabled by default
- ✅ **`exec`** - Masking enabled by default for non-interactive commands
- ⚠️ **`attach`/`shell`** - Masking **requires experimental `--pty` flag** (macOS/Linux only)

**Interactive Sessions:**

Interactive TTY sessions (`attach`, `shell`) cannot mask output in standard mode due to TTY limitations. To enable masking in interactive sessions:

```bash
# Enable masking in interactive sessions (experimental, macOS/Linux only)
atmos devcontainer attach geodesic --pty
atmos devcontainer shell geodesic --pty
```

:::warning
The `--pty` flag is experimental and not available on Windows. Standard attach/shell mode works on all platforms but cannot mask output in interactive TTY sessions.
:::

For complete masking configuration (patterns, options, use cases), see the [Secret Masking Configuration](/cli/configuration/settings/mask) documentation.

## Examples

```shell
# List all devcontainers
atmos devcontainer list

# Start a devcontainer
atmos devcontainer start geodesic

# Start and attach in one command
atmos devcontainer start geodesic --attach

# Attach to a running devcontainer
atmos devcontainer attach geodesic

# Show logs
atmos devcontainer logs geodesic

# Rebuild a devcontainer
atmos devcontainer rebuild geodesic

# Stop a devcontainer
atmos devcontainer stop geodesic

# Remove a devcontainer
atmos devcontainer remove geodesic
```

## See Also

- [Devcontainer Configuration](/cli/configuration/devcontainer) — Configure devcontainers in `atmos.yaml`
- [Dev Container Specification](https://containers.dev/) — Official Dev Container spec
- [Atmos Components](/components) — Learn about Atmos components

---

## atmos devcontainer attach

Use this command to attach to a running devcontainer and get an interactive shell. If the container is not running, it will be started automatically.

## Usage

```shell
atmos devcontainer attach  [flags]
```

## Arguments

- **`name`**
  Name of the devcontainer to attach to

## Flags

- **`--instance string`**
  Instance name for this devcontainer (default: 
  `default`
  )

## Examples

```shell
# Attach to a devcontainer
atmos devcontainer attach geodesic

# Attach to a specific instance
atmos devcontainer attach terraform --instance project-a
```

## Behavior

- Opens an interactive shell (`/bin/bash` by default) in the container
- Uses login shell (`-l` flag) if `userEnvProbe` is set to `loginShell` or `loginInteractiveShell` in the devcontainer configuration
- Automatically starts the container if it's not running
- Inherits your terminal size and supports full TTY features

## See Also

- [`atmos devcontainer start`](/cli/commands/devcontainer/start)
- [`atmos devcontainer exec`](/cli/commands/devcontainer/exec)

---

## atmos devcontainer config

Use this command to view the resolved configuration for a devcontainer, including all settings from your `atmos.yaml` and devcontainer spec.

## Usage

```shell
atmos devcontainer config 
```

## Arguments

- **`name`**
  Name of the devcontainer to show configuration for

## Examples

```shell
# Show devcontainer configuration
atmos devcontainer config geodesic
```

Example output:

```
Runtime: podman

Name: Geodesic
Image: cloudposse/geodesic:latest

Workspace Folder: /workspace
Workspace Mount: type=bind,source=/Users/erik/project,target=/workspace

Mounts:
  - type=bind,source=/Users/erik/.aws,target=/root/.aws,readonly
  - type=volume,source=geodesic-home,target=/root

Forward Ports:
  - 8080
  - 3000

Environment Variables:
  ATMOS_BASE_PATH: /workspace
  ATMOS_IDENTITY: acme-identity

Run Arguments:
  - --hostname=geodesic

Remote User: root
```

## See Also

- [`atmos devcontainer list`](/cli/commands/devcontainer/list)

---

## atmos devcontainer exec

Use this command to execute a specific command inside a running devcontainer without attaching to an interactive shell.

## Usage

```shell
atmos devcontainer exec  --  [args...]
```

## Arguments

- **`name`**
  Name of the devcontainer to execute the command in
- **`command` (after `--`)**
  The command and arguments to execute in the container

## Flags

- **`--instance string`**
  Instance name for this devcontainer (default: 
  `default`
  )
- **`--interactive, -i`**
  Enable interactive TTY mode for full terminal support (tab completion, colors, etc.). 
  **Note**
  : Output masking is not available in interactive mode due to TTY limitations.
- **`--pty`**
  **Experimental**
  : Use PTY mode with masking support. Provides both TTY features AND output masking. 
  **Platform**
  : macOS and Linux only (not available on Windows).

## Examples

```shell
# Run a single command (non-interactive, output masked)
atmos devcontainer exec geodesic -- terraform version

# Run a command with arguments
atmos devcontainer exec geodesic -- atmos terraform plan vpc -s dev

# Check environment variables (output masked)
atmos devcontainer exec geodesic -- env | grep AWS

# Use interactive mode for full TTY support
atmos devcontainer exec geodesic --interactive -- bash
atmos devcontainer exec geodesic -i -- vim ~/.bashrc

# Execute in a specific instance
atmos devcontainer exec terraform --instance project-a -- terraform init
```

## Behavior

- Executes the command in the running container
- Streams stdout and stderr to your terminal
- Returns the exit code of the command
- Container must be running (use `atmos devcontainer start` first)
- **Automatic masking**: Output is automatically masked based on patterns configured in `atmos.yaml`

## Output Masking

Atmos provides three execution modes with different tradeoffs between TTY features and output masking.

For complete masking configuration (patterns, options, use cases), see the [Secret Masking Configuration](/cli/configuration/settings/mask) documentation.

### 1. Non-Interactive Mode (Default)

Output masking works reliably. Sensitive data like AWS keys, GitHub tokens, and other secrets are automatically redacted according to your mask configuration.

```shell
atmos devcontainer exec geodesic -- env | grep AWS
# AWS_ACCESS_KEY_ID=***MASKED***
# AWS_SECRET_ACCESS_KEY=***MASKED***
```

**Use when**: You need automatic masking of sensitive data in command output.

### 2. Interactive Mode (`--interactive`)

Full TTY support (tab completion, colors, cursor control), but output masking is not available due to TTY data flowing at the kernel level.

```shell
atmos devcontainer exec geodesic --interactive -- bash
# Inside bash: echo $AWS_SECRET_ACCESS_KEY shows actual value
```

**Use when**: You need full TTY features like tab completion or interactive editors.

### 3. PTY Mode (`--pty`) **EXPERIMENTAL**

Provides **both** TTY features AND output masking using a PTY (pseudo-terminal) proxy layer.

```shell
atmos devcontainer exec geodesic --pty -- bash
# Full TTY + automatic masking of sensitive data
```

**Platform support**: macOS and Linux only (not available on Windows)

**Use when**: You need both TTY features AND masking protection (experimental feature, feedback welcome).

:::tip Mode Selection Guide

- **Non-interactive** (default): Masking works, no TTY features
- **Interactive** (`--interactive`): Full TTY, no masking
- **PTY** (`--pty`): Both TTY + masking (experimental, Unix only)
- For interactive shells, consider [`atmos devcontainer shell`](/cli/commands/devcontainer/shell) instead
  :::

## See Also

- [`atmos devcontainer attach`](/cli/commands/devcontainer/attach)
- [`atmos devcontainer start`](/cli/commands/devcontainer/start)
- [`atmos devcontainer shell`](/cli/commands/devcontainer/shell) - Interactive shell (masking not available)

---

## atmos devcontainer list

Use this command to list all devcontainers configured in your Atmos project.

## Usage

```shell
atmos devcontainer list
```

This command displays all devcontainers defined in your `atmos.yaml` configuration along with their current status (running, stopped, or not created).

## Output

The command shows:

- **Name**: The devcontainer name from your configuration
- **Status**: Current state (running, stopped, or `-` if not created)
- **Runtime**: Container runtime being used (docker or podman)
- **Image**: The container image specified in the configuration

## Examples

```shell
# List all devcontainers
atmos devcontainer list
```

Example output:

```
NAME        STATUS    RUNTIME   IMAGE
geodesic    running   podman    cloudposse/geodesic:latest
terraform   stopped   podman    hashicorp/terraform:1.6
python      -         podman    python:3.11-slim
```

## See Also

- [`atmos devcontainer start`](/cli/commands/devcontainer/start)
- [`atmos devcontainer config`](/cli/commands/devcontainer/config)

---

## atmos devcontainer logs

Use this command to view logs from a devcontainer, useful for debugging container startup issues or monitoring background processes.

## Usage

```shell
atmos devcontainer logs  [flags]
```

## Arguments

- **`name`**
  Name of the devcontainer to show logs from

## Flags

- **`--instance string`**
  Instance name for this devcontainer (default: 
  `default`
  )
- **`--follow` / `-f`**
  Follow log output in real-time
- **`--tail string`**
  Number of lines to show from the end of the logs (default: 
  `all`
  )

## Examples

```shell
# Show all logs
atmos devcontainer logs geodesic

# Follow logs in real-time
atmos devcontainer logs geodesic --follow

# Show last 100 lines
atmos devcontainer logs geodesic --tail 100

# Show logs from a specific instance
atmos devcontainer logs terraform --instance project-a --follow
```

## See Also

- [`atmos devcontainer start`](/cli/commands/devcontainer/start)
- [`atmos devcontainer attach`](/cli/commands/devcontainer/attach)

---

## atmos devcontainer rebuild

Use this command to rebuild a devcontainer from scratch. This stops and removes the existing container, pulls the latest image, and creates a new container.

## Usage

```shell
atmos devcontainer rebuild  [flags]
```

## Arguments

- **`name`**
  Name of the devcontainer to rebuild

## Flags

- **`--instance string`**
  Instance name for this devcontainer (default: 
  `default`
  )
- **`--attach`**
  Attach to the container after rebuilding
- **`--no-pull`**
  Don't pull the latest image before rebuilding

## Examples

```shell
# Rebuild a devcontainer
atmos devcontainer rebuild geodesic

# Rebuild and attach
atmos devcontainer rebuild geodesic --attach

# Rebuild without pulling latest image
atmos devcontainer rebuild geodesic --no-pull

# Rebuild a specific instance
atmos devcontainer rebuild terraform --instance project-a
```

## Behavior

1. Stops the container if it's running
2. Removes the existing container
3. Pulls the latest image (unless `--no-pull` is specified)
4. Creates a new container with the current configuration
5. Starts the new container
6. Optionally attaches to the container (if `--attach` is specified)

## Use Cases

- Configuration changes: After updating your `devcontainer.json` or `atmos.yaml`
- Image updates: To get the latest version of the base image
- Clean slate: When you want to start fresh without any accumulated state

## See Also

- [`atmos devcontainer start`](/cli/commands/devcontainer/start)
- [`atmos devcontainer remove`](/cli/commands/devcontainer/remove)

---

## atmos devcontainer remove

Use this command to permanently remove a devcontainer. This stops the container if it's running and deletes it.

## Usage

```shell
atmos devcontainer remove  [flags]
```

## Arguments

- **`name`**
  Name of the devcontainer to remove

## Flags

- **`--instance string`**
  Instance name for this devcontainer (default: 
  `default`
  )
- **`--force` / `-f`**
  Force removal even if the container is running

## Examples

```shell
# Remove a devcontainer
atmos devcontainer remove geodesic

# Force remove a running devcontainer
atmos devcontainer remove geodesic --force

# Remove a specific instance
atmos devcontainer remove terraform --instance project-a
```

## Behavior

- Stops the container if it's running (or immediately if `--force` is used)
- Removes the container and its associated data
- Does not remove the container image (use `docker rmi` or `podman rmi` separately if needed)
- Does not remove named volumes (use `docker volume rm` or `podman volume rm` separately if needed)

## See Also

- [`atmos devcontainer stop`](/cli/commands/devcontainer/stop)
- [`atmos devcontainer rebuild`](/cli/commands/devcontainer/rebuild)

---

## atmos devcontainer shell

Use this command to quickly launch an interactive shell in a devcontainer. This is a convenience command equivalent to `start --attach` that provides the fastest way to get into a development environment.

## Usage

```shell
atmos devcontainer shell  [flags]
```

## Arguments

- **`name`**
  Name of the devcontainer to launch (from 
  `atmos.yaml`
   configuration)

## Flags

- **`--instance string`**
  Instance name for this devcontainer (default: 
  `default`
  ). Use this to run multiple instances of the same devcontainer configuration.
- **`--identity string` or `-i`**
  Authenticate with specified identity. Inside the container, cloud provider SDKs automatically use the authenticated identity.
- **`--new`**
  Always create a new instance with auto-generated numbered name based on the 
  `--instance`
   value (e.g., 
  `default-1`
  , 
  `default-2`
  , or 
  `alice-1`
   with 
  `--instance alice`
  ). Use this when you want a fresh container that doesn't reuse existing instances.
- **`--replace`**
  Destroy and recreate the instance specified by 
  `--instance`
   flag. This rebuilds the container from scratch: if using a 
  `build`
   configuration, it rebuilds the image from the Dockerfile; if using an 
  `image`
  , it pulls the latest version. The container is then recreated with current configuration.
- **`--rm`**
  Automatically remove the container when you exit the shell. Similar to Docker's 
  `docker run --rm`
   behavior. Useful for temporary or one-off containers.
- **`--pty`**
  Experimental: Use PTY mode with masking support (not available on Windows). Enables masking of sensitive values in terminal output. See 
  [Secret Masking Configuration](/cli/configuration/settings/mask)
   for pattern configuration.

## Examples

### Basic Usage

```shell
# Launch shell in the geodesic devcontainer
atmos devcontainer shell geodesic

# Launch shell in a named instance
atmos devcontainer shell terraform --instance alice

# Launch shell with custom runtime (via environment variable)
export ATMOS_CONTAINER_RUNTIME=podman
atmos devcontainer shell geodesic
```

### Instance Management

```shell
# Create a new auto-numbered instance (default-1, default-2, etc.)
atmos devcontainer shell geodesic --new

# Create a new auto-numbered instance with custom base name
atmos devcontainer shell geodesic --instance alice --new
# Creates: alice-1, alice-2, etc.

# Rebuild existing instance from scratch
atmos devcontainer shell terraform --replace

# Rebuild a specific named instance
atmos devcontainer shell terraform --instance prod --replace
```

### Rebuilding Custom Dockerfiles

When using a devcontainer with a custom Dockerfile (using `build` configuration), the `--replace` flag will rebuild the image from your Dockerfile:

```shell
# After modifying your Dockerfile, rebuild and relaunch
atmos devcontainer shell geodesic --replace
```

This is particularly useful when:

- You've updated your Dockerfile with new tools or dependencies
- You want to pick up changes to build arguments
- You need a fresh container with the latest image build

### Temporary Containers

```shell
# Launch shell and auto-remove container on exit (like 'docker run --rm')
atmos devcontainer shell geodesic --rm

# Combine --new with --rm for completely ephemeral containers
atmos devcontainer shell geodesic --new --rm
```

### Authenticated Containers

```shell
# Launch shell with Atmos-managed identity
atmos devcontainer shell geodesic --identity prod-admin

# Launch new instance with identity that auto-removes on exit
atmos devcontainer shell terraform --identity dev-user --new --rm
```

## Behavior

This command performs the following operations automatically:

1. **Container doesn't exist**: Creates a new container with the configured image, mounts, ports, and environment variables
2. **Container exists but stopped**: Starts the existing container
3. **Container running**: Connects to the running container
4. **Always**: Attaches to the container with an interactive shell

The `shell` command is designed to be the quickest way to get into a devcontainer environment. It's equivalent to running `atmos devcontainer start  --attach` and follows the same pattern as other Atmos shell commands:

- `atmos terraform shell` - Interactive Terraform shell
- `atmos auth shell` - Interactive shell with authenticated identity

## Comparison with Other Commands

| Command | Creates Container | Starts Container | Attaches Shell |
|---------|------------------|------------------|----------------|
| `atmos devcontainer start` | ✅ | ✅ | ❌ |
| `atmos devcontainer start --attach` | ✅ | ✅ | ✅ |
| `atmos devcontainer shell` | ✅ | ✅ | ✅ |
| `atmos devcontainer attach` | ❌ | ❌ | ✅ |

**Use `shell` when**: You want to quickly get into an interactive development environment
**Use `start` when**: You want to start a container without attaching (e.g., to run background services)
**Use `attach` when**: The container is already running and you just want to connect to it

## Use with Aliases

The `shell` command works particularly well with command aliases in `atmos.yaml`:

```yaml
aliases:
  shell: "devcontainer shell geodesic"
```

This allows you to simply run:

```shell
atmos shell
```

This pattern provides a consistent experience with traditional shell wrappers like Geodesic.

## See Also

- [Devcontainer Configuration](/cli/configuration/devcontainer) — Configure devcontainers in `atmos.yaml`
- [`atmos devcontainer start`](/cli/commands/devcontainer/start)
- [`atmos devcontainer attach`](/cli/commands/devcontainer/attach)
- [`atmos devcontainer list`](/cli/commands/devcontainer/list)
- [Command Aliases](/cli/configuration/aliases)

---

## atmos devcontainer start

Use this command to start a devcontainer. If the container doesn't exist, it will be created. If it exists but is stopped, it will be started.

## Usage

```shell
atmos devcontainer start  [flags]
```

## Arguments

- **`name`**
  Name of the devcontainer to start (from 
  `atmos.yaml`
   configuration)

## Flags

- **`--instance string`**
  Instance name for this devcontainer (default: 
  `default`
  ). Use this to run multiple instances of the same devcontainer configuration.
- **`--attach`**
  Attach to the container after starting it. This immediately opens an interactive shell in the container.

## Examples

```shell
# Start a devcontainer
atmos devcontainer start geodesic

# Start and immediately attach
atmos devcontainer start geodesic --attach

# Start a specific instance
atmos devcontainer start terraform --instance project-a

# Start with custom runtime (via environment variable)
export ATMOS_CONTAINER_RUNTIME=podman
atmos devcontainer start geodesic
```

## Behavior

1. **Container doesn't exist**: Creates a new container with the configured image, mounts, ports, and environment variables, then starts it
2. **Container exists but stopped**: Starts the existing container
3. **Container already running**: Reports that the container is already running

The container name follows the pattern: `atmos-devcontainer--`

## See Also

- [Devcontainer Configuration](/cli/configuration/devcontainer) — Configure devcontainers in `atmos.yaml`
- [`atmos devcontainer attach`](/cli/commands/devcontainer/attach)
- [`atmos devcontainer stop`](/cli/commands/devcontainer/stop)
- [`atmos devcontainer list`](/cli/commands/devcontainer/list)

---

## atmos devcontainer stop

Use this command to stop a running devcontainer. The container is not removed, so you can start it again later.

## Usage

```shell
atmos devcontainer stop  [flags]
```

## Arguments

- **`name`**
  Name of the devcontainer to stop

## Flags

- **`--instance string`**
  Instance name for this devcontainer (default: 
  `default`
  )
- **`--timeout int`**
  Timeout in seconds to wait for container to stop (default: 
  `10`
  )

## Examples

```shell
# Stop a devcontainer
atmos devcontainer stop geodesic

# Stop with custom timeout
atmos devcontainer stop geodesic --timeout 30

# Stop a specific instance
atmos devcontainer stop terraform --instance project-a
```

## See Also

- [`atmos devcontainer start`](/cli/commands/devcontainer/start)
- [`atmos devcontainer remove`](/cli/commands/devcontainer/remove)

---

## atmos docs generate

Use this command to generate one of your documentation artifacts (e.g. a README) as defined by the **named** section under `docs.generate.` in `atmos.yaml`.

Replace `` with the name of the section you want to run (for example, `readme`, `release-notes`, etc.).

In `atmos.yaml`, you can define **one or more** documentation‐generation blocks under `docs.generate`.  Each top‐level key becomes a CLI argument:

```yaml
docs:
  generate:
    readme:
      base-dir: .
      input:
        - "./README.yaml"
      template: "https://.../README.md.gotmpl"
      output: "./README.md"
      terraform:
        source: src/
        enabled: false
        format: "markdown"
        show_providers: false
        show_inputs: true
        show_outputs: true
        sort_by: "name"
        hide_empty: false
        indent_level: 2

    release-notes:
      base-dir: .
      input:
        - "./CHANGELOG.yaml"
      template: "./release-notes.gotmpl"
      output: "./RELEASE_NOTES.md"
```

For each CLI argument the command combines all local or remote YAML files specified at `input` and template file then generates the documentation artifact at the respective `output` folder. In case the template contains `terraform_docs` key, e.g.

```yaml
{{- $data := (ds "config") -}}

{{ $data.name | default "Project Title" }}

{{ $data.description | default "No description provided." }}

{{ if has $data "extra_info" }}
Extra info: {{ $data.extra_info }}
{{ end }}

{{ if has $data "terraform_docs" }}
## Terraform Docs
{{ $data.terraform_docs }}
{{ end }}

```

the resultant file will also have a corresponding section rendered. By default `terraform.format` is set to `markdown table`, and can also be `markdown`, `tfvars hcl`, and `tfvars json`.

## Dynamic Keys

If you add a new key under docs.generate—say readme2 or release-notes —you simply pass that key to the CLI:

```shell
atmos docs generate readme2
atmos docs generate release-notes
```

## Usage

```shell
atmos docs generate readme
```

## Supported Sources for README.yaml and template

### Local Sources

It supports the following local file sources:

- Absolute paths

  ```yaml
  docs:
    generate:
      readme:
        input:
          - "/Users/me/Documents/README.yaml"
        template: "/Users/me/Documents/README.md.gotmpl"
  ```

- Paths relative to the current working directory

  ```yaml
  docs:
    generate:
      readme:
        input:
          - "./README.yaml"
        template: "./README.md.gotmpl"
  ```

- Paths relative to the `base_dir` defined in `atmos.yaml` CLI config file (then resolved as relative to cwd)

  ```yaml
  docs:
    generate:
      readme:
        input:
          - "terraform/README.yaml"
        template: "terraform/README.md.gotmpl"
  ```

### Remote Sources

To download remote files, Atmos uses [`go-getter`](https://github.com/hashicorp/go-getter)
(used by [Terraform](https://www.terraform.io/) for downloading modules)

---

## atmos docs

Use this command to open the [Atmos docs](https://atmos.tools/) or generate documentation artifacts like READMEs and release notes.

_\[Video: atmos docs]_

**Configure Documentation Generation**

Learn how to configure documentation generators for READMEs, release notes, and other artifacts.

Configuration Reference[Read more](/cli/configuration/docs)

## Usage

When run on its own, the `atmos docs` command opens [Atmos docs](https://atmos.tools/), but it can also display documentation for specified components. For example:

```shell
atmos docs
atmos docs vpc
atmos docs eks/cluster
```

## Subcommands

---

## atmos emulator down

Use this command to stop and remove the emulator's container. The container is discovered by the label derived from its canonical instance address. Persisted state is **kept** — the next [`atmos emulator up`](/cli/commands/emulator/up) resumes from it. Use [`atmos emulator reset`](/cli/commands/emulator/reset) to wipe persisted state instead.

> ⚠️ Experimental

## Usage

```shell
atmos emulator down  --stack  [flags]
```

## Examples

```shell
# Stop and remove the aws emulator in the plat-ue2-dev stack
atmos emulator down aws --stack=plat-ue2-dev

# Using the emu alias
atmos emu down aws --stack=plat-ue2-dev
```

## Arguments

- **`component`**
  **Required.**
   The emulator component to stop (for example, 
  `aws`
  ).

## Flags

- **`-s, --stack`**
  **Required.**
   The stack the emulator component belongs to (for example, 
  `plat-ue2-dev`
  ).

## See Also

- [`atmos emulator up`](/cli/commands/emulator/up) — Start (or reuse) the emulator container
- [`atmos emulator reset`](/cli/commands/emulator/reset) — Stop the emulator and wipe its persisted state
- [`atmos emulator ps`](/cli/commands/emulator/ps) — List running emulators in the stack

---

## atmos emulator exec

Use this command to run a command inside the emulator's container. Pass the command and its arguments after `--`. If no command is supplied, a shell is opened. The container is discovered by label.

> ⚠️ Experimental

## Usage

```shell
atmos emulator exec  --stack  --  [args...]
```

## Examples

```shell
# List S3 buckets in the local aws emulator
atmos emulator exec aws --stack=plat-ue2-dev -- aws s3 ls

# Open a shell in the emulator container (no command after --)
atmos emulator exec aws --stack=plat-ue2-dev

# Using the emu alias
atmos emu exec aws --stack=plat-ue2-dev -- env
```

## Arguments

- **`component`**
  **Required.**
   The emulator component to run the command in (for example, 
  `aws`
  ).
- **`command` (after `--`)**
  The command and arguments to run inside the container. Everything after 
  `--`
   is passed through to the container verbatim. If omitted, a shell is opened.

## Flags

- **`-s, --stack`**
  **Required.**
   The stack the emulator component belongs to (for example, 
  `plat-ue2-dev`
  ).

## See Also

- [`atmos emulator up`](/cli/commands/emulator/up) — Start (or reuse) the emulator container
- [`atmos emulator logs`](/cli/commands/emulator/logs) — Stream the emulator container's logs

---

## atmos emulator list

Use this command to list emulator containers in a clean, theme-aware table with a status dot (green when running), a short image name, and the container ID. Containers are discovered by the labels derived from their canonical instance addresses. Scope the output to a single stack with `--stack`, or omit it to list emulators across every stack.

> ⚠️ Experimental

## Usage

```shell
atmos emulator list [flags]
```

Unlike [`atmos emulator ps`](/cli/commands/emulator/ps), `list` does not take a component argument — it lists every emulator (optionally filtered by stack).

## Examples

```shell
# List emulators across all stacks
atmos emulator list

# List emulators in a single stack
atmos emulator list --stack=plat-ue2-dev

# Using the emu alias and the ls shorthand
atmos emu ls --stack=plat-ue2-dev
```

## Flags

- **`-s, --stack`**
  Optional. Restrict the listing to a single stack (for example, 
  `plat-ue2-dev`
  ). When omitted, emulators from every stack are listed.

## See Also

- [`atmos emulator ps`](/cli/commands/emulator/ps) — List running emulators in a component's stack
- [`atmos emulator up`](/cli/commands/emulator/up) — Start (or reuse) the emulator container
- [`atmos emulator logs`](/cli/commands/emulator/logs) — Stream the emulator container's logs

---

## atmos emulator logs

Use this command to stream the emulator container's logs, useful for debugging emulator startup or watching requests as your components interact with the local cloud API. The container is discovered by label.

> ⚠️ Experimental

## Usage

```shell
atmos emulator logs  --stack  [flags]
```

## Examples

```shell
# Stream the aws emulator's logs in the plat-ue2-dev stack
atmos emulator logs aws --stack=plat-ue2-dev

# Using the emu alias
atmos emu logs aws --stack=plat-ue2-dev
```

## Arguments

- **`component`**
  **Required.**
   The emulator component whose logs are streamed (for example, 
  `aws`
  ).

## Flags

- **`-s, --stack`**
  **Required.**
   The stack the emulator component belongs to (for example, 
  `plat-ue2-dev`
  ).

## See Also

- [`atmos emulator ps`](/cli/commands/emulator/ps) — List running emulators in the stack
- [`atmos emulator exec`](/cli/commands/emulator/exec) — Run a command in the emulator container

---

## atmos emulator ps

Use this command to list the running emulator containers in the component's stack. Containers are discovered by the labels derived from their canonical instance addresses.

> ⚠️ Experimental

## Usage

```shell
atmos emulator ps  --stack  [flags]
```

## Examples

```shell
# List running emulators in the plat-ue2-dev stack
atmos emulator ps aws --stack=plat-ue2-dev

# Using the emu alias
atmos emu ps aws --stack=plat-ue2-dev
```

## Arguments

- **`component`**
  **Required.**
   The emulator component whose stack is inspected (for example, 
  `aws`
  ).

## Flags

- **`-s, --stack`**
  **Required.**
   The stack to list running emulators for (for example, 
  `plat-ue2-dev`
  ).

## See Also

- [`atmos emulator up`](/cli/commands/emulator/up) — Start (or reuse) the emulator container
- [`atmos emulator logs`](/cli/commands/emulator/logs) — Stream the emulator container's logs

---

## atmos emulator reset

Use this command to stop and remove the emulator's container and then delete its persisted state directory under the XDG cache. The next [`atmos emulator up`](/cli/commands/emulator/up) starts a fresh instance. This is the deliberate counterpart to [`atmos emulator down`](/cli/commands/emulator/down), which keeps persisted state.

> ⚠️ Experimental

## Usage

```shell
atmos emulator reset  --stack  [flags]
```

Unless `--force` is set, `reset` prompts for confirmation before deleting persisted state.

## Examples

```shell
# Stop the aws emulator and wipe its persisted state (with a confirmation prompt)
atmos emulator reset aws --stack=plat-ue2-dev

# Wipe without prompting
atmos emulator reset aws --stack=plat-ue2-dev --force

# Using the emu alias
atmos emu reset aws --stack=plat-ue2-dev --force
```

## Arguments

- **`component`**
  **Required.**
   The emulator component to reset (for example, 
  `aws`
  ).

## Flags

- **`-s, --stack`**
  **Required.**
   The stack the emulator component belongs to (for example, 
  `plat-ue2-dev`
  ).
- **`-f, --force`**
  Wipe persisted state without prompting for confirmation. Also settable with 
  `ATMOS_EMULATOR_RESET_FORCE=true`
  .

## See Also

- [`atmos emulator up`](/cli/commands/emulator/up) — Start (or reuse) the emulator container
- [`atmos emulator down`](/cli/commands/emulator/down) — Stop and remove the emulator container (state is kept)
- [`atmos emulator ps`](/cli/commands/emulator/ps) — List running emulators in the stack

---

## atmos emulator up

Use this command to start (or reuse) the emulator's long-running container. The container is labeled by its canonical instance address and outlives the `atmos` process, so it stays available for subsequent commands.

By default the emulator **persists its state** across `down`/`up` by bind-mounting a host directory under the XDG cache (`$XDG_CACHE_HOME/atmos/emulator/`, overridable with `ATMOS_XDG_CACHE_HOME`) onto the emulator's data directory. Pass `--ephemeral` for a throwaway instance, or use [`atmos emulator reset`](/cli/commands/emulator/reset) to wipe persisted state.

> ⚠️ Experimental

## Usage

```shell
atmos emulator up  --stack  [flags]
```

## Examples

```shell
# Start (or reuse) the aws emulator in the plat-ue2-dev stack
atmos emulator up aws --stack=plat-ue2-dev

# Start a throwaway instance that does not persist state
atmos emulator up aws --stack=plat-ue2-dev --ephemeral

# Using the emu alias
atmos emu up aws --stack=plat-ue2-dev
```

## Arguments

- **`component`**
  **Required.**
   The emulator component to start (for example, 
  `aws`
  ).

## Flags

- **`-s, --stack`**
  **Required.**
   The stack the emulator component belongs to (for example, 
  `plat-ue2-dev`
  ).
- **`--ephemeral`**
  Run without persisting state; data is discarded on 
  `down`
  . Persistence is enabled by default, so this forces a throwaway instance for this 
  `up`
   (overriding the component's 
  `ephemeral:`
   config). Also settable with 
  `ATMOS_EMULATOR_EPHEMERAL=true`
  .

## See Also

- [`atmos emulator down`](/cli/commands/emulator/down) — Stop and remove the emulator container (state is kept)
- [`atmos emulator reset`](/cli/commands/emulator/reset) — Stop the emulator and wipe its persisted state
- [`atmos emulator ps`](/cli/commands/emulator/ps) — List running emulators in the stack

---

## atmos emulator

Use these commands to manage emulator components: stack-scoped, long-running containers that stand in for a cloud API (AWS, GCP, Azure), Kubernetes, or a backing service (Vault) during local development and testing.

An emulator container outlives the `atmos` process and is discovered by labels derived from the canonical component instance address, so subsequent commands (`ps`, `logs`, `exec`, `down`) reattach to the already-running container.

> ⚠️ Experimental

**Configure Container Runtimes**

Learn how to select and configure the container runtime (Docker or Podman) that Atmos uses to run emulators.

Configuration Reference[Read more](/stacks/components/emulator#container-runtime)

## Usage

```shell
atmos emulator   --stack  [flags]
```

The `emulator` command also has the alias `emu`.

## Subcommands

## Container Runtime

Emulators run inside a container runtime. Atmos selects the runtime via the global
`container.runtime.provider` configuration and the `ATMOS_CONTAINER_RUNTIME`
environment variable; `auto` (or an omitted provider) detects Docker, then Podman.
There is no per-command
runtime flag.

```shell
# Force Podman for emulator commands
ATMOS_CONTAINER_RUNTIME=podman atmos emulator up aws --stack=plat-ue2-dev
```

## Configuration

Emulator components are declared in your stacks under `components.emulator`. Each
emulator selects a `driver` (for example, `floci/aws` for a local AWS sandbox):

```yaml
components:
  emulator:
    aws:
      driver: floci/aws
      region: "{{ .vars.region }}"
```

With the configuration above, `atmos emulator up aws --stack=plat-ue2-dev` starts the
local AWS sandbox for the `plat-ue2-dev` stack.

For the full list of drivers and how to configure each emulator type (AWS, GCP, Azure,
Kubernetes, Vault/OpenBao, and registry), see [Emulator Components](/stacks/components/emulator).

## Examples

```shell
# Start (or reuse) the aws emulator in the plat-ue2-dev stack
atmos emulator up aws --stack=plat-ue2-dev

# List running emulators in the stack
atmos emulator ps aws --stack=plat-ue2-dev

# Stream the emulator's logs
atmos emulator logs aws --stack=plat-ue2-dev

# Run a command inside the emulator container
atmos emulator exec aws --stack=plat-ue2-dev -- aws s3 ls

# Stop and remove the emulator container
atmos emulator down aws --stack=plat-ue2-dev
```

## See Also

- [Atmos Components](/components) — Learn about Atmos components
- [Emulator Components](/stacks/components/emulator) — Configure emulator components in stack manifests

---

## atmos env

Output environment variables from the `env` section of `atmos.yaml` in various formats suitable for shell evaluation, `.env` files, JSON consumption, or GitHub Actions workflows.

## Usage

```shell
atmos env [--format bash|json|dotenv|github] [--output ]
```

## How It Works

The `atmos env` command reads the `env` section from your `atmos.yaml` configuration (including any active profiles) and outputs the environment variables in your chosen format. This is useful for:

- Exporting Atmos-configured environment variables to your shell
- Generating `.env` files for other tools
- Integrating with CI/CD pipelines, especially GitHub Actions
- Inspecting what environment variables Atmos will set

## Examples

### Shell Evaluation

Load environment variables into your current shell:

**Bash/Zsh:**

```bash
# Load env vars into current shell
eval $(atmos env)

# Now tools like Terraform can use GITHUB_TOKEN, TF_PLUGIN_CACHE_DIR, etc.
terraform init
```

**PowerShell (Windows):**

```powershell
# Load env vars into current session using JSON format
$envVars = atmos env --format json | ConvertFrom-Json
$envVars.PSObject.Properties | ForEach-Object { Set-Item -Path "Env:$($_.Name)" -Value $_.Value }

# Now tools like Terraform can use GITHUB_TOKEN, TF_PLUGIN_CACHE_DIR, etc.
terraform init
```

### Using with Profiles

Profiles are automatically applied when specified:

```bash
# Use CI profile
eval $(atmos env --profile ci)

# Or via environment variable
ATMOS_PROFILE=ci eval $(atmos env)
```

### GitHub Actions Integration

Write environment variables directly to GitHub Actions:

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Install Atmos
        uses: cloudposse/github-action-atmos@v2

      - name: Export Atmos environment
        run: atmos env --format github

      - name: Use environment variables
        run: |
          echo "GitHub token available for private modules"
          terraform init
```

The `--format github` option automatically writes to the `$GITHUB_ENV` file. You can also specify a custom output file:

```bash
# Write to custom file
atmos env --format github --output /tmp/my-env

# In GitHub Actions, use the default $GITHUB_ENV
atmos env --format github
```

### Generate .env File

Create a `.env` file for tools that support it:

```bash
atmos env --format dotenv --output .env
```

### JSON Output

Get environment variables as JSON for programmatic use:

```bash
atmos env --format json | jq .GITHUB_TOKEN
```

## Output Formats

| Format | Output Example | Use Case |
|--------|----------------|----------|
| `bash` (default) | `export KEY='value'` | Shell evaluation with `eval $(atmos env)` |
| `dotenv` | `KEY='value'` | `.env` files for Docker, direnv, etc. |
| `json` | `{"KEY": "value"}` | Programmatic consumption with jq, scripts |
| `github` | `KEY=value` | GitHub Actions `$GITHUB_ENV` file |

### Format Details

**bash** (default): Shell export statements with proper escaping for single quotes.

```bash
export GITHUB_TOKEN='ghp_xxxx'
export TF_PLUGIN_CACHE_DIR='/tmp/terraform-plugin-cache'
```

**dotenv**: Standard `.env` file format.

```
GITHUB_TOKEN='ghp_xxxx'
TF_PLUGIN_CACHE_DIR='/tmp/terraform-plugin-cache'
```

**json**: JSON object for programmatic use.

```json
{
  "GITHUB_TOKEN": "ghp_xxxx",
  "TF_PLUGIN_CACHE_DIR": "/tmp/terraform-plugin-cache"
}
```

**github**: GitHub Actions environment file format. Multiline values use heredoc syntax.

```
GITHUB_TOKEN=ghp_xxxx
TF_PLUGIN_CACHE_DIR=/tmp/terraform-plugin-cache
```

## Flags

- **`--format`, `-f`**

  Output format for the environment variables. Default: `bash`.
  - **`bash`** (default): Prints `export KEY='value'` lines suitable for shell evaluation
  - **`dotenv`**: Prints `KEY='value'` lines in dotenv format
  - **`json`**: Prints a JSON object of environment variables
  - **`github`**: Prints `KEY=value` lines for GitHub Actions `$GITHUB_ENV` file
- **`--output`, `-o`**

  Output file path. When specified, writes to the file instead of stdout.

  For `--format github`, if `--output` is not specified, the command reads the `GITHUB_ENV` environment variable and writes to that file. If `GITHUB_ENV` is not set, an error is returned.

  For other formats, if `--output` is not specified, output goes to stdout.

## Configuration

Environment variables are configured in the `env` section of `atmos.yaml`:

**File:** `atmos.yaml`

```yaml
env:
  # Dynamic values using YAML functions
  GITHUB_TOKEN: !exec gh auth token

  # Static values
  AWS_SDK_LOAD_CONFIG: "true"
  TF_PLUGIN_CACHE_DIR: /tmp/terraform-plugin-cache
```

**Configure Environment Variables**

Learn how to define global environment variables in your `atmos.yaml` using static values, YAML functions, and shell expansion.

## Notes

- Environment variables are sorted alphabetically in the output for deterministic results
- The `github` format appends to the output file (doesn't overwrite), following GitHub Actions conventions
- Shell escaping is applied for `bash` and `dotenv` formats (single quotes with `'` escaped as `'\''`)
- Profile merging happens automatically - use `--profile` or `ATMOS_PROFILE` to select a profile
- This command only outputs global env from `atmos.yaml`, not component-level env from stacks

## See Also

- [Environment Variables Configuration](/cli/configuration/env) - Configure global environment variables
- [Profiles](/cli/configuration/profiles) - Environment-specific configuration overrides
- [Stack Environment Variables](/stacks/env) - Component-level environment variables
- [`atmos auth env`](/cli/commands/auth/env) - Export cloud provider credentials

---

## GKE kubeconfig authentication

Configure a GKE kubeconfig whose short-lived access tokens are supplied dynamically by Atmos Auth. No `gcloud`, Application Default Credentials bootstrap, or external GKE authentication plugin is required.

## Usage

```shell
atmos gcp gke  [flags]
```

## Examples

```shell
atmos gcp gke token --identity example-deployer
```

## Configuration

```yaml
auth:
  integrations:
    example-gke:
      kind: gcp/gke
      via:
        identity: example-deployer
      spec:
        cluster:
          name: example-cluster
          project_id: example-project
          location: us-central1
          alias: example
          kubeconfig:
            update: merge
```

The cluster `name`, `project_id`, and regional or zonal `location` are required. With no custom path, Atmos writes its own kubeconfig under the XDG config directory. `kubeconfig.update` supports `merge` (the default), `replace`, and `error`. Configuring a shared kubeconfig path is explicit opt-in and may update that file's `current-context`.

## Use the integration

Selecting the linked identity provisions the kubeconfig and contributes `KUBECONFIG` and `KUBE_CONFIG_PATH` to the child environment:

```shell
atmos auth exec --identity example-deployer -- kubectl get namespaces
atmos helm plan example-release --stack example-stack --identity example-deployer
```

The same composed identity environment is available to native Helm, Kubernetes, Helmfile, kubectl subprocesses, and workflows.

For an opt-in Helm safety guard, set `require_identity: true` and select a default identity in the GKE component's `auth` block:

```yaml
components:
  helm:
    example-release:
      auth:
        require_identity: true
        identities:
          example-deployer:
            default: true
        integrations:
          example-gke:
            kind: gcp/gke
            via:
              identity: example-deployer
            spec:
              cluster:
                name: example-cluster
                project_id: example-project
                location: us-central1
```

With the guard enabled, Atmos resolves the component default even when `--identity` is omitted, refuses the mutation if no GKE endpoint was provisioned, and verifies that the effective Kubernetes REST endpoint matches the endpoint returned by GKE. The flag is off by default and does not change existing EKS or AKS behavior.

If you use a custom blocking Helm hook instead, set `on_failure: fail`; command hooks otherwise warn and allow execution to continue. Hooks run from the component directory, so resolve repository scripts to an absolute path rather than assuming the repository root is the working directory.

## How it works

Atmos calls the GKE API for `projects/{project}/locations/{location}/clusters/{name}` using the resolved identity's access token. It writes only the Kubernetes API endpoint, cluster CA, context, and this exec plugin to kubeconfig:

```shell
atmos gcp gke token --identity example-deployer
```

Kubernetes invokes the command whenever it needs a fresh credential. The access token and expiration are returned as Kubernetes `ExecCredential` JSON and are never persisted in kubeconfig.

Cluster discovery is deduplicated per process using the cluster and all kubeconfig output settings. Bulk commands that target the same integration therefore make one describe request, while distinct paths, aliases, update modes, or identities are provisioned separately.

GKE is an Auth integration rather than a Kubernetes identity because the GCP identity remains the source credential and the kubeconfig is derived client configuration. Kubernetes RBAC remains independently required after Google authenticates the caller.

This integration and its endpoint guard cover GKE only. Other cluster types continue to use their existing effective kubeconfig behavior.

## Permissions

The identity must be able to describe the cluster, normally with `container.clusters.get`, and its provider must be able to obtain or refresh a GCP OAuth2 access token. Kubernetes roles and role bindings still determine what that identity may do inside the cluster.

## See also

- [`atmos gcp gke token`](token) — Exec credential command
- [Atmos Auth](/cli/configuration/auth) — Providers, identities, and integrations

---

## atmos gcp gke token

Resolve or refresh an Atmos GCP identity and emit its short-lived access token as Kubernetes `ExecCredential` JSON.

## Usage

```shell
atmos gcp gke token [--identity ]
```

## Examples

```shell
atmos gcp gke token --identity example-deployer
```

The generated GKE kubeconfig invokes this command automatically. It writes only JSON to stdout; diagnostics go to stderr. The token is not written to kubeconfig or included in error messages.

## Flags

- **`--identity`, `-i`**
  The Atmos GCP identity to resolve. If omitted, Atmos uses 
  `ATMOS_IDENTITY`
   or the only configured identity.

## Output

```json
{
  "apiVersion": "client.authentication.k8s.io/v1beta1",
  "kind": "ExecCredential",
  "status": {
    "expirationTimestamp": "2026-08-07T12:30:00Z",
    "token": "example-redacted-token"
  }
}
```

The command uses the existing Auth manager, including valid cached credentials and provider refresh when required. It suppresses linked integration auto-provisioning during token resolution so the GKE integration cannot recursively rewrite its own kubeconfig.

---

## atmos gcp

Commands for working with Google Cloud services through Atmos Auth.

## Usage

```shell
atmos gcp  [flags]
```

## Examples

```shell
atmos gcp gke token --identity example-deployer
```

## Subcommands

---

## atmos git clean

Remove workdirs for repositories configured under `git.repositories`. Clean only accepts configured repository names, verifies the target is a Git worktree whose `origin` matches the configured repository URI, and refuses dangerous filesystem targets such as roots, the current project, parent directories, symlinks, and invalid XDG cache roots. Clean workdirs do not require `--force`; dirty workdirs require `--force` before local changes are discarded.

## Usage

```shell
atmos git clean [name] [flags]
```

Clean a named managed repository:

```shell
atmos git clean deploy --dry-run
atmos git clean deploy
```

When exactly one repository is configured, the name can be omitted:

```shell
atmos git clean --dry-run
atmos git clean
```

Clean every configured repository workdir:

```shell
atmos git clean --all
```

## Safety

`atmos git clean` treats `git.repositories..workdir` as a managed clone location, not as arbitrary deletion input. Before deleting, Atmos:

- Resolves the configured or automatic XDG workdir.
- Refuses filesystem roots, volume roots, the current project directory, and parent directories of the current project.
- Refuses symlinks and non-directory targets.
- Requires the target to be a Git worktree.
- Requires `remote.origin.url` to match the configured repository `uri`.
- For automatic XDG workdirs, rejects dangerous cache roots from `ATMOS_XDG_CACHE_HOME` or `XDG_CACHE_HOME`, including relative paths and project/root directories.
- Requires `--force` when the workdir has uncommitted changes.

Missing workdirs are treated as already clean.

## Flags

- **`--all` (optional)**

  Clean every repository configured under `git.repositories`. Mutually exclusive with a positional repository name.
  Environment variable: `ATMOS_GIT_CLEAN_ALL`
- **`--force` / `-f` (optional)**

  Delete a dirty workdir after safety checks pass. Clean workdirs do not require `--force`.
  Environment variable: `ATMOS_GIT_CLEAN_FORCE`
- **`--dry-run` / `-n` (optional)**

  Report what would be deleted without deleting it.
  Environment variable: `ATMOS_GIT_DRY_RUN`

## Related

- [`atmos git clone`](/cli/commands/git/clone) — clone or reconcile managed repositories
- [`atmos git status`](/cli/commands/git/status) — inspect managed repository status
- [`atmos git list`](/cli/commands/git/list) — list configured repositories and their workdirs

---

## atmos git clone

Clone a named repository configured under [`git.repositories`](/cli/configuration/git), the single configured repository when only one exists, an ad hoc URI, or — in CI — the current repository as a replacement for `actions/checkout`. Clone is a _reconcile_ operation: if the destination already exists, Atmos fetches and fast-forwards instead of failing, so restored CI caches and re-runs are always safe.

## Usage

```shell
atmos git clone [name-or-uri] [flags] [-- ]
```

Configure repositories once in `atmos.yaml`, then clone them by name:

```yaml
git:
  repositories:
    flux-deploy:
      uri: https://github.com/acme/flux-deploy.git
      branch: main
      clone:
        depth: 1
        single_branch: true
```

```shell
atmos git clone flux-deploy
```

## Examples

```shell
# Clone a named managed repository (destination: automatic XDG workdir)
atmos git clone flux-deploy

# Clone the single configured repository when only one exists
atmos git clone

# Clone/reconcile every repository configured under git.repositories
atmos git clone --all

# Clone an ad hoc HTTPS URI (destination: /, like plain git clone)
atmos git clone https://github.com/acme/repo.git

# SCP-style URI
atmos git clone git@github.com:acme/repo.git

# go-getter style URI — same syntax used in vendoring and `source` configs
atmos git clone "git::https://github.com/acme/repo.git?ref=main&depth=1"

# Shallow, single-branch clone into an explicit destination
atmos git clone flux-deploy --depth=1 --single-branch --workdir=./deploy

# In CI (ci.enabled: true): check out the current repository into the
# working directory — replaces actions/checkout
atmos git clone

# Pass native arguments to the underlying git clone invocation
atmos git clone flux-deploy -- --no-tags
```

## Clone Destinations

The destination depends on the argument form:

| Clone form | Destination |
| --- | --- |
| Named managed repository (`atmos git clone flux-deploy`) | Automatic XDG workdir (`$XDG_CACHE_HOME/atmos/git/repositories/`), captured by the [native CI cache](/cli/configuration/ci) |
| Single configured repository (`atmos git clone` with exactly one repository outside CI) | Automatic XDG workdir (`$XDG_CACHE_HOME/atmos/git/repositories/`) |
| No-arg CI checkout replacement (`atmos git clone` with CI checkout enabled and detected CI metadata) | The **working directory** (e.g., `GITHUB_WORKSPACE`), exactly like `actions/checkout` |
| Ad hoc URI (`atmos git clone https://...`) | `/`, like plain `git clone` |

`--workdir=` overrides the destination in all forms.

## CI Checkout Replacement

When invoked with no argument in CI, `atmos git clone` infers the repository and ref from CI provider metadata. This requires:

1. CI checkout enabled by `ci.enabled: true`, `--ci`, or `ATMOS_CI=true`.
2. A detected CI provider (for example, GitHub Actions).
3. Repository metadata supplied by the provider.

`--ci` and `ATMOS_CI=true` are useful before the repository is checked out, when its
`atmos.yaml` is not available yet. `--ci=false` or `ATMOS_CI=false` explicitly opts out of the
CI checkout path and uses normal no-argument repository resolution instead. The command-line flag
takes precedence over `ATMOS_CI`.

Outside CI, no-arg clone reconciles the single configured repository when exactly one repository exists under `git.repositories`. If zero or multiple repositories are configured, pass a repository name, URI, or `--all`.

### Fork-PR safety gate

Because `atmos git clone` replaces `actions/checkout`, it inherits the same ["pwn request"](https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/) risk: cloning untrusted fork code in a job that holds the base repository's secrets. Mirroring `actions/checkout` v7, Atmos **refuses by default** to clone fork content under the elevated `pull_request_target` and `workflow_run` events.

The gate engages only on the dangerous combination — an elevated event **and** a fork-targeting clone:

- an explicit `--branch` / ref override that is a pull-request ref (e.g. `refs/pull//merge` or `refs/pull//head`), or
- an ad hoc clone URI whose `owner/repo` differs from the base `GITHUB_REPOSITORY`.

The safe no-arg checkout (base repository at its base ref) is never gated, and non-elevated events (`pull_request`, `push`, `merge_group`) are unaffected.

**Recommended:** use a `pull_request` workflow (which withholds fork secrets) for clone-and-plan of fork contributions; reserve `pull_request_target` / `workflow_run` for trusted, secret-free steps.

To bypass the gate deliberately, set the intentionally named `--allow-unsafe-fork` flag, `ATMOS_ALLOW_UNSAFE_FORK_EXECUTION=true`, or `ci.allow_unsafe_fork_execution: true` in `atmos.yaml`.

:::warning Fetch depth and `atmos describe affected`

Affected detection requires merge-base history. If [`atmos describe affected`](/cli/commands/describe/affected) runs against the clone, use `--depth=0` (full history) or a depth large enough to include the merge base — the same guidance that applies to `fetch-depth` with `actions/checkout`.

:::

## Arguments

- **`name-or-uri` (optional)**

  A repository name configured under `git.repositories`, or a Git URI. Supported URI forms:
  - HTTPS: `https://github.com/acme/repo.git`
  - SCP-style: `git@github.com:acme/repo.git`
  - go-getter style: `git::https://github.com/acme/repo.git?ref=main&depth=1` — the `git::` prefix is stripped, `?ref=` maps to the branch/ref and `?depth=` to clone depth; unknown query parameters are rejected
  When omitted: with CI checkout enabled (`ci.enabled: true`, `--ci`, or `ATMOS_CI=true`) and a detected CI provider, performs the current-repository checkout; otherwise, reconciles the single configured repository when exactly one entry exists under [`git.repositories`](/cli/configuration/git). When zero or multiple repositories are configured, pass a repository name, a URI, or `--all`.

## Flags

- **`--all` (optional)**

  Clone/reconcile every repository configured under `git.repositories`, concurrently. Every repository is attempted; failures are aggregated and reported at the end. Mutually exclusive with a positional argument.
  Environment variable: `ATMOS_GIT_CLONE_ALL`
- **`--ci` (optional)**

  Explicitly enable the current-repository CI checkout for a no-argument clone. Set `--ci=false` to opt out, even when CI is detected or `ci.enabled` is true. This selector does not change named, URI, or `--all` clones.
  Environment variable: `ATMOS_CI`
- **`--repo-uri` (optional)**

  Remote repository URI (overrides the configured URI).
  Environment variable: `ATMOS_GIT_REPO_URI`
- **`--branch` / `-b` (optional)**

  Branch to clone. Precedence: flag > `?ref=` query parameter > repository config > remote default branch.
  Environment variable: `ATMOS_GIT_BRANCH`
- **`--remote` (optional)**

  Remote name. Default: `origin`.
  Environment variable: `ATMOS_GIT_REMOTE`
- **`--workdir` (optional)**

  Override the destination directory for all clone forms.
  Environment variable: `ATMOS_GIT_WORKDIR`
- **`--depth` (optional)**

  Shallow clone depth. `0` (the default) means full history. Precedence: flag > `?depth=` query parameter > `clone.depth` config > default.
  Environment variable: `ATMOS_GIT_DEPTH`
- **`--filter` (optional)**

  Partial-clone filter spec (e.g., `blob:none`), matching Git's own `--filter` flag.
  Environment variable: `ATMOS_GIT_FILTER`
- **`--single-branch` (optional)**

  Limit the clone to the specified branch.
  Environment variable: `ATMOS_GIT_SINGLE_BRANCH`
- **`--submodules` (optional)**

  Initialize submodules after clone. Off by default.
  Environment variable: `ATMOS_GIT_SUBMODULES`
- **`--allow-unsafe-fork` (optional)**

  Opt out of the [fork-PR safety gate](#fork-pr-safety-gate) and allow cloning untrusted fork content in `pull_request_target` / `workflow_run` events. **Unsafe** — only set this when a fork-facing workflow has a documented reason to bypass the gate. Off by default.
  Environment variable: `ATMOS_ALLOW_UNSAFE_FORK_EXECUTION`
- **`--identity` (optional)**

  Atmos Auth identity to use for this operation (global flag). Overrides the repository's `auth.identity`.
  Environment variable: `ATMOS_IDENTITY`

## Native Git Arguments

Arguments after `--` are passed verbatim to the underlying `git clone` invocation. They apply only when a fresh clone runs; reconciling an existing workdir (fetch + fast-forward) does not invoke `git clone`.

```shell
atmos git clone flux-deploy -- --no-tags
```

## Related

- [`atmos git init`](/cli/commands/git/init) — initialize a repository whose remote has no content yet
- [`atmos git pull`](/cli/commands/git/pull) — fast-forward an existing workdir
- [`atmos git list`](/cli/commands/git/list) — list configured repositories and their workdirs
- [Git Configuration](/cli/configuration/git) — repository fields, defaults, and the native CI lifecycle

---

## atmos git commit

Stage paths and create a commit in a repository configured under [`git.repositories`](/cli/configuration/git), or in any local path. Commits are path-scoped and safe by default: when `--path` is provided, only those paths are staged, and the commit **fails** if unrelated dirty files exist outside the managed paths — Atmos never sweeps up files it wasn't told to commit.

## Usage

```shell
atmos git commit  --message= [flags]
```

With a configured repository (commit signing and author come from the repository config):

```yaml
git:
  repositories:
    flux-deploy:
      uri: https://github.com/acme/flux-deploy.git
      commit:
        signing: auto            # auto | always | never
        author:
          name: atmos[bot]
          email: atmos-bot@acme.com
```

```shell
atmos git commit flux-deploy --message="Render argocd for prod" --path=clusters/prod
```

## Examples

```shell
# Commit specific paths in a managed repository
atmos git commit flux-deploy --message="Render argocd for prod" --path=clusters/prod/argocd

# Multiple paths (repeatable or comma-separated)
atmos git commit flux-deploy --message="Update clusters" --path=clusters/prod --path=clusters/staging

# Commit in a repository at a local path
atmos git commit ./deployments --message="Update generated artifacts"

# Force GPG signing for this commit
atmos git commit flux-deploy --message="Signed release manifest" --sign

# Preview what would be staged and committed, without committing
atmos git commit flux-deploy --dry-run
```

## Behavior

- **Path-scoped staging:** with `--path`, only the listed repo-relative paths are staged. Dirty files outside the managed paths fail the commit with a list of the offending files.
- **Path validation:** every path must resolve inside the repository worktree; path traversal out of the worktree is an error.
- **Clean no-op:** when there is nothing to commit, the command exits successfully without creating an empty commit.
- **Commit author:** in environments with no `user.name`/`user.email` (typical CI runners), the repository's `commit.author` is passed per invocation without mutating Git config. Locally, your own Git config wins — when Git already resolves an author, Atmos passes nothing.
- **Signing:** the repository's `commit.signing` mode applies (`auto` by default — Git config decides); `--sign` and `--no-sign` override it per invocation.

## Arguments

- **`name-or-path` (required)**

  A repository name configured under `git.repositories`, or a filesystem path to an existing Git working tree.

## Flags

- **`--message` / `-m` (required, except with `--dry-run`)**

  Commit message.
  Environment variable: `ATMOS_GIT_MESSAGE`
- **`--path` (optional)**

  Stage only these repo-relative paths. Repeatable (`--path=a --path=b`) or comma-separated (`--path=a,b`). When omitted, all changes in the worktree are staged.
  Environment variable: `ATMOS_GIT_COMMIT_PATH`
- **`--sign` (optional)**

  Sign the commit with GPG (passes `-S` to `git commit`). Mutually exclusive with `--no-sign`.
  Environment variable: `ATMOS_GIT_SIGN`
- **`--no-sign` (optional)**

  Disable GPG signing (passes `--no-gpg-sign` to `git commit`). Mutually exclusive with `--sign`.
  Environment variable: `ATMOS_GIT_NO_SIGN`
- **`--dry-run` / `-n` (optional)**

  Report exactly what would be staged and committed without performing the commit.
  Environment variable: `ATMOS_GIT_DRY_RUN`
- **`--identity` (optional)**

  Atmos Auth identity to use for this operation (global flag). Overrides the repository's `auth.identity`.
  Environment variable: `ATMOS_IDENTITY`

## Related

- [`atmos git diff`](/cli/commands/git/diff) — preview the changes before committing
- [`atmos git push`](/cli/commands/git/push) — publish the commit to the remote
- [`kind: git` hooks](/stacks/hooks#kind-git) — commit and push automatically on lifecycle events
- [Git Configuration](/cli/configuration/git) — `commit.signing`, `commit.author`, and defaults

---

## atmos git diff

Show uncommitted changes in a repository configured under [`git.repositories`](/cli/configuration/git), or in any local path. This is the read-before-write step of GitOps publishing — the GitOps analog of `terraform plan`: see exactly what _would_ be committed to a deployment repository without committing anything.

## Usage

```shell
atmos git diff  [flags]
```

With a configured deployment repository:

```yaml
git:
  repositories:
    flux-deploy:
      uri: https://github.com/acme/flux-deploy.git
```

```shell
atmos git diff flux-deploy --path=clusters/prod
```

The unified diff is written to stdout (pipeable). Untracked files are reported separately on stderr so they don't corrupt piped diff output.

## Examples

```shell
# Show all uncommitted changes in a managed repository
atmos git diff flux-deploy

# Scope the diff to specific repo-relative paths
atmos git diff flux-deploy --path=clusters/prod --path=clusters/staging

# Diff a repository at a local path
atmos git diff ./deployments

# Pull-request preview in CI: render manifests, then show what would change
atmos git diff flux-deploy --path=clusters/prod > preview.diff
```

## Arguments

- **`name-or-path` (required)**

  A repository name configured under `git.repositories`, or a filesystem path to an existing Git working tree. URIs are not accepted — diff operates on a local workdir.

## Flags

- **`--path` (optional)**

  Limit the diff to these repo-relative paths. Repeatable (`--path=a --path=b`) or comma-separated (`--path=a,b`).
  Environment variable: `ATMOS_GIT_DIFF_PATH`
- **`--identity` (optional)**

  Atmos Auth identity to use for this operation (global flag). Overrides the repository's `auth.identity`.
  Environment variable: `ATMOS_IDENTITY`

## Related

- [`atmos git commit`](/cli/commands/git/commit) — commit the changes shown by diff
- [`atmos git status`](/cli/commands/git/status) — summary view of the working tree
- [Git Configuration](/cli/configuration/git) — repository fields and defaults

---

## atmos git hooks

Install, uninstall, and run local Git hooks (`pre-commit`, `commit-msg`, `pre-push`, ...) configured under [`git.hooks`](/cli/configuration/git#local-git-hooks) in `atmos.yaml`. Atmos writes thin `.git/hooks/*` shims that delegate to `atmos git hooks run`, so your hooks execute through Atmos workflows and custom commands — with the toolchain, environment, and identity behavior you already have — instead of through Husky-style shell scripts.

Configure hooks once in `atmos.yaml`:

```yaml
git:
  hooks:
    pre-commit:
      command: atmos workflow pre-commit
    commit-msg:
      command: atmos workflow commit-msg -- "$1"
```

Then install the shims:

```shell
atmos git hooks install
```

:::note Local Git hooks vs. lifecycle hooks

Local Git hooks are triggered by **Git itself** (`git commit`, `git push`, ...) in the current repository. They are separate from Atmos lifecycle hooks (the [`git` hook kind](/stacks/hooks#kind-git)), which trigger on Atmos events like `after.terraform.apply`.

:::

## Subcommands

## Related

- [Git Configuration](/cli/configuration/git#local-git-hooks) — the `git.hooks` section
- [Workflows](/cli/configuration/workflows) — what hook commands typically invoke
- [`kind: git` lifecycle hooks](/stacks/hooks#kind-git) — event-bound Git publishing

---

## atmos git hooks install

Write local `.git/hooks/` shim scripts for the hooks configured under [`git.hooks`](/cli/configuration/git#local-git-hooks). Each shim is a one-liner that delegates to `atmos git hooks run`, keeping all hook logic in `atmos.yaml` where it is versioned, shared, and executed through Atmos workflows and custom commands.

## Usage

```shell
atmos git hooks install [hook-name...] [flags]
```

Given this configuration:

```yaml
git:
  hooks:
    pre-commit:
      command: atmos workflow pre-commit
    commit-msg:
      command: atmos workflow commit-msg -- "$1"
```

`install` writes a shim like this for each hook:

```sh
#!/bin/sh
exec atmos git hooks run pre-commit "$@"
```

## Examples

```shell
# Install all hooks configured under git.hooks
atmos git hooks install

# Install only specific hooks
atmos git hooks install pre-commit commit-msg

# Overwrite existing hooks
atmos git hooks install --force
```

## Behavior

- Installs **all** configured hooks when no hook names are provided; only the requested hooks otherwise.
- **Refuses to overwrite** an existing hook unless `--force` is passed.
- Marks generated shim scripts executable.
- Resolves the hooks directory through Git (`git rev-parse --git-path hooks`), so linked worktrees — where `.git` is a file and hooks live in the common dir — work correctly.
- Does **not** manage `core.hooksPath`, but warns when it is set (for example, by Husky), because Git ignores `.git/hooks/*` shims in that case.

:::warning PATH and GUI Git clients

Shims invoke `atmos` from `PATH`. GUI Git clients (IDEs, Sourcetree) may run hooks with a different `PATH` than your shell. If hooks fail only from a GUI client, ensure the directory containing `atmos` is on the `PATH` the client uses — this is the most common failure mode for hook managers of this style.

:::

## Arguments

- **`hook-name...` (optional)**

  One or more Git hook names (`pre-commit`, `commit-msg`, `pre-push`, ...) to install. When omitted, all hooks configured under `git.hooks` are installed.

## Flags

- **`--force` / `-f` (optional)**

  Overwrite existing hook scripts. Without it, install refuses to replace a hook that already exists.
  Environment variable: `ATMOS_GIT_HOOKS_FORCE`

## Related

- [`atmos git hooks run`](/cli/commands/git/hooks/run) — what the generated shims execute
- [`atmos git hooks uninstall`](/cli/commands/git/hooks/uninstall) — remove Atmos-generated shims
- [Git Configuration](/cli/configuration/git#local-git-hooks) — the `git.hooks` section

---

## atmos git hooks run

Execute the command configured for a Git hook under [`git.hooks`](/cli/configuration/git#local-git-hooks). This is what the `.git/hooks/*` shims installed by [`atmos git hooks install`](/cli/commands/git/hooks/install) invoke, but it can also be run directly — useful for testing a hook without triggering it through Git.

## Usage

```shell
atmos git hooks run  [args...]
```

Given this configuration:

```yaml
git:
  hooks:
    pre-commit:
      command: atmos workflow pre-commit
    commit-msg:
      command: atmos workflow commit-msg -- "$1"
```

Git invokes the installed shim, which runs:

```shell
atmos git hooks run commit-msg .git/COMMIT_EDITMSG
```

## Examples

```shell
# Test a hook without committing
atmos git hooks run pre-commit

# Run a hook that takes arguments (commit-msg receives the message file path)
atmos git hooks run commit-msg .git/COMMIT_EDITMSG
```

## Behavior

- Loads the Atmos configuration for the current repository and resolves `git.hooks..command`.
- Executes the configured command through the shared workflow/command dispatch, inheriting toolchain `PATH`, environment, and identity behavior.
- Forwards hook arguments **and stdin** — hooks such as `pre-push` and `pre-receive` receive their input on stdin, not argv.
- Propagates the command's exit code, so a failing hook command blocks the Git operation exactly as a hand-written hook would.
- Fails with a clear error listing the configured hooks when `` is not configured under `git.hooks`.

## Arguments

- **`hook-name` (required)**

  The Git hook to run, matching a key under `git.hooks` (`pre-commit`, `commit-msg`, `pre-push`, ...).
- **`args...` (optional)**

  Arguments forwarded verbatim to the configured command — exactly what Git passes to the hook (for example, the commit message file path for `commit-msg`). Arguments are passed through even when they look like flags.

## Related

- [`atmos git hooks install`](/cli/commands/git/hooks/install) — install the shims that call this command
- [Workflows](/cli/configuration/workflows) — what hook commands typically invoke
- [Git Configuration](/cli/configuration/git#local-git-hooks) — the `git.hooks` section

---

## atmos git hooks uninstall

Remove local `.git/hooks/*` shims previously installed by [`atmos git hooks install`](/cli/commands/git/hooks/install). Uninstall only ever removes shims that Atmos generated (identified by shim content) — user-authored hook scripts are never touched.

## Usage

```shell
atmos git hooks uninstall [hook-name...]
```

## Examples

```shell
# Remove all Atmos-generated hook shims
atmos git hooks uninstall

# Remove specific hooks only
atmos git hooks uninstall pre-commit commit-msg
```

## Behavior

- Removes **only** shims generated by Atmos. A hook script that was written or modified by the user is left in place.
- Removing a shim does not change the hook configuration under `git.hooks` in `atmos.yaml` — reinstall at any time with `atmos git hooks install`.

## Arguments

- **`hook-name...` (optional)**

  One or more Git hook names to uninstall. When omitted, all Atmos-generated shims are removed.

## Related

- [`atmos git hooks install`](/cli/commands/git/hooks/install) — install the shims
- [Git Configuration](/cli/configuration/git#local-git-hooks) — the `git.hooks` section

---

## atmos git init

Initialize a named repository configured under [`git.repositories`](/cli/configuration/git) whose remote has no content yet — the inverse of [`atmos git clone`](/cli/commands/git/clone). Atmos creates the workdir, runs `git init` on the configured branch, and wires the configured remote so [`atmos git commit`](/cli/commands/git/commit) and [`atmos git push`](/cli/commands/git/push) work immediately. With `--from=`, the new repository is seeded from a template or migrated from an existing repository.

## Usage

```shell
atmos git init [name] [flags] [-- ]
```

Configure the repository once in `atmos.yaml`, then initialize it by name:

```yaml
git:
  repositories:
    flux-deploy:
      uri: https://github.com/acme/flux-deploy.git
      branch: main
```

```shell
atmos git init flux-deploy
```

The seed source can also be configured per repository under `init`, so a plain
`atmos git init flux-deploy` reproduces the same bootstrap every time:

```yaml
git:
  repositories:
    flux-deploy:
      uri: https://github.com/acme/flux-deploy.git
      branch: main
      init:
        from: https://github.com/acme/flux-template.git
        keep_history: false
```

The `--from` and `--keep-history` flags override these configured defaults.

## Examples

```shell
# Initialize an empty repository on the configured branch with origin wired up
atmos git init flux-deploy

# Initialize the single configured repository when only one exists
atmos git init

# Seed from a template repository (fresh history: one initial commit,
# no link to the template remains)
atmos git init flux-deploy --from=https://github.com/acme/flux-template.git

# Migrate an existing repository: keep its full history and keep it pullable
# as the 'upstream' remote
atmos git init flux-deploy --from=https://github.com/acme/old-deploy.git --keep-history

# Re-running init is idempotent: reconciles an existing repo in place
atmos git init flux-deploy

# Force a clean re-create from scratch (destructive: deletes the workdir first)
atmos git init flux-deploy --force

# Preview without touching the filesystem
atmos git init flux-deploy --from=https://github.com/acme/flux-template.git --dry-run

# Pass native arguments to the underlying git invocation
atmos git init flux-deploy -- --template=/path/to/git-template
```

## Seeding Modes

| Mode | History | Remotes |
| --- | --- | --- |
| No `--from` | New, empty (no commits) | `origin` → configured `uri` |
| `--from=` (default) | Single fresh initial commit (`Initialize from `); source history discarded | `origin` → configured `uri` |
| `--from= --keep-history` | Source's full history preserved | `origin` → configured `uri`, `upstream` → source `uri` (pull future template updates with `git pull upstream`) |

In fresh mode the configured `branch` names the **new** history (`git init -b `); the source's default branch supplies the content. In keep-history mode the configured `branch` must exist in the source repository, because the history is the source's.

The initial commit created in fresh mode honors the repository's [`commit.signing`](/cli/configuration/git) mode and `commit.author` override.

:::note Idempotent by default; `--force` re-creates
Re-running `atmos git init` is safe: when the resolved workdir is **already an initialized Git repository**, init reconciles it in place — it re-runs `git init` (idempotent) and re-points the configured remote, without re-seeding or erroring. This holds whether or not `init.from` is configured (the repository already exists, so there is nothing to seed).

Any **other non-empty directory** (not a Git repository) is refused, so init never clobbers unrelated content.

Pass `--force` to **delete the existing workdir and re-initialize from scratch** — this is destructive (uncommitted/unpushed local content is lost) and re-runs the full create or seed. Use [`--dry-run`](#flags) to preview, or [`atmos git clean`](/cli/commands/git/clean) to remove the workdir explicitly.
:::

## Arguments

- **`name` (optional)**

  A repository name configured under `git.repositories`. The repository's `uri` is required — it becomes the configured remote. When omitted, Atmos initializes the single configured repository when exactly one entry exists.

## Flags

- **`--from` (optional)**

  Seed the new repository's content from another repository URI (a template, or a repository being migrated). Overrides the repository's configured `init.from`.
  Environment variable: `ATMOS_GIT_FROM`
- **`--keep-history` (optional)**

  Keep the seed repository's full history and keep the source reachable as the `upstream` remote so future updates can be pulled. Requires a seed source from either `--from` or the repository's configured `init.from`. Also settable as `init.keep_history`.
  Environment variable: `ATMOS_GIT_KEEP_HISTORY`
- **`--branch` / `-b` (optional)**

  Initial branch name. Precedence: flag > repository config > Git's own `init.defaultBranch`.
  Environment variable: `ATMOS_GIT_BRANCH`
- **`--workdir` (optional)**

  Override the destination directory. Default: the repository's configured `workdir`, or the automatic XDG workdir (`$XDG_CACHE_HOME/atmos/git/repositories/`).
  Environment variable: `ATMOS_GIT_WORKDIR`
- **`--force` / `-f` (optional)**

  Delete the existing workdir and re-initialize from scratch. Destructive — uncommitted/unpushed local content is lost. Without `--force`, re-running init reconciles an existing repository in place (idempotent); use `--force` only when you want a clean re-create or re-seed.
  Environment variable: `ATMOS_GIT_FORCE`
- **`--dry-run` / `-n` (optional)**

  Report what would be done without initializing.
  Environment variable: `ATMOS_GIT_DRY_RUN`
- **`--identity` (optional)**

  Atmos Auth identity to use for this operation (global flag). Overrides the repository's `auth.identity`.
  Environment variable: `ATMOS_IDENTITY`

## Native Git Arguments

Arguments after `--` are passed verbatim to the underlying git invocation: `git init` for an empty init, or the `git clone` of the `--from` repository.

```shell
# Clone the template without tags
atmos git init flux-deploy --from=https://github.com/acme/flux-template.git -- --no-tags
```

## Related

- [`atmos git clone`](/cli/commands/git/clone) — clone or reconcile an existing repository
- [`atmos git commit`](/cli/commands/git/commit) — stage and commit changes
- [`atmos git push`](/cli/commands/git/push) — publish commits to the remote
- [Git Configuration](/cli/configuration/git) — repository fields and defaults

---

## atmos git list

List the repositories configured under [`git.repositories`](/cli/configuration/git) with their local clone state, URIs, providers, branches, and resolved workdirs. Built on the standard Atmos list rendering pipeline, with the same column, format, and filter behavior as other `atmos list` commands. Also available as `atmos list git-repositories`.

## Usage

```shell
atmos git list [flags]
```

With repositories configured in `atmos.yaml`:

```yaml
git:
  repositories:
    flux-deploy:
      uri: https://github.com/acme/flux-deploy.git
      branch: main

    generated-terraform:
      uri: https://github.com/acme/generated-terraform.git
```

```shell
atmos git list
atmos list git-repositories     # alias
```

## Examples

```shell
# List all configured repositories (default sort: name ascending).
# Default table output shows a green dot for clean clones, yellow for dirty
# clones, and gray for missing workdirs.
atmos git list

# Select columns
atmos git list --columns=name,uri,workdir

# Include status fields when using custom columns
atmos git list --check-status --columns=status,name,workdir

# Machine-readable output
atmos git list --format=json
atmos git list --format=csv --delimiter=";"

# Alias registered under atmos list
atmos list git-repositories --format=yaml
```

## Columns

- **`name`**
  Logical repository name (the key under 
  `git.repositories`
  ).
- **`uri`**
  Repository URI.
- **`provider`**
  Resolved provider (
  `cli`
  ).
- **`branch`**
  Configured branch, or 
  `(default)`
   when the remote default branch is used.
- **`workdir`**
  Resolved workdir — the automatic XDG location or the explicit 
  `workdir`
   override.
- **`status`**
  Colored table indicator for local clone state: green dot for 
  `cloned`
  , yellow dot for 
  `dirty`
  , and gray dot for 
  `missing`
  . In non-TTY output this resolves to the semantic status text.
- **`status_text`**
  `cloned`
  , 
  `missing`
  , or 
  `dirty`
  . Used by machine-readable default output and custom columns that need status text.

Columns can also be customized in `atmos.yaml` under `git.list.columns` — see [Git Configuration](/cli/configuration/git#list-output).

## Flags

- **`--check-status` (optional)**

  Probe each workdir on disk and populate status fields when using custom columns. Default table output already probes status to show the dot column. Probes run concurrently with a bounded worker pool.
- **`--columns` (optional)**

  Comma-separated list of columns to display.
  Environment variable: `ATMOS_GIT_LIST_COLUMNS`
- **`--format` (optional)**

  Output format: `table` (default), `json`, `yaml`, `csv`, or `tsv`.
  Environment variable: `ATMOS_GIT_LIST_FORMAT`
- **`--delimiter` (optional)**

  Field delimiter for `csv`/`tsv` output.
- **`--filter` (optional)**

  YQ expression to filter the listed repositories.
  Environment variable: `ATMOS_GIT_LIST_FILTER`

## Related

- [`atmos git status`](/cli/commands/git/status) — full porcelain status for one repository (shares the `--check-status` probe)
- [`atmos git clone`](/cli/commands/git/clone) — materialize a `missing` workdir
- [Git Configuration](/cli/configuration/git) — `git.repositories` and `git.list`

---

## atmos git pull

Pull the latest changes for a repository configured under [`git.repositories`](/cli/configuration/git), or for any local path. Pulls are **always fast-forward-only** — Atmos never creates merge commits or rebases on pull, so a diverged branch fails fast instead of producing surprise history.

## Usage

```shell
atmos git pull [name-or-path] [flags] [-- ]
```

With a configured repository:

```yaml
git:
  repositories:
    flux-deploy:
      uri: https://github.com/acme/flux-deploy.git
      branch: main
```

```shell
atmos git pull flux-deploy
```

## Examples

```shell
# Pull a named managed repository
atmos git pull flux-deploy

# Pull the single configured repository
atmos git pull

# Clone the single configured repository first when its workdir is missing
atmos git pull --clone

# Pull a repository at a local path
atmos git pull ./deployments

# Pull all configured repositories concurrently
atmos git pull --all

# Clone missing configured workdirs before pulling all repositories
atmos git pull --all --clone

# Pull a specific branch from a specific remote
atmos git pull flux-deploy --remote=origin --branch=main

# Pass native arguments to the underlying git pull invocation
atmos git pull flux-deploy -- --no-tags
```

## Argument Resolution

The positional argument is resolved as: configured repository **name** → **URI** (rejected — pull needs a local workdir) → **path**. To force path interpretation for an argument that collides with a repository name, use an explicit path prefix (`./deployments`).

## Arguments

- **`name-or-path` (required unless exactly one repository is configured, or `--all` is set)**

  A repository name configured under `git.repositories`, or a filesystem path to an existing Git working tree. When exactly one repository is configured, omitting this argument pulls that repository.

## Flags

- **`--all` (optional)**

  Fast-forward pull every repository configured under `git.repositories`, concurrently. Every repository is attempted; failures are aggregated and reported at the end. Mutually exclusive with a positional argument.
  Environment variable: `ATMOS_GIT_PULL_ALL`
- **`--clone` (optional)**

  Clone a configured repository when its workdir is missing. This flag only applies to configured repository names, including no-arg single-repository pull and `--all`; it is rejected for local path arguments.
  Environment variable: `ATMOS_GIT_PULL_CLONE`
- **`--branch` / `-b` (optional)**

  Branch to pull. Defaults to the repository's configured branch, or the current branch.
  Environment variable: `ATMOS_GIT_BRANCH`
- **`--remote` (optional)**

  Remote name. Default: `origin`.
  Environment variable: `ATMOS_GIT_REMOTE`
- **`--identity` (optional)**

  Atmos Auth identity to use for this operation (global flag). Overrides the repository's `auth.identity`.
  Environment variable: `ATMOS_IDENTITY`

## Native Git Arguments

Arguments after `--` are passed verbatim to the underlying `git pull --ff-only` invocation.

```shell
atmos git pull flux-deploy -- --no-tags
```

## Related

- [`atmos git clone`](/cli/commands/git/clone) — clone or reconcile a workdir
- [`atmos git status`](/cli/commands/git/status) — inspect the working tree before pulling
- [Git Configuration](/cli/configuration/git) — repository fields and defaults

---

## atmos git push

Push commits from a repository configured under [`git.repositories`](/cli/configuration/git), or from any local path, to its remote. Atmos **never force-pushes**. When a push is rejected because the remote moved (another job or human pushed first), Atmos automatically retries: `pull --ff-only`, then re-push, bounded by `push.retries` (default `3`).

## Usage

```shell
atmos git push  [flags] [-- ]
```

With a configured repository:

```yaml
git:
  repositories:
    flux-deploy:
      uri: https://github.com/acme/flux-deploy.git
      push:
        retries: 3        # bounded pull --ff-only + re-push loop on rejection
```

```shell
atmos git push flux-deploy
```

## Examples

```shell
# Push a managed repository to its configured remote/branch
atmos git push flux-deploy

# Push a repository at a local path
atmos git push ./deployments

# Push a specific branch to a specific remote
atmos git push flux-deploy --remote=origin --branch=main

# Report what would be pushed without pushing
atmos git push flux-deploy --dry-run

# Pass native arguments to the underlying git push invocation
atmos git push flux-deploy -- --follow-tags
```

## Push Contention

A rejected non-fast-forward push is the most common failure mode in GitOps publishing — multiple components, CI jobs, or humans pushing to the same deployment branch. Atmos handles it instead of surfacing a raw Git error:

1. On rejection, run `pull --ff-only`, then re-push.
2. Repeat up to `push.retries` times (default `3`; configurable per repository).
3. After exhaustion, fail with a clear error and hints (serialize publishers, or batch publishing).

## Arguments

- **`name-or-path` (required)**

  A repository name configured under `git.repositories`, or a filesystem path to an existing Git working tree. URIs are not accepted — push operates on a local workdir.

## Flags

- **`--branch` / `-b` (optional)**

  Branch to push. Defaults to the repository's configured branch, or the current branch.
  Environment variable: `ATMOS_GIT_BRANCH`
- **`--remote` (optional)**

  Remote name. Default: `origin`.
  Environment variable: `ATMOS_GIT_REMOTE`
- **`--dry-run` / `-n` (optional)**

  Report what would be pushed without pushing.
  Environment variable: `ATMOS_GIT_DRY_RUN`
- **`--identity` (optional)**

  Atmos Auth identity to use for this operation (global flag). Overrides the repository's `auth.identity`.
  Environment variable: `ATMOS_IDENTITY`

## Native Git Arguments

Arguments after `--` are passed verbatim to each underlying `git push` invocation (not to the `pull --ff-only` recovery between retries).

```shell
atmos git push flux-deploy -- --follow-tags
```

## Related

- [`atmos git commit`](/cli/commands/git/commit) — create the commit to push
- [`kind: git` hooks](/stacks/hooks#kind-git) — commit and push automatically on lifecycle events
- [Git Configuration](/cli/configuration/git) — `push.retries` and authentication resolution

---

## atmos git status

Report the working tree status of a repository configured under [`git.repositories`](/cli/configuration/git), or of any local path. Changed entries are written to stdout in porcelain format (one `CODE path` line per entry), so the output is pipeable; a clean tree is reported as a human-readable message on stderr.

## Usage

```shell
atmos git status [name-or-path] [flags]
```

With a configured repository:

```yaml
git:
  repositories:
    flux-deploy:
      uri: https://github.com/acme/flux-deploy.git
```

```shell
atmos git status flux-deploy
```

## Examples

```shell
# Status of a named managed repository
atmos git status flux-deploy

# Status of the single configured repository
atmos git status

# Status of a repository at a local path
atmos git status ./deployments

# Status of all configured repositories
atmos git status --all

# Pipe the porcelain output
atmos git status flux-deploy | grep '^ M'
```

## Arguments

- **`name-or-path` (required unless exactly one repository is configured, or `--all` is set)**

  A repository name configured under `git.repositories`, or a filesystem path to an existing Git working tree. When exactly one repository is configured, omitting this argument reports status for that repository. Use an explicit path prefix (`./deployments`) to force path interpretation when an argument collides with a repository name.

## Flags

- **`--all` (optional)**

  Report status for every repository configured under `git.repositories`, concurrently. Every repository is attempted; failures are aggregated and reported at the end. Mutually exclusive with a positional argument.
  Environment variable: `ATMOS_GIT_STATUS_ALL`
- **`--identity` (optional)**

  Atmos Auth identity to use for this operation (global flag). Overrides the repository's `auth.identity`.
  Environment variable: `ATMOS_IDENTITY`

## Related

- [`atmos git diff`](/cli/commands/git/diff) — show the actual content changes
- [`atmos git list`](/cli/commands/git/list) — list configured repositories (`--check-status` shares this status probe)
- [Git Configuration](/cli/configuration/git) — repository fields and defaults

---

## atmos git

`atmos git` is GitOps enablement for Atmos: it makes **automated Git commits easy, safe, and conventional** — the same way on your laptop and in CI, across every project. Treat Git repositories as artifact repositories: render or generate files, place them into a repository worktree, and publish them — committed and pushed automatically — from the CLI, from [hooks](/stacks/hooks#kind-git) (e.g. after `terraform apply`), or from native CI workflows. Initialize, clone, pull, inspect, diff, commit, push, and clean managed repositories by logical name, with consistent authentication, commit signing, and safety rules so no two pipelines reinvent their own brittle `git` scripting.

> ⚠️ Experimental

**Configure Git Repositories**

Define managed repositories, authentication, commit signing, and local Git hooks under the top-level `git` section of `atmos.yaml`.

Configuration Reference[Read more](/cli/configuration/git)

## Managed Repositories

Define repositories once under `git.repositories` in `atmos.yaml`, then refer to them everywhere by logical name:

```yaml
git:
  repositories:
    flux-deploy:
      uri: https://github.com/acme/flux-deploy.git
      auth:
        identity: platform-admin

    generated-terraform:
      uri: https://github.com/acme/generated-terraform.git
```

```shell
atmos git clone flux-deploy
atmos git status flux-deploy
atmos git commit flux-deploy --message="Update manifests" --path=clusters/prod
atmos git push flux-deploy
atmos git clean flux-deploy --dry-run
```

Repository names (`flux-deploy`, `generated-terraform`) are arbitrary, user-defined logical keys — not reserved values. Repository configuration follows standard Atmos deep-merge, so repositories can be defined in imported configuration and overridden per environment.

## How It Differs from Plain Git

Every `atmos git` command goes through the shared Atmos Git service, which adds:

- **Authentication via Atmos Auth** — repository `auth.identity` brings linked integrations (such as `github/sts`) along automatically; the ambient credential broker covers the zero-config CI case; your own credential helpers and SSH agent always continue to work.
- **Safety rules** — pulls are always fast-forward-only, force push is never performed, rejected pushes retry with a bounded `pull --rebase` + re-push loop, and path-scoped commits refuse to commit when unrelated dirty files are present.
- **Reconcile semantics** — `atmos git clone` is idempotent: it clones when the workdir is absent and fetches/fast-forwards when it already exists, which makes restored CI caches safe.
- **Automatic workdirs** — managed repositories clone into a deterministic location under the Atmos XDG cache root, so the [native CI cache](/cli/configuration/ci) captures them across runs for free.
- **Native escape hatch** — `init`, `clone`, `pull`, and `push` pass arguments after `--` verbatim to the underlying git invocation (e.g., `atmos git clone flux-deploy -- --no-tags`), so Atmos safety rules and uncommon git flags compose.

## Subcommands

## Related

- [Git Configuration](/cli/configuration/git) — `git.repositories`, `git.hooks`, and `git.list` in `atmos.yaml`
- [`kind: git` hooks](/stacks/hooks#kind-git) — publish artifacts on lifecycle events like `after.terraform.apply`
- [Authentication](/cli/configuration/auth) — identities and integrations used by `auth.identity`
- [CI Configuration](/cli/configuration/ci) — native CI detection used by no-arg `atmos git clone`

---

## atmos helm apply

Install or upgrade a Helm release (`helm upgrade --install`) for a component in
a stack, using values resolved from your stack configuration. With `--target`,
`apply` instead delivers the rendered manifests to a provision target such as a
Git deployment repository.

## Usage

```shell
atmos helm apply  --stack  [options]
```

Install or upgrade the release in the cluster:

```shell
atmos helm apply monitoring -s plat-ue2-dev
```

Upgrade an existing release with an explicit production readiness and recovery policy:

```shell
atmos helm apply monitoring -s plat-ue2-dev \
  --on-failure=rollback \
  --cleanup-on-failure \
  --wait=watcher \
  --wait-for-jobs \
  --timeout=30m \
  --history-max=10
```

Deliver rendered manifests to a provision target instead of the cluster:

```shell
atmos helm apply monitoring -s plat-ue2-dev --target deployment-repo
```

Apply all or affected Helm components in dependency order:

```shell
atmos helm apply --all -s plat-ue2-dev
atmos helm apply --affected --base origin/main
```

Apply components filtered by tags or labels (composes with `--all`/`--affected` to narrow the selected set further):

```shell
atmos helm apply --all --tags production,tier-1
atmos helm apply --affected --labels cost-center=platform
```

In CI, bulk applies selected with `--all` or `--affected` write one aggregate GitHub job summary
after the dependency graph completes. It lists component status and Helm release metadata in stable
stack/component order and includes details for failures.

While a cluster apply is running, Atmos reports the selected install or upgrade operation, effective
wait policy, timeout, and elapsed-time heartbeats. Bulk output includes the component and stack on
every line so concurrent or dependency-ordered operations remain distinguishable. Progress is written
through the masked UI stream; chart hook container logs are not streamed automatically.

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--target` (optional)**
  Provision target to deliver to (e.g. a Git deployment repository). Defaults to 
  `provision.default`
  , otherwise the cluster.
- **`--dependency-update` (optional)**
  Fetch declared chart dependencies when they are missing. This may access dependency repositories and update the chart's 
  charts/
   directory and lock file.
- **`--dry-run` (optional)**
  Preview the install or upgrade without persisting release state or creating Kubernetes resources.
- **`--on-failure` (optional)**
  Failure action for the selected operation: 
  `uninstall`
   or 
  `keep`
   for install; 
  `rollback`
   or 
  `keep`
   for upgrade. The explicit flag overrides stack configuration for this invocation.
- **`--cleanup-on-failure` (optional)**
  Remove resources newly created during a failed upgrade, independently of rollback. Fails if the selected operation is install.
- **`--wait[=strategy]` (optional)**
  Use 
  `watcher`
  , 
  `hookOnly`
  , or 
  `legacy`
  . Passing 
  `--wait`
   without a value selects 
  `watcher`
  . Boolean values remain accepted temporarily but are deprecated.
- **`--wait-for-jobs` (optional)**
  Wait for ordinary Jobs in the release manifest. Requires 
  `watcher`
   or 
  `legacy`
  .
- **`--timeout` (optional)**
  Helm release-operation timeout, such as 
  `10m`
   or 
  `1h`
  . 
  `0s`
   is explicitly unbounded.
- **`--history-max` (optional)**
  Maximum retained release revisions for an upgrade. Defaults to 
  `10`
  ; 
  `0`
   means unlimited. Fails if the selected operation is install.
- **`--no-hooks` (optional)**
  Disable Helm chart hooks. Atmos lifecycle hooks are unaffected.
- **`--skip-crds` (optional)**
  Skip CRD installation on a first install. Fails if the selected operation is upgrade.
- **`--all` (optional)**
  Apply all Helm components in dependency order.
- **`--affected` (optional)**
  Apply affected Helm components and their dependencies.
- **`--include-dependents` (optional)**
  With 
  `--affected`
  , include dependent Helm components.
- **`--tags` (optional)**
  Filter by tags (comma-separated, matches any): 
  `--tags=production,tier-1`
  . Composes with 
  `--all`
  /
  `--affected`
   to narrow the selected set further; cannot be combined with a single component argument.
- **`--labels` (optional)**
  Filter by labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--labels=cost-center=platform,compliance=sox`
  . Composes with 
  `--all`
  /
  `--affected`
  /
  `--tags`
  ; cannot be combined with a single component argument.

Lifecycle flags apply only to direct Kubernetes delivery. Combining an explicit
lifecycle flag with an external `--target` fails instead of implying that Atmos
waited for or rolled back a GitOps deployment.

---

## atmos helm delete

Uninstall a Helm release (`helm uninstall`) for a component in a stack.
Deleting a release that does not exist is a no-op.

Atmos reports the effective delete wait policy when the operation starts, emits elapsed-time
heartbeats while Helm is waiting, and prints a terminal success or failure status. Bulk output
identifies the component and stack on every line.

## Usage

```shell
atmos helm delete  --stack  [options]
atmos helm delete monitoring -s plat-ue2-dev

# Preview without removing release state or Kubernetes resources
atmos helm delete monitoring -s plat-ue2-dev --dry-run --wait=watcher --timeout=10m

# Delete components filtered by tags or labels (composes with --all/--affected)
atmos helm delete --all --tags production,tier-1
```

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--all` / `--affected` / `--include-dependents` (optional)**
  Process multiple Helm components in reverse dependency order, deleting dependents before their dependencies.
- **`--tags` / `--labels` (optional)**
  Filter by tags (comma-separated, matches any) or labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--tags=production,tier-1`
  , 
  `--labels=cost-center=platform`
  . Compose with 
  `--all`
  /
  `--affected`
   to narrow the selected set further; cannot be combined with a single component argument.
- **`--wait[=strategy]` (optional)**
  Use 
  `watcher`
  , 
  `hookOnly`
  , or 
  `legacy`
   while deleting. Passing 
  `--wait`
   without a value selects 
  `watcher`
  . Boolean values remain accepted temporarily but are deprecated.
- **`--timeout` (optional)**
  Helm uninstall timeout, such as 
  `10m`
  . 
  `0s`
   is explicitly unbounded.
- **`--no-hooks` (optional)**
  Disable Helm chart uninstall hooks. Atmos lifecycle hooks are unaffected.
- **`--dry-run` (optional)**
  Preview the uninstall without deleting release history or Kubernetes resources.

---

## atmos helm deploy

Deploy a Helm release for a component in a stack. `deploy` is an alias for
[`apply`](/cli/commands/helm/apply): it installs or upgrades the release
(`helm upgrade --install`), or delivers rendered manifests to a provision
target when `--target` is set.

## Usage

```shell
atmos helm deploy  --stack  [options]
atmos helm deploy monitoring -s plat-ue2-dev --target deployment-repo

# Deploy components filtered by tags or labels (composes with --all/--affected)
atmos helm deploy --all --tags production,tier-1
atmos helm deploy --affected --labels cost-center=platform
```

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--target` (optional)**
  Provision target to deliver to. Defaults to 
  `provision.default`
  , otherwise the cluster.
- **`--dependency-update` (optional)**
  Fetch declared chart dependencies when they are missing. See 
  [`apply`](/cli/commands/helm/apply#flags)
  .
- **`--all` / `--affected` / `--include-dependents` (optional)**
  Process multiple Helm components in dependency order. See 
  [`apply`](/cli/commands/helm/apply)
  .
- **`--tags` / `--labels` (optional)**
  Filter by tags (comma-separated, matches any) or labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--tags=production,tier-1`
  , 
  `--labels=cost-center=platform`
  . Compose with 
  `--all`
  /
  `--affected`
   to narrow the selected set further; cannot be combined with a single component argument.

---

## atmos helm diff

Show a real unified diff between the chart Atmos would render and a baseline —
the currently deployed release, a local manifest file, or the manifests in your
GitOps deployment repository. The chart is rendered client-side with the Helm Go
SDK and compared with the [helm-diff](https://github.com/databus23/helm-diff)
engine, so changes read as familiar `+`/`-` lines. Secret values are redacted.

## Usage

```shell
atmos helm diff  --stack  [options]

# Diff against the deployed release (requires cluster access).
atmos helm diff monitoring -s plat-ue2-dev

# Diff against a local baseline manifest (offline, no cluster).
atmos helm diff monitoring -s plat-ue2-dev --from-manifest=current.yaml

# Diff against the GitOps deployment repository (offline, git access only).
atmos helm diff monitoring -s plat-ue2-dev --against=target

# Diff all components tagged "production" (composes with --all/--affected)
atmos helm diff --all --tags production
```

## Baselines

`diff` compares the freshly rendered chart against one baseline, selected by flag
precedence (`--from-manifest` → `--against` → deployed release):

- **Deployed release (default)**
  The manifest of the currently installed release, read from the cluster. A release that does not exist yet shows every object as added. This is the only mode that needs cluster access.
- **`--from-manifest=`**
  A local baseline manifest file. Fully offline — no cluster, no git.
- **`--against=target[:]`**
  The manifests currently published in a non-cluster 
  provision target
   (e.g. a git deployment repository). Reads the target's current state and diffs against it — the producer side of a GitOps workflow. Offline (git access only). Without 
  :
   the 
  provision.default
   target is used.

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--against=` (optional)**
  Baseline to diff against: 
  release
   (the deployed release, default) or 
  target[:]
   for a git deployment-repo provision target.
- **`--from-manifest=` (optional)**
  Diff against a local baseline manifest file instead of the cluster (offline).
- **`--context=` (optional)**
  Number of unchanged context lines shown around each change. Defaults to 
  3
  .
- **`--dependency-update` (optional)**
  Fetch declared chart dependencies when they are missing. This may access dependency repositories and update the chart's 
  charts/
   directory and lock file.
- **`--all` / `--affected` / `--include-dependents` (optional)**
  Process multiple Helm components in dependency order.
- **`--tags` / `--labels` (optional)**
  Filter by tags (comma-separated, matches any) or labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--tags=production,tier-1`
  , 
  `--labels=cost-center=platform`
  . Compose with 
  `--all`
  /
  `--affected`
   to narrow the selected set further; cannot be combined with a single component argument.

:::note
[`atmos helm plan`](/cli/commands/helm/plan) is an alias for `diff`. To render the
chart without comparing, use [`atmos helm template`](/cli/commands/helm/template).
The diff uses the helm-diff library directly — you do **not** need to install the
`helm-diff` CLI plugin.
:::

---

## atmos helm plan

Preview the changes a Helm apply would make. `plan` is an alias for
[`diff`](/cli/commands/helm/diff): it renders the chart client-side and shows a
unified diff against a baseline (the deployed release, a local manifest file, or
a GitOps deployment repository). See [`diff`](/cli/commands/helm/diff) for the
full description of baselines and flags.

## Usage

```shell
atmos helm plan  --stack  [options]

# Diff against the deployed release (requires cluster access).
atmos helm plan monitoring -s plat-ue2-dev

# Diff offline against a local manifest or the GitOps deployment repository.
atmos helm plan monitoring -s plat-ue2-dev --from-manifest=current.yaml
atmos helm plan monitoring -s plat-ue2-dev --against=target

# Plan all components tagged "production" (composes with --all/--affected)
atmos helm plan --all --tags production
```

In CI, bulk plans selected with `--all` or `--affected` write one aggregate GitHub job summary
after the dependency graph completes. It lists every attempted component in stable stack/component
order, distinguishes changed, unchanged, and failed results, and includes collapsible diffs for
changed components.

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--against=` / `--from-manifest=` / `--context=` (optional)**
  Select and tune the diff baseline. See 
  [`diff`](/cli/commands/helm/diff#flags)
  .
- **`--dependency-update` (optional)**
  Fetch declared chart dependencies when they are missing. See 
  [`diff`](/cli/commands/helm/diff#flags)
  .
- **`--all` / `--affected` / `--include-dependents` (optional)**
  Process multiple Helm components in dependency order.
- **`--tags` / `--labels` (optional)**
  Filter by tags (comma-separated, matches any) or labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--tags=production,tier-1`
  , 
  `--labels=cost-center=platform`
  . Compose with 
  `--all`
  /
  `--affected`
   to narrow the selected set further; cannot be combined with a single component argument.

---

## atmos helm plugin

Install and list Helm CLI plugins (such as `helm-diff` and `helm-secrets`) in
an Atmos-managed `HELM_PLUGINS` directory. Atmos installs the plugins declared
by a component automatically before running `helmfile`, and these commands let
you pre-install or inspect them on demand.

## Usage

```shell
atmos helm plugin list
atmos helm plugin install ...
atmos helm plugin install --component  --stack 
```

## Subcommands

- **`list`**
  List the plugins installed in the Atmos-managed 
  `HELM_PLUGINS`
   directory.
- **`install [plugin...]`**

  Install one or more plugins. Each plugin is a compact spec: a built-in alias
  (`diff`, `secrets`, `git`, `s3`, `unittest`), an `owner/repo`, or a full URL,
  each optionally pinned with `@version`. When called with `--component` and
  `--stack` instead of explicit plugins, Atmos installs the plugins declared by
  that component.

## Flags

- **`--component` (optional)**
  Install the plugins declared by this component. Requires 
  `--stack`
  .
- **`--stack`, `-s` (optional)**
  Atmos stack to resolve the component from when using 
  `--component`
  .

## Examples

```shell
# Install specific plugins, pinned to a version.
atmos helm plugin install diff@v3.9.4 secrets@v4.6.0

# Install by owner/repo or full URL.
atmos helm plugin install databus23/helm-diff@v3.9.4
atmos helm plugin install https://github.com/jkroepke/helm-secrets@v4.6.0

# Install the plugins a component declares.
atmos helm plugin install --component=my-app --stack=plat-ue2-dev

# List installed plugins.
atmos helm plugin list
```

## Declaring plugins on a component

Helm plugins are declared with a `plugins` list on `helm` and `helmfile`
components. Atmos installs them into the managed `HELM_PLUGINS` directory and
points `helmfile` at it before running, so plugins like `helm-diff` are available
to `helmfile diff` and `helmfile apply`:

```yaml
components:
  helmfile:
    my-app:
      plugins:
        - diff@v3.9.4
        - secrets@v4.6.0
      vars:
        # ...
```

Each entry is a compact spec — an alias, an `owner/repo`, or a full URL — pinned
with `@version` (or `@latest`). Built-in aliases: `diff`, `secrets`, `git`,
`s3`, `unittest`.

:::note
The native [`helm`](/cli/commands/helm/usage) component renders and deploys charts
with the Helm Go SDK, which does **not** run Helm CLI subcommand plugins such as
`helm-secrets`. (It does not need the `helm-diff` plugin either —
[`atmos helm diff`](/cli/commands/helm/diff) embeds the helm-diff library directly.)
Declare `plugins` on a [`helmfile`](/cli/commands/helmfile/usage) component, which
shells out to the `helm` binary so Atmos installs the plugins and exposes them to
`helmfile diff` / `helmfile apply`.
:::

:::caution
Plugin versions must be concrete tags (e.g. `v3.9.4`) or `latest`. Semver
constraints (e.g. `3.9.x`) are not supported for Helm plugins.
:::

---

## atmos helm repo list

List the declarative Helm chart repositories associated with native Helm
components. The command reads Atmos stack configuration and shows which
repositories each component inherits from global `components.helm.repositories`,
declares locally, or references directly with `repository`.

## Usage

```shell
atmos helm repo list [component] --stack  [options]
```

List all Helm repository associations in a stack:

```shell
atmos helm repo list -s plat-ue2-dev
```

List repository associations for one component:

```shell
atmos helm repo list monitoring -s plat-ue2-dev
```

Render machine-readable output:

```shell
atmos helm repo list -s plat-ue2-dev --format=json
atmos helm repo list monitoring -s plat-ue2-dev --format=yaml
```

## Columns

- **`stack`**
  Atmos stack containing the Helm component.
- **`component`**
  Helm component name.
- **`name`**
  Repository name. Direct 
  `repository: https://...`
   entries are shown as 
  `direct`
  .
- **`url`**
  Repository URL.
- **`source`**
  `global`
  , 
  `component`
  , or 
  `direct`
  .
- **`chart`**
  The component's resolved chart reference.
- **`used`**
  `true`
   when the component's 
  `chart`
   uses the repository prefix, or when the row is a direct repository URL.

## Flags

- **`--stack`, `-s` (optional)**
  Filter by stack.
- **`--format`, `-f` (optional)**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , or 
  `tsv`
  .
- **`--columns` (optional)**
  Comma-separated columns to display.
- **`--process-templates` (optional)**
  Enable or disable Go template processing in stack manifests. Defaults to 
  `true`
  .
- **`--process-functions` (optional)**
  Enable or disable YAML function processing in stack manifests. Defaults to 
  `true`
  .
- **`--skip` (optional)**
  Skip paths when loading stack manifests.

## Related

- [Helm command overview](/cli/commands/helm/usage)
- [Helm component configuration](/stacks/components/helm)
- [Helm CLI configuration](/cli/configuration/components/helm)

---

## atmos helm template

Render a Helm chart to Kubernetes manifests without touching a cluster.
`template` resolves the stack's `values`, `values_files`, chart reference, and
version, then renders the chart in-process with the Helm Go SDK (equivalent to
`helm template`). Output goes to stdout, to files, or to a provision target.

## Usage

```shell
atmos helm template  --stack  [options]
```

By default, `template` writes a multi-document YAML stream to stdout:

```shell
atmos helm template monitoring -s plat-ue2-dev
```

Write a single multi-document YAML file:

```shell
atmos helm template monitoring -s plat-ue2-dev --output rendered/monitoring.yaml
```

Write one file per object:

```shell
atmos helm template monitoring -s plat-ue2-dev --output-dir rendered/monitoring --split
```

Deliver the rendered manifests to a provision target (e.g. a Git/ArgoCD repo):

```shell
atmos helm template monitoring -s plat-ue2-dev --target deployment-repo
```

Render all or affected Helm components in dependency order:

```shell
atmos helm template --all -s plat-ue2-dev
atmos helm template --affected --base origin/main
```

Render components filtered by tags or labels (composes with `--all`/`--affected` to narrow the selected set further):

```shell
atmos helm template --all --tags production,tier-1
atmos helm template --affected --labels cost-center=platform
```

`--output` and `--output-dir` are only supported when rendering one component.

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--output` (optional)**
  Write rendered manifests to a single multi-document YAML file.
- **`--output-dir` (optional)**
  Write rendered manifests to a directory. Without 
  `--split`
  , Atmos writes 
  `manifest.yaml`
   in that directory.
- **`--split` (optional)**
  Write one YAML file per object. Requires 
  `--output-dir`
  .
- **`--dependency-update` (optional)**
  Fetch declared chart dependencies when they are missing. This may access dependency repositories and update the chart's 
  charts/
   directory and lock file.
- **`--all` (optional)**
  Render all Helm components in dependency order.
- **`--affected` (optional)**
  Render affected Helm components and their dependencies.
- **`--include-dependents` (optional)**
  With 
  `--affected`
  , include dependent Helm components.
- **`--tags` (optional)**
  Filter by tags (comma-separated, matches any): 
  `--tags=production,tier-1`
  . Composes with 
  `--all`
  /
  `--affected`
   to narrow the selected set further; cannot be combined with a single component argument.
- **`--labels` (optional)**
  Filter by labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--labels=cost-center=platform,compliance=sox`
  . Composes with 
  `--all`
  /
  `--affected`
  /
  `--tags`
  ; cannot be combined with a single component argument.

:::note
`render` is accepted as an alias for `template`.
:::

---

## atmos helm

Deploy Helm charts as first-class Atmos components — local charts, remote
repository charts, and OCI charts — using the same stack-based workflow you use
for Terraform and Kubernetes. Atmos renders, previews, installs, and deletes
releases through the Helm Go SDK, with values, credentials, hooks, and
dependency ordering handled by your stack configuration. No `helm` or
`helmfile` binary required.

> ⚠️ Experimental

**Configure Helm Components**

Set the default component location in `atmos.yaml`, then describe what each
stack should deploy with `chart`, `version`, `repositories`, `values`,
`values_files`, `namespace`, `env`, hooks, and dependencies.

Stack Configuration[Read more](/stacks/components/helm)

## Usage

```shell
atmos helm template  --stack 
atmos helm diff  --stack 
atmos helm plan  --stack 
atmos helm apply  --stack 
atmos helm deploy  --stack 
atmos helm delete  --stack 

atmos helm template --all --stack 
atmos helm apply --affected --base origin/main
atmos helm deploy  --stack  --ci

atmos helm apply --affected --tags production
atmos helm apply --all --labels cost-center=platform
```

The native Helm component renders charts in-process with the Helm Go SDK
(equivalent to `helm template`), so `atmos helm template` works without a cluster
or credentials. `apply`/`deploy` install or upgrade the release
(`helm upgrade --install`); `delete` uninstalls it.

## Native CI Summaries

When `ci.enabled: true` and CI is detected, or when CI mode is forced with `--ci` or `ATMOS_CI`,
native Helm commands write a Markdown step summary through Atmos native CI. Helm CI support is
summaries-only; it does not write output variables, commit statuses, PR comments, or artifacts.

Supported summaries:

| Command | Summary template |
|---------|------------------|
| `template`, `render` | `ci.templates.helm.template` |
| `diff`, `plan` | `ci.templates.helm.diff` |
| `apply`, `deploy` | `ci.templates.helm.apply` |
| `delete`, `destroy` | `ci.templates.helm.delete` |

Summaries include component, stack, command status, a local reproduction command, and Helm metadata
such as release name, namespace, chart, target, object counts, object kinds, and rendered manifest
size when available.
Cluster-backed release summaries also show the effective Helm lifecycle policy.
External-target summaries explicitly report that release lifecycle was bypassed.

## Release lifecycle

For cluster delivery, `apply`/`deploy` and `delete` expose Helm 4 wait and timeout
behavior. Apply/deploy additionally support rollback on failure, ordinary Job
waiting, failed-upgrade cleanup, history limits, chart-hook suppression, and CRD
skipping. Configure defaults and inherited policy in the stack component, then
use command flags only for an explicit invocation override.

See [Helm stack release lifecycle](/stacks/components/helm#release-lifecycle),
[`helm apply`](/cli/commands/helm/apply), and
[`helm delete`](/cli/commands/helm/delete) for the complete field and flag
reference.

## Chart sources

The `chart` field accepts three kinds of references:

- **Local chart**
  A path relative to the component directory (e.g. 
  `chart: .`
   or 
  `chart: ./charts/app`
  ), or an absolute path.
- **Remote repository chart**
  Either 
  `repository: https://...`
   plus 
  `chart: `
  , or a 
  `repo/name`
   reference resolved against merged global and component 
  `repositories:`
   entries. Declarative repositories are added/updated in Helm's local repository config before chart operations.
- **OCI chart**
  An 
  `oci://`
   reference (e.g. 
  `chart: oci://ghcr.io/acme/charts/app`
  ).

## Values

The component `values:` map **is** the Helm chart's values, merged through Atmos
inheritance (imports, base components, and overrides). Optional `values_files:`
overlay value files (templated, layered) underneath the inline `values`. Secret
values flow in through Atmos native secrets (the `!secret` YAML function) and are
masked automatically — Helm has no native secrets concept, so Atmos provides it.

```yaml
components:
  helm:
    monitoring:
      chart: prometheus-community/kube-prometheus-stack
      version: "65.1.1"
      repositories:
        - name: prometheus-community
          url: https://prometheus-community.github.io/helm-charts
      namespace: monitoring
      values:
        grafana:
          adminPassword: !secret grafana_admin_password
```

List repository associations for Helm components:

```shell
atmos helm repo list --stack=ue2-dev
atmos helm repo list monitoring --format=json
```

## Provision targets

Like the Kubernetes component, `apply`/`deploy` can deliver the rendered
manifests to a **provision target** instead of a cluster — for example, commit
them to a Git deployment repository monitored by ArgoCD:

```yaml
components:
  helm:
    monitoring:
      # ...
      provision:
        default: cluster
        targets:
          cluster:
            kind: kubernetes
          deployment-repo:
            kind: git
            repository: deployments
            path: "clusters/{{ .vars.stage }}/monitoring"
```

```shell
atmos helm deploy monitoring -s plat-ue2-dev --target deployment-repo
```

## Subcommands

- **[`template`](/cli/commands/helm/template)**
  Render the chart to Kubernetes manifests (stdout, files, or a provision target).
- **[`diff`](/cli/commands/helm/diff) / [`plan`](/cli/commands/helm/plan)**
  Show a unified diff against a baseline — the deployed release, a local manifest, or a GitOps deployment repository.
- **[`apply`](/cli/commands/helm/apply) / [`deploy`](/cli/commands/helm/deploy)**
  Install or upgrade the release, or deliver rendered manifests to a target.
- **[`delete`](/cli/commands/helm/delete)**
  Uninstall the release.
- **[`plugin`](/cli/commands/helm/plugin)**
  Install and list Helm CLI plugins (e.g. 
  `helm-secrets`
  ) used by 
  `helmfile`
   components.

---

## atmos helmfile generate varfile

Use this command to generate a varfile for a `helmfile` component in a stack.

## Usage

Execute the `helmfile generate varfile` command like this:

```shell
atmos helmfile generate varfile  -s  [options]
```

This command generates a varfile for a `helmfile` component in a stack.

:::tip
Run `atmos helmfile generate varfile --help` to see all the available options
:::

## Examples

```shell
atmos helmfile generate varfile echo-server -s tenant1-ue2-dev
atmos helmfile generate varfile echo-server -s tenant1-ue2-dev
atmos helmfile generate varfile echo-server -s tenant1-ue2-dev -f vars.yaml
atmos helmfile generate varfile echo-server --stack tenant1-ue2-dev --file=vars.yaml
```

## Arguments

- **`component` (required)**
  Atmos helmfile component.

## Flags

- **`--stack` / `-s` (required)**
  Atmos stack.
- **`--file` / `-f` (optional)**
  File name to write the varfile to.
  If not specified, the varfile name is generated automatically from the context.
- **`--dry-run` (optional)**
  Dry run.

---

## atmos helmfile source

Use these commands to manage Helmfile component sources with just-in-time (JIT) vendoring. This enables components to declare their source location inline using the top-level `source` field without requiring a separate `component.yaml` file. Sources are automatically provisioned when running helmfile commands—just run `atmos helmfile sync` and the source is downloaded on first use.

**Source-Based Version Pinning**

Learn how to use the source field for native per-environment version control directly in stack configuration.

Design Pattern[Read more](/design-patterns/version-management/source-based-versioning)

## Usage

```shell
atmos helmfile source  [options]
```

## Subcommands

## Automatic Provisioning

Sources are automatically provisioned when running any helmfile command. If a component has `source` configured and the target directory doesn't exist, Atmos downloads the source before running helmfile:

```shell
# Source is automatically provisioned on first use
atmos helmfile sync ingress-nginx --stack dev
# → Auto-provisioning source for component 'ingress-nginx'
# → Auto-provisioned source to components/helmfile/ingress-nginx
# → Helmfile runs
```

This means you can simply configure your component's source and run helmfile—no explicit vendor step needed.

## CLI Commands

The explicit CLI commands are useful for fine-grained control:

- Force re-vendor to get the latest version
- Describe source configuration
- Delete vendored sources
- List components with sources

## How It Works

The source provisioner enables just-in-time vendoring of Helmfile components directly from stack configuration. Instead of pre-vendoring components or maintaining separate `component.yaml` files, you can declare the source inline:

```yaml
# stacks/dev.yaml
components:
  helmfile:
    ingress-nginx:
      source:
        uri: github.com/cloudposse/helmfiles//releases/ingress-nginx
        version: 1.0.0
        included_paths:
          - "*.yaml"
          - "values/**"
        excluded_paths:
          - "*.md"
          - "tests/**"
      vars:
        namespace: ingress-nginx
```

When you run `atmos helmfile source pull ingress-nginx --stack dev`, Atmos:

1. **Reads Configuration** - Extracts `source` from the component's stack manifest.
2. **Resolves Source** - Parses the go-getter-compatible URI with optional version.
3. **Downloads Content** - Fetches the source using go-getter (supports git, s3, http, oci, etc.).
4. **Filters Files** - Applies `included_paths` and `excluded_paths` patterns.
5. **Copies to Target** - Places files in the component directory.

## Source Specification

The `source` field supports two formats:

### String Format (Simple)

For simple cases, use a go-getter URI string:

```yaml
source: "github.com/cloudposse/helmfiles//releases/ingress-nginx?ref=1.0.0"
```

### Map Format (Full Control)

For more control, use a map with explicit fields:

```yaml
source:
  uri: github.com/cloudposse/helmfiles//releases/ingress-nginx
  version: 1.0.0
  included_paths:
    - "*.yaml"
    - "values/**"
  excluded_paths:
    - "*.md"
    - "tests/**"
```

### Source Fields

- **`uri`**
  Go-getter compatible source URI. Supports git, s3, http, gcs, oci, and other protocols.
- **`version`**
  Version tag, branch, or commit. Appended as 
  `?ref=`
   for git sources. For non-git sources (S3, HTTP, OCI), the version should be embedded in the URI itself.
- **`included_paths`**
  Glob patterns for files to include. If specified, only matching files are copied.
- **`excluded_paths`**
  Glob patterns for files to exclude. Applied after included_paths filtering.
- **`ttl`**

  Cache duration for JIT-vendored sources. Controls how long a cached source is reused before re-pulling from the remote. When set, Atmos checks the source's last update time against this TTL. If expired, the source is re-pulled automatically on the next command invocation. If not set, cached sources are reused indefinitely (only re-pulled on version or URI changes).

  Examples: `"0s"` (always re-pull), `"1h"` (hourly), `"7d"` (weekly), `"daily"`.

  A global default can be set in `atmos.yaml` under `components.helmfile.source.ttl` and overridden per-component.
- **`retry`**

  Optional retry configuration for handling transient network errors during download. Useful for unreliable networks or when hitting rate limits.
  ```yaml
  retry:
    max_attempts: 5
    initial_delay: 2s
    max_delay: 60s
    backoff_strategy: exponential
  ```
  All fields are optional. Recommended: `max_attempts`, `initial_delay`, `max_delay`, `backoff_strategy` (`exponential`, `linear`, `constant`). Additional options: `multiplier`, `random_jitter`, `max_elapsed_time`.

  When `retry` is omitted, git downloads still retry transient transport failures — a DNS lookup that fails, a connection refused or reset, a timeout, or a TLS handshake error — up to 3 attempts in total, with exponential backoff (1s initial delay, 8s maximum, multiplier 2.0, 20% random jitter — each overridable through the matching `retry` field). Missing refs are never retried. Authentication failures fail fast, except when Atmos brokered the credential itself (for example a GitHub App installation token), where a short post-issue retry window applies. Set `max_attempts: 1` to disable retrying entirely.

## Examples

### Vendor a Component

Download and vendor a component source:

```shell
atmos helmfile source pull ingress-nginx --stack dev
```

### Force Re-vendor

Force re-download even if the component directory exists:

```shell
atmos helmfile source pull ingress-nginx --stack dev --force
```

### View Source Configuration

Display the source configuration for a component:

```shell
atmos helmfile source describe ingress-nginx --stack dev
```

### List Components with Sources

List all components that have source configured:

```shell
atmos helmfile source list --stack dev
```

### Delete Vendored Source

Remove the vendored component directory (requires --force for safety):

```shell
atmos helmfile source delete ingress-nginx --stack dev --force
```

## Arguments

- **`component`**
  The Atmos component name (required for pull, describe, delete)

## Flags

- **`--stack` / `-s`**
  Atmos stack name (required). Can also be set via 
  `ATMOS_STACK`
   environment variable.
- **`--identity` / `-i`**
  Identity to use for authentication when downloading from protected sources.
- **`--force` / `-f`**
  Force re-vendor even if component directory exists (pull command), or confirm deletion (delete command).

## Authentication

Source commands support authentication for accessing private repositories or cloud storage.

### Component-Level Identity

Components can specify their own authentication identity:

```yaml
components:
  helmfile:
    ingress-nginx:
      source:
        uri: github.com/my-org/private-helmfiles//releases/ingress-nginx
        version: v1.0.0
      auth:
        identities:
          github-deployer:
            default: true
            kind: github/app
            via:
              provider: github-app
```

### Identity Flag Override

Override the component's default identity:

```shell
atmos helmfile source pull ingress-nginx --stack dev --identity admin
```

For more details on configuring authentication identities, see the [Authentication Guide](/cli/configuration/auth).

## Configuration Patterns

### Inheritance with source

Use stack inheritance to share source configurations:

```yaml
# stacks/catalog/ingress-nginx/defaults.yaml
components:
  helmfile:
    ingress-nginx/defaults:
      source:
        uri: github.com/cloudposse/helmfiles//releases/ingress-nginx
        version: 1.0.0

# stacks/dev.yaml
components:
  helmfile:
    ingress-nginx:
      metadata:
        inherits: [ingress-nginx/defaults]
      # Inherits source configuration
      vars:
        namespace: ingress-nginx
```

### Version Override per Environment

Override the version per environment:

```yaml
# stacks/dev.yaml
components:
  helmfile:
    ingress-nginx:
      metadata:
        inherits: [ingress-nginx/defaults]
      source:
        version: 1.1.0  # Override version for dev

# stacks/prod.yaml
components:
  helmfile:
    ingress-nginx:
      metadata:
        inherits: [ingress-nginx/defaults]
      source:
        version: 1.0.0  # Pin to stable version for prod
```

## Supported Source Types

The source provisioner uses go-getter and supports multiple protocols:

### Git Sources

```yaml
source:
  uri: github.com/cloudposse/helmfiles//releases/ingress-nginx
  version: 1.0.0

# Also supports:
# - git::https://github.com/org/repo.git//path
# - git::ssh://git@github.com/org/repo.git//path
```

### S3 Sources

```yaml
source:
  uri: s3::https://s3-us-east-1.amazonaws.com/my-bucket/helmfiles/ingress-nginx.tar.gz
```

### HTTP/HTTPS Sources

```yaml
source:
  uri: https://releases.example.com/helmfiles/ingress-nginx-1.0.0.tar.gz
```

### OCI Registry Sources

```yaml
source:
  uri: oci::registry.example.com/helmfiles/ingress-nginx:v1.0.0
```

## Error Handling

### Missing source

```
Error: source not configured

Hint: Add source to the component configuration in your stack manifest

Example:
  components:
    helmfile:
      ingress-nginx:
        source:
          uri: github.com/cloudposse/helmfiles//releases/ingress-nginx
          version: 1.0.0
```

### Component Directory Exists

```
Error: component directory already exists

Hint: Use --force to overwrite the existing directory
```

### Download Failed

```
Error: failed to download source

Cause: failed to clone repository: authentication required

Hint: Check credentials or use --identity to specify authentication
```

## Comparison with Vendoring

| Feature | source | component.yaml |
|---------|--------|----------------|
| Configuration location | Inline in stack | Separate file |
| Version per environment | Yes | Single version |
| JIT download | Yes | Pre-vendored |
| Path filtering | Yes | Yes |
| Mixins support | No | Yes |

_Mixins allow combining multiple source configurations into a single component. See [Vendoring](/vendor) for details._

Use `source` when:

- You want version control per environment
- You prefer configuration colocation in stacks
- You need just-in-time vendoring

Use `component.yaml` when:

- You need mixin support
- You want pre-vendored components
- You have complex vendoring requirements

## See Also

- [Source-Based Version Pinning](/design-patterns/version-management/source-based-versioning) — Design pattern for per-environment version management
- [Stack Configuration](/learn/stacks) — Learn about Atmos stacks
- [Vendoring](/vendor) — Pre-vendor components for immutability and audit trails
- [`atmos vendor pull`](/cli/commands/vendor/pull) — Traditional component vendoring

---

## atmos helmfile source delete

Use this command to delete the vendored source directory for a Helmfile component. This removes the component directory that was created by `atmos helmfile source pull`.

**Source-Based Version Pinning**

Learn how to configure the `source` field for per-environment version control.

Design Pattern[Read more](/design-patterns/version-management/source-based-versioning)

## Usage

```shell
atmos helmfile source delete  --stack  --force
```

## Description

The `delete` command removes the vendored component directory. For safety, this command requires the `--force` flag to confirm deletion.

This is useful for:

- Cleaning up vendored components before re-vendoring
- Removing components that are no longer needed
- Resetting to a clean state for troubleshooting

## Examples

### Delete Vendored Component

Remove a vendored component directory:

```shell
atmos helmfile source delete ingress-nginx --stack dev --force
```

Output:

```
Deleting directory: components/helmfile/ingress-nginx
✓ Successfully deleted: components/helmfile/ingress-nginx
```

### Without Force Flag

Attempting to delete without `--force` shows an error:

```shell
atmos helmfile source delete ingress-nginx --stack dev
```

Output:

```
Error: --force flag is required

Explanation: Deletion requires --force flag for safety

Hint: Use --force to confirm deletion
```

## Arguments

- **`component`**
  **Required.**
   The name of the Atmos component to delete.

## Flags

- **`--stack` / `-s`**
  **Required.**
   The Atmos stack name. Can also be set via 
  `ATMOS_STACK`
   environment variable.
- **`--force` / `-f`**
  **Required.**
   Confirm deletion. Without this flag, the command fails for safety.

## Safety

The `delete` command has several safety features:

1. **Requires --force** - Prevents accidental deletion
2. **Only deletes source-managed components** - Only works on components with `source` configured
3. **Non-destructive on missing directories** - Shows a warning if the directory doesn't exist instead of failing

## See Also

- [Source-Based Version Pinning](/design-patterns/version-management/source-based-versioning) — Design pattern for per-environment version control
- [`atmos helmfile source`](/cli/commands/helmfile/source) — Parent command overview
- [`atmos helmfile source pull`](/cli/commands/helmfile/source/pull) — Vendor component source
- [`atmos helmfile source list`](/cli/commands/helmfile/source/list) — List components with sources
- [`atmos helmfile source describe`](/cli/commands/helmfile/source/describe) — View source configuration

---

## atmos helmfile source describe

Use this command to display the `source` configuration for a Helmfile component. This shows the source URI, version, and any path filters configured for vendoring.

**Source-Based Version Pinning**

Learn how to configure the `source` field for per-environment version control.

Design Pattern[Read more](/design-patterns/version-management/source-based-versioning)

## Usage

```shell
atmos helmfile source describe  --stack 
```

## Description

The `describe` command extracts and displays the `source` configuration from a component's stack manifest. The output matches the stack manifest schema format, making it easy to copy and paste into your configuration. This is useful for:

- Verifying source configuration before vendoring
- Checking the version configured for a component
- Reviewing included/excluded path filters
- Debugging source provisioning issues

## Examples

### View Source Configuration

Display the source configuration for a component:

```shell
atmos helmfile source describe ingress-nginx --stack dev
```

Output:

```yaml
components:
  helmfile:
    ingress-nginx:
      source:
        uri: github.com/cloudposse/helmfiles//releases/ingress-nginx
        version: 1.0.0
        included_paths:
          - "*.yaml"
          - "values/**"
        excluded_paths:
          - "*.md"
          - "tests/**"
```

### String Format Source

When source is configured as a simple string:

```shell
atmos helmfile source describe cert-manager --stack dev
```

Output:

```yaml
components:
  helmfile:
    cert-manager:
      source:
        uri: github.com/cloudposse/helmfiles//releases/cert-manager?ref=1.0.0
```

## Arguments

- **`component`**
  **Required.**
   The name of the Atmos component to describe.

## Flags

- **`--stack` / `-s`**
  **Required.**
   The Atmos stack name. Can also be set via 
  `ATMOS_STACK`
   environment variable.

## Output Format

The command outputs YAML matching the stack manifest schema:

- **`components.helmfile..source`**
  The parsed source specification for the component.
- **`source.uri`**
  The go-getter-compatible source URI.
- **`source.version`**
  The version tag, branch, or commit (if specified separately).
- **`source.included_paths`**
  List of glob patterns for files to include.
- **`source.excluded_paths`**
  List of glob patterns for files to exclude.

## See Also

- [Source-Based Version Pinning](/design-patterns/version-management/source-based-versioning) — Design pattern for per-environment version control
- [`atmos helmfile source`](/cli/commands/helmfile/source) — Parent command overview
- [`atmos helmfile source pull`](/cli/commands/helmfile/source/pull) — Vendor component source
- [`atmos helmfile source list`](/cli/commands/helmfile/source/list) — List components with sources
- [`atmos helmfile source delete`](/cli/commands/helmfile/source/delete) — Remove vendored source

---

## atmos helmfile source list

Use this command to list all Helmfile components that have `source` configured. This shows which components can be vendored using the source provisioner.

**Source-Based Version Pinning**

Learn how to configure the `source` field for per-environment version control.

Design Pattern[Read more](/design-patterns/version-management/source-based-versioning)

## Usage

```shell
atmos helmfile source list [component] [flags]
```

## Description

The `list` command scans component configurations and displays all Helmfile components that have `source` defined. This helps identify:

- Which components are configured for JIT vendoring
- The source URIs and versions for each component
- Components that may need to be vendored before use

:::tip
To list sources across all component types (Terraform, Helmfile, Packer) in a unified view, use [`atmos list sources`](/cli/commands/list/sources).
:::

## Examples

### List All Helmfile Sources Across All Stacks

```shell
atmos helmfile source list
```

Output:

```
STACK              COMPONENT       URI                                                         VERSION
plat-ue2-dev       ingress-nginx   github.com/cloudposse/helmfiles//releases/ingress-nginx     1.0.0
plat-ue2-dev       cert-manager    github.com/cloudposse/helmfiles//releases/cert-manager      1.0.0
plat-ue2-prod      ingress-nginx   github.com/cloudposse/helmfiles//releases/ingress-nginx     1.1.0
```

### List Sources in a Specific Stack

```shell
atmos helmfile source list --stack plat-ue2-dev
```

When filtering by stack, the Stack column is omitted:

```
COMPONENT       URI                                                         VERSION
ingress-nginx   github.com/cloudposse/helmfiles//releases/ingress-nginx     1.0.0
cert-manager    github.com/cloudposse/helmfiles//releases/cert-manager      1.0.0
```

### List Sources for a Specific Component

```shell
atmos helmfile source list ingress-nginx
```

Shows the component across all stacks:

```
STACK              COMPONENT       URI                                                         VERSION
plat-ue2-dev       ingress-nginx   github.com/cloudposse/helmfiles//releases/ingress-nginx     1.0.0
plat-ue2-prod      ingress-nginx   github.com/cloudposse/helmfiles//releases/ingress-nginx     1.1.0
```

### Output in Different Formats

```shell
# JSON format
atmos helmfile source list --format json

# YAML format
atmos helmfile source list --format yaml

# CSV format
atmos helmfile source list --format csv

# TSV format (tab-separated, for pipelines)
atmos helmfile source list --format tsv
```

## Dynamic Columns

The command automatically adjusts columns based on context:

| Context | Columns Shown |
|---------|---------------|
| All stacks | Stack, Component, Folder\*, URI, Version |
| Single stack (`--stack`) | Component, Folder\*, URI, Version |

\*The Folder column only appears when any component uses `metadata.component` to specify a different folder name than the component instance name.

## Arguments

- **`component` (optional)**
  Filter results to a specific component name or folder (
  `metadata.component`
  ). When provided, only shows sources for components matching this name across all stacks.

## Flags

- **`--stack` / `-s` (optional)**
  Filter by stack name. When provided, only shows sources within that stack and omits the Stack column from output.
  Environment variable: 
  `ATMOS_STACK`
- **`--format` / `-f`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
   (default: 
  `table`
  ).
  Environment variable: 
  `ATMOS_FORMAT`

## See Also

- [Source-Based Version Pinning](/design-patterns/version-management/source-based-versioning) - Design pattern for per-environment version control
- [`atmos list sources`](/cli/commands/list/sources) - Unified view across all component types
- [`atmos helmfile source`](/cli/commands/helmfile/source) - Parent command overview
- [`atmos helmfile source describe`](/cli/commands/helmfile/source/describe) - View source configuration for a specific component
- [`atmos helmfile source pull`](/cli/commands/helmfile/source/pull) - Vendor a specific component

---

## atmos helmfile source pull

Use this command to vendor a Helmfile component source based on its `source` configuration. This downloads the component from the specified URI and places it in the appropriate component directory.

**Source-Based Version Pinning**

Learn how to configure the `source` field for per-environment version control.

Design Pattern[Read more](/design-patterns/version-management/source-based-versioning)

## Usage

```shell
atmos helmfile source pull  --stack  [flags]
```

## Description

The `pull` command vendors a Helmfile component source by:

1. Reading the `source` configuration from the component's stack manifest
2. Downloading the source from the specified URI using go-getter
3. Applying any `included_paths` and `excluded_paths` filters
4. Copying the filtered content to the component directory

If the component is already vendored, it will be skipped unless `--force` is specified.

## Examples

### Basic Usage

Vendor a component source for a specific stack:

```shell
atmos helmfile source pull ingress-nginx --stack dev
```

### Force Re-vendor

Re-vendor even if the component directory already exists:

```shell
atmos helmfile source pull ingress-nginx --stack dev --force
```

### With Identity Override

Use a specific identity for authentication:

```shell
atmos helmfile source pull ingress-nginx --stack dev --identity admin
```

## Arguments

- **`component`**
  **Required.**
   The name of the Atmos component to vendor.

## Flags

- **`--stack` / `-s`**
  **Required.**
   The Atmos stack name. Can also be set via 
  `ATMOS_STACK`
   environment variable.
- **`--force` / `-f`**
  Force re-vendor even if the component directory already exists. Without this flag, the command skips vendoring if the directory exists.
- **`--identity` / `-i`**
  Identity to use for authentication when downloading from protected sources. Overrides the component's default identity.

## Configuration

The component must have `source` configured in its stack manifest:

```yaml
components:
  helmfile:
    ingress-nginx:
      source:
        uri: github.com/cloudposse/helmfiles//releases/ingress-nginx
        version: 1.0.0
        included_paths:
          - "*.yaml"
        excluded_paths:
          - "*.md"
          - "tests/**"
```

## Output

On success:

```
Vendoring component 'ingress-nginx' from source...
Downloading from github.com/cloudposse/helmfiles//releases/ingress-nginx?ref=1.0.0
✓ Successfully vendored component to components/helmfile/ingress-nginx
```

## See Also

- [Source-Based Version Pinning](/design-patterns/version-management/source-based-versioning) — Design pattern for per-environment version control
- [`atmos helmfile source`](/cli/commands/helmfile/source) — Parent command overview
- [`atmos helmfile source describe`](/cli/commands/helmfile/source/describe) — View source configuration
- [`atmos helmfile source list`](/cli/commands/helmfile/source/list) — List components with source
- [`atmos helmfile source delete`](/cli/commands/helmfile/source/delete) — Remove vendored source

---

## atmos helmfile template

Render all Helm releases defined in a Helmfile component to Kubernetes
manifests by running `helmfile template` with the generated values file. The
rendered manifests are written to stdout by default, or delivered to a
provision target (such as a Git deployment repository monitored by ArgoCD) when
`--target` is set. This enables a GitOps workflow where CI renders manifests
from Helmfile components and publishes them to a deployment repository — the
producer side of GitOps.

## Usage

```shell
atmos helmfile template  --stack  [options]
```

Render to stdout:

```shell
atmos helmfile template echo-server -s tenant1-ue2-dev
```

Deliver the rendered manifests to a provision target (e.g. a Git/ArgoCD repo):

```shell
atmos helmfile template echo-server -s tenant1-ue2-dev --target deployment-repo
```

Configure the target in the component's `provision` section:

```yaml
components:
  helmfile:
    echo-server:
      provision:
        default: cluster
        targets:
          cluster:
            kind: kubernetes
          deployment-repo:
            kind: git
            repository: deployments
            path: "clusters/{{ .vars.stage }}/echo-server"
```

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--target` (optional)**
  Provision target to deliver the rendered manifests to (e.g. a Git deployment repository). When omitted (or when the selected target is the cluster), the manifests are written to stdout.

Any additional flags are passed through to `helmfile template`.

:::info
This command implements the request in
[cloudposse/atmos#2069](https://github.com/cloudposse/atmos/issues/2069).
Helmfile remains useful when you need its advanced orchestration features. For
most new chart deployments, native [`atmos helm`](/cli/commands/helm/usage)
provides the common workflow directly in Atmos.
:::

---

## atmos helmfile

Use these subcommands to run `helmfile` commands.

:::info Native Helm and Helmfile
Atmos continues to support Helmfile, which remains a great choice when you need
its advanced orchestration features. For most new chart deployments, native
[`atmos helm`](/cli/commands/helm/usage) is now the simpler Atmos-native path:
the common Helmfile use cases are handled directly by Atmos with one less layer
to configure and debug.
:::

# Usage

The `helmfile` integration passes through all arguments to the `helmfile` command.

Executes `helmfile` commands.

```shell
atmos helmfile   -s  [options]
atmos helmfile   --stack  [options]
atmos helmfile   --stack  --ci [options]
```

## Path-Based Component Resolution

Atmos supports using filesystem paths instead of component names for convenience. This allows you to navigate to a component directory and use `.` to reference it:

```shell
# Navigate to component directory
cd components/helmfile/echo-server

# Use . to reference current directory
atmos helmfile diff . -s dev
atmos helmfile apply . -s dev
```

This automatically resolves the path to the component name configured in your stack, eliminating the need to remember exact component names.

**Supported path formats:**

- `.` - Current directory
- `./component` - Relative path from current directory
- `../other-component` - Relative path to sibling directory
- `/absolute/path/to/component` - Absolute path

**Requirements:**

- Must be inside a component directory under the configured base path
- Must specify `--stack` flag
- Component must exist in the specified stack configuration
- **The component path must resolve to a unique component name** - If multiple components in the stack reference the same component path, you must use the unique component name instead of the path

:::warning Path Resolution Limitation
Path-based resolution only works when the component path resolves to a **single unique component** in the stack.

For example, if both `app/1` and `app/2` reference `components/helmfile/my-app`:

```bash
cd components/helmfile/my-app
atmos helmfile apply . --stack dev  # ❌ Error: ambiguous - which component?
```

Instead, you must use the unique component names:

```bash
atmos helmfile apply app/1 --stack dev  # ✓ Explicit and unambiguous
atmos helmfile apply app/2 --stack dev  # ✓ Explicit and unambiguous
```

:::

:::info
Atmos supports all `helmfile` commands and options described in [Helmfile CLI reference](https://github.com/helmfile/helmfile#cli-reference).

In addition, the `component` argument and `stack` flag are required to generate variables for the component in the stack.
:::

**Additions and differences from native Helmfile:**

- `atmos helmfile generate varfile` command generates a varfile for the component in the stack

- `atmos helmfile` commands support [GLOBAL OPTIONS](https://github.com/roboll/helmfile#cli-reference) using the command-line flag `--global-options`.
  Usage: `atmos helmfile   -s  [command options] [arguments] --global-options="--no-color --namespace=test"`

- before executing the `helmfile` commands, Atmos runs `aws eks update-kubeconfig` to read kubeconfig from the EKS cluster and use it to
  authenticate with the cluster. This can be disabled in `atmos.yaml` CLI config by setting `components.helmfile.use_eks` to `false`

- double-dash `--` can be used to signify the end of the options for Atmos and the start of the additional native arguments and flags for
  the `helmfile` commands.

- `--ci` enables Atmos native CI mode for supported Helmfile operations and is not forwarded to the `helmfile` binary.

## Native CI Summaries

When `ci.enabled: true` and CI is detected, or when CI mode is forced with `--ci` or `ATMOS_CI`,
Helmfile commands write a Markdown step summary through Atmos native CI. Helmfile CI support is
summaries-only; it does not write output variables, commit statuses, PR comments, or artifacts.

Supported summaries:

| Command | Summary template |
|---------|------------------|
| `template` | `ci.templates.helmfile.template` |
| `diff` | `ci.templates.helmfile.diff` |
| `apply`, `sync`, `deploy` | `ci.templates.helmfile.apply` |
| `destroy` | `ci.templates.helmfile.destroy` |

Summaries include component, stack, command status, a local reproduction command, and captured
masked stdout/stderr in a collapsible section.

:::tip
Run `atmos helmfile --help` to see all the available options
:::

## Examples

### Component Name Examples

```shell
atmos helmfile diff echo-server -s tenant1-ue2-dev
atmos helmfile diff echo-server -s tenant1-ue2-dev --redirect-stderr /dev/null

atmos helmfile apply echo-server -s tenant1-ue2-dev
atmos helmfile apply echo-server -s tenant1-ue2-dev --redirect-stderr /dev/stdout

atmos helmfile sync echo-server --stack tenant1-ue2-dev
atmos helmfile sync echo-server --stack tenant1-ue2-dev --redirect-stderr ./errors.txt

atmos helmfile destroy echo-server --stack=tenant1-ue2-dev
atmos helmfile destroy echo-server --stack=tenant1-ue2-dev --redirect-stderr /dev/stdout
```

### Path-Based Examples

```shell
# Navigate to component directory and use current directory
cd components/helmfile/echo-server
atmos helmfile diff . -s dev
atmos helmfile apply . -s dev

# Use relative path from components/helmfile directory
cd components/helmfile
atmos helmfile sync ./echo-server -s prod

# Use from project root with relative path
atmos helmfile apply components/helmfile/echo-server -s dev

# Combine with other flags
cd components/helmfile/echo-server
atmos helmfile diff . -s dev --redirect-stderr /dev/null
atmos helmfile sync . -s dev --global-options="--no-color"
```

## Arguments

- **`component` (required)**

  Atmos component name or filesystem path.
  Supports both:

  Component names: echo-server, apps/nginx
  Filesystem paths: . (current directory), ./echo-server, components/helmfile/echo-server

  When using paths, Atmos automatically resolves the path to the component name based on your stack configuration.
  See Path-Based Component Resolution above.

## Flags

- **`--stack` / `-s` (required)**
  Atmos stack.
- **`--dry-run` (optional)**
  Dry run.
- **`--redirect-stderr` (optional)**
  File descriptor to redirect 
  `stderr`
   to.
  Errors can be redirected to any file or any standard file descriptor
  (including 
  `/dev/null`
  ).

:::note

All native `helmfile` flags, command options, and arguments are supported

:::

**Configure Helmfile**

Learn how to configure Helmfile components in your `atmos.yaml`, including EKS integration and kubeconfig settings.

## Subcommands

---

## atmos help

## Usage

The `atmos --help` and `atmos -h` commands show help for all Atmos CLI commands.

From time to time, Atmos will check for a newer release and let you know if one is available.
Please see the [`atmos version`](/cli/commands/version/usage) documentation to configure this behavior.

```shell
atmos help
atmos --help
atmos -h
```

## Examples

```shell
atmos help               # Starts an interactive help UI in the terminal
atmos --help             # Shows help for all Atmos CLI commands
atmos -h                 # Shows help for all Atmos CLI commands
atmos atlantis --help    # Executes 'atlantis' commands
atmos aws --help         # Executes 'aws' commands
atmos completion --help  # Executes 'completion' commands
atmos describe --help    # Executes 'describe' commands
atmos terraform --help   # Executes 'terraform' commands
atmos helmfile --help    # Executes 'helmfile' commands
atmos packer --help      # Executes 'packer' commands
atmos validate --help    # Executes 'validate' commands
atmos vendor --help      # Executes 'vendor' commands
atmos workflow --help    # Executes 'workflow' commands
```

## Screenshots

The `atmos help` starts an interactive help UI in the terminal:

---

## atmos init

Initialize a project from a proven Atmos starting point. `atmos init` selects a project template,
collects its validated answers, generates the project, and records the source needed for later
updates.

## Usage

```shell
atmos init [template] [target] [flags]
```

```shell
# Choose a template and target interactively.
atmos init

# Initialize a minimal cloud-agnostic project.
atmos init basic ./my-project

# Initialize an AWS application or landing-zone foundation.
atmos init aws/app ./my-app
atmos init aws/landing-zone ./my-platform

# Provide values for automated project creation.
atmos init basic ./my-project --set project_name=my-project --interactive=false
```

## Templates

The built-in catalog includes `basic`, `simple`, `atmos`, `aws/app`,
`aws/landing-zone`, `gcp/landing-zone`, and `azure/landing-zone`. Run
[`atmos scaffold list`](/cli/commands/scaffold/list) to see the complete catalog, including
configured and remote sources available to the current project.

`basic` is a small cloud-agnostic project with a real local greeting component. `aws/app` starts
an application SDLC layout with development, staging, and production stacks. The landing-zone
templates establish cloud-specific platform foundations.

`[template]` also accepts a direct source instead of a catalog name — a local path, git, HTTPS,
S3, or an OCI registry reference:

```shell
atmos init oci://ghcr.io/example/templates:v1.0.0 ./my-project
```

An OCI source is pulled the same way `atmos vendor pull` fetches OCI-hosted components; see
[Vendor URL Syntax](/vendor/url-syntax#oci-syntax) for authentication details.

## Shared Scaffold Contract

Project templates use the same `AtmosScaffoldConfig` manifest and generation engine as
[`atmos scaffold generate`](/cli/commands/scaffold/generate). A template can define validated
`spec.fields`, conditional `spec.files`, and step-backed `spec.hooks`:

```yaml title="scaffold.yaml"
apiVersion: atmos/v1
kind: AtmosScaffoldConfig
metadata:
  name: application-project
spec:
  fields:
    - name: environments
      type: multiselect
      options: [dev, staging, prod]
      default: [dev]
    - name: enable_monitoring
      type: confirm
      default: false
  files:
    - path: monitoring.tf
      when: "answers.enable_monitoring == true"
  hooks:
    format:
      events: [after.scaffold.generate]
      kind: step
      type: shell
      with:
        command: terraform fmt -recursive
```

Conditions use `when:` predicates or CEL over earlier `answers`. Generation hooks can use only
`kind: step` and ordered `kind: steps`; see [scaffold templates](/cli/commands/scaffold/usage) for the
complete authoring model, including `--skip-hooks` and answer templating.

## Updating an Initialized Project

`init` records the selected source, base revision, and answers in `.atmos/scaffold.yaml`. Re-run
with `--update` to bring an existing project forward using an optimistic three-way merge:

```shell
cd my-project
atmos init --update
atmos init --update --merge-strategy=theirs
```

`manual` is the default merge strategy and surfaces conflicts. `ours` preserves local changes;
`theirs` applies the template side of a conflict. `atmos init` creates Git history by default; pass
`--no-git` when that is not wanted.

## Flags and Automation

- **`--set key=value` (repeatable)**
  Provide a template answer; repeat for multiple fields.
- **`--interactive=false`**
  Run without form prompts when values and defaults satisfy active fields.
- **`--force`**
  Permit writes into an existing target.
- **`--update`**
  Merge a generated project with its template's newer revision.
- **`--base-ref`**
  Override the recorded merge base.
- **`--merge-driver` (default `auto`)**

  Choose `auto` (YAML-aware for `.yaml`/`.yml`, text otherwise) or `text` to force every file
  through the line-oriented text merge driver, preserving formatting (e.g. blank lines) that a
  YAML-aware re-encode would otherwise collapse.
- **`--merge-strategy`**
  Select 
  `manual`
  , 
  `ours`
  , or 
  `theirs`
  .
- **`--skip-hooks`**
  Skip all hooks or named generation hooks.
- **`--no-git`**
  Do not initialize or commit Git history.

## Related Commands

- [Scaffold templates](/cli/commands/scaffold/usage)
- [`atmos scaffold generate`](/cli/commands/scaffold/generate)
- [`atmos scaffold validate`](/cli/commands/scaffold/validate)

---

## atmos kubernetes apply

Deploy the Kubernetes objects defined by a stack-configured component without
leaving Atmos. `apply` renders the manifests, prepares the component
environment, runs hooks, and applies the result to the target cluster.

**Configure Kubernetes Components**

Define where manifests come from, which values they receive, and which
credentials or hooks should run from the Kubernetes component stack
configuration.

Stack Configuration[Read more](/stacks/components/kubernetes)
atmos.yaml Configuration[Read more](/cli/configuration/components/kubernetes)

## Usage

```shell
atmos kubernetes apply  --stack 
atmos kubernetes apply --all --stack 
```

`apply` renders the final manifests and applies them through Kubernetes Go
clients. Atmos resolves each object from GVK to GVR using discovery and a
RESTMapper, then applies it through the dynamic client with server-side apply.

The command does not require the `kubectl` binary.

## Example

```shell
atmos kubernetes apply argocd -s plat-ue2-dev
```

Example output:

```text
applied v1/Namespace argocd
applied apps/v1/Deployment argocd/argocd-server
```

Apply components filtered by tags or labels (composes with `--all`/`--affected` to narrow the selected set further):

```shell
atmos kubernetes apply --all --tags production,tier-1
atmos kubernetes apply --affected --labels cost-center=platform
```

To publish the rendered manifests to a Git deployment repository instead of
applying to the cluster, configure `provision.targets` and select a git target
with `--target`. See
[Deployment repositories (GitOps)](/cli/commands/kubernetes/deploy#deployment-repositories-gitops).

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--target` (optional)**

  Provision target to deliver to (a named `provision.targets` entry, e.g. a git
  deployment repository). Defaults to `provision.default`, otherwise the cluster.
- **`--all` (optional)**
  Apply all Kubernetes components in dependency order.
- **`--affected` (optional)**
  Apply affected Kubernetes components and their dependencies.
- **`--include-dependents` (optional)**
  With 
  `--affected`
  , include dependent Kubernetes components.
- **`--tags` (optional)**

  Filter by tags (comma-separated, matches any): `--tags=production,tier-1`. Composes with `--all`/`--affected` to narrow the selected set further; cannot be combined with a single component argument.
- **`--labels` (optional)**

  Filter by labels (comma-separated `key=value` or `key:value` pairs, matches all): `--labels=cost-center=platform,compliance=sox`. Composes with `--all`/`--affected`/`--tags`; cannot be combined with a single component argument.

---

## atmos kubernetes delete

Remove the Kubernetes objects managed by a stack-configured component. Atmos
renders the same manifests used for deployment, resolves each object, and
deletes it from the target cluster by name and namespace.

**Configure Kubernetes Components**

Keep the desired objects in stack configuration, then use `delete` when a
component should be removed from an environment.

Stack Configuration[Read more](/stacks/components/kubernetes)

## Usage

```shell
atmos kubernetes delete  --stack 
atmos kubernetes delete --all --stack 
```

`delete` renders the final manifests, resolves each object from GVK to GVR using
discovery and a RESTMapper, and deletes each object by name and namespace
through the Kubernetes dynamic client.

The command does not require the `kubectl` binary.

## Example

```shell
atmos kubernetes delete argocd -s plat-ue2-dev
```

Example output:

```text
deleted apps/v1/Deployment argocd/argocd-server
not-found v1/Service argocd/argocd-server
```

Delete components filtered by tags or labels (composes with `--all`/`--affected` to narrow the selected set further):

```shell
atmos kubernetes delete --all --tags production,tier-1
atmos kubernetes delete --affected --labels cost-center=platform
```

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--all` (optional)**
  Delete all Kubernetes components in dependency order.
- **`--affected` (optional)**
  Delete affected Kubernetes components and their dependencies.
- **`--include-dependents` (optional)**
  With 
  `--affected`
  , include dependent Kubernetes components.
- **`--tags` (optional)**

  Filter by tags (comma-separated, matches any): `--tags=production,tier-1`. Composes with `--all`/`--affected` to narrow the selected set further; cannot be combined with a single component argument.
- **`--labels` (optional)**

  Filter by labels (comma-separated `key=value` or `key:value` pairs, matches all): `--labels=cost-center=platform,compliance=sox`. Composes with `--all`/`--affected`/`--tags`; cannot be combined with a single component argument.

---

## atmos kubernetes deploy

Run a Kubernetes deployment using Atmos language. `deploy` is the
workflow-friendly alias for applying a stack-configured Kubernetes component,
useful when your automation treats Kubernetes resources as deployable
services.

**Configure Kubernetes Components**

`deploy` uses the same stack and `atmos.yaml` settings as `apply`, including
manifest paths, variables, credentials, hooks, and dependency ordering.

Stack Configuration[Read more](/stacks/components/kubernetes)
atmos.yaml Configuration[Read more](/cli/configuration/components/kubernetes)

## Usage

```shell
atmos kubernetes deploy  --stack 
atmos kubernetes deploy --affected --base origin/main --include-dependents
```

In v1, `deploy` is an alias of
[`atmos kubernetes apply`](/cli/commands/kubernetes/apply). It renders the final
manifests and applies them through the Kubernetes Go SDK using server-side
apply.

Use `deploy` when you want command language that matches an application deployment workflow. Use `apply` when you want command language that mirrors Kubernetes API behavior.

## Example

```shell
atmos kubernetes deploy argocd -s plat-ue2-dev
```

Deploy components filtered by tags or labels (composes with `--all`/`--affected` to narrow the selected set further):

```shell
atmos kubernetes deploy --all --tags production,tier-1
atmos kubernetes deploy --affected --labels cost-center=platform
```

## Deployment repositories (GitOps)

By default `deploy`/`apply` applies the rendered manifests to the cluster. A
component can instead publish them to a Git deployment repository (the
source-of-truth that Argo CD or Flux reconciles) by declaring delivery targets
under `provision.targets`:

```yaml
components:
  kubernetes:
    argocd:
      provision:
        default: cluster
        targets:
          cluster:
            kind: kubernetes
          deployment-repo:
            kind: git
            repository: deployments        # references git.repositories.
            path: "clusters/{{ .vars.cluster }}/argocd"
            commit:
              message: "Render {{ .vars.app_name }} for {{ .vars.stage }}"
```

Selecting the git target renders the manifests once and commits them to the
configured repository instead of applying to the cluster:

```shell
atmos kubernetes deploy argocd -s plat-ue2-dev --target=deployment-repo
```

When `--target` is omitted, `provision.default` is used, otherwise the cluster.
Credentials for cloning and pushing come from Atmos Auth (GitHub STS), so no
tokens are stored in the manifests.

### File vs. directory delivery (`split`)

A git target's `path` can be either a directory (one file per rendered object) or
the exact name of a single output file. Set `split` on the target to control
which:

- **`split: true`**
  `path`
   is a directory; every rendered object is written to its own generated filename inside it.
- **`split: false`**
  `path`
   is the exact output file; every rendered object is merged into one multi-document YAML file written to that path.
- **unset (default)**

  Inferred from `path`: if the last path segment looks like a manifest
  filename (matches `/\.(ya?ml|json)$/i`, e.g. `kustomization.yaml`), `split`
  defaults to `false`; otherwise it defaults to `true`, preserving the
  directory behavior every existing configuration already relies on.

```yaml
components:
  kubernetes:
    argocd:
      provision:
        targets:
          deployment-repo:
            kind: git
            repository: deployments
            path: "kustomize/overlays/{{ .vars.environment }}/kustomization.yaml"
            # split is omitted here: the path ends in .yaml, so it is inferred
            # as split: false and written as a single file, not a directory.
            commit:
              message: "Render manifests for {{ .vars.environment }}"
```

See [Generating a Kustomize component for GitOps](/stacks/components/kubernetes#generating-a-kustomize-component-for-gitops)
for a complete walkthrough of this pattern.

## Flags

- **`--stack`, `-s` (optional)**
  Atmos stack. Required when deploying a named component; not required with 
  `--affected`
  .
- **`--target` (optional)**

  Provision target to deliver to (a named `provision.targets` entry, e.g. a git
  deployment repository). Defaults to `provision.default`, otherwise the cluster.
- **`--all` (optional)**
  Deploy all Kubernetes components in dependency order.
- **`--affected` (optional)**
  Deploy affected Kubernetes components and their dependencies.
- **`--include-dependents` (optional)**
  With 
  `--affected`
  , include dependent Kubernetes components.
- **`--tags` (optional)**

  Filter by tags (comma-separated, matches any): `--tags=production,tier-1`. Composes with `--all`/`--affected` to narrow the selected set further; cannot be combined with a single component argument.
- **`--labels` (optional)**

  Filter by labels (comma-separated `key=value` or `key:value` pairs, matches all): `--labels=cost-center=platform,compliance=sox`. Composes with `--all`/`--affected`/`--tags`; cannot be combined with a single component argument.

---

## atmos kubernetes diff

Check what would change in the cluster before you deploy. `diff` renders the
component, asks Kubernetes to validate the update with server-side dry run,
and reports which objects would be created, changed, or left alone.

**Configure Kubernetes Components**

`diff` uses the same stack inputs as deployment: `provider`, `paths`,
`manifests`, `vars`, `env`, Auth, hooks, and dependencies.

Stack Configuration[Read more](/stacks/components/kubernetes)

## Usage

```shell
atmos kubernetes diff  --stack 
atmos kubernetes diff --affected --base origin/main
```

`diff` renders the final manifests, sends each object through Kubernetes
server-side dry-run apply, reads the live object through the dynamic client,
normalizes volatile metadata, and reports whether each object would be created,
changed, or left unchanged.

The command does not require the `kubectl` binary and does not run `kubectl diff`.

## Example

```shell
atmos kubernetes diff argocd -s plat-ue2-dev
```

Diff components filtered by tags or labels (composes with `--all`/`--affected` to narrow the selected set further):

```shell
atmos kubernetes diff --all --tags production,tier-1
atmos kubernetes diff --affected --labels cost-center=platform
```

Example output (each created or changed object is followed by its unified diff;
`no-change` objects and `Secret` objects show only the action line):

```diff
create v1/Namespace argocd
--- a/v1/Namespace_argocd.yaml
+++ b/v1/Namespace_argocd.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: argocd
changed apps/v1/Deployment argocd/argocd-server
@@ -8,7 +8,7 @@
   replicas: 2
-  image: argocd-server:2.10.0
+  image: argocd-server:2.11.0
no-change v1/Service argocd/argocd-server
```

When run in CI (`ci.enabled: true`), the same diff is also written to the job summary as a
collapsible **Kubernetes Diff** block. See [Job Summaries](/ci/job-summaries). `Secret` objects
are omitted from the diff so their data is never printed or written to the summary.

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--all` (optional)**
  Diff all Kubernetes components in dependency order.
- **`--affected` (optional)**
  Diff affected Kubernetes components and their dependencies.
- **`--include-dependents` (optional)**
  With 
  `--affected`
  , include dependent Kubernetes components.
- **`--tags` (optional)**

  Filter by tags (comma-separated, matches any): `--tags=production,tier-1`. Composes with `--all`/`--affected` to narrow the selected set further; cannot be combined with a single component argument.
- **`--labels` (optional)**

  Filter by labels (comma-separated `key=value` or `key:value` pairs, matches all): `--labels=cost-center=platform,compliance=sox`. Composes with `--all`/`--affected`/`--tags`; cannot be combined with a single component argument.

---

## atmos kubernetes plan

Give Kubernetes components a Terraform-style preview step. `plan` shows what a
stack-configured component would create or change before you approve the
deployment workflow.

**Configure Kubernetes Components**

`plan` uses the same Kubernetes component stack configuration as `diff`:
manifest inputs, rendered values, Auth, hooks, and dependencies.

Stack Configuration[Read more](/stacks/components/kubernetes)

## Usage

```shell
atmos kubernetes plan  --stack 
atmos kubernetes plan --affected --base origin/main
```

In v1, `plan` is an alias of
[`atmos kubernetes diff`](/cli/commands/kubernetes/diff). It renders the final
manifests, validates them with Kubernetes server-side dry-run apply, reads live
objects, and reports create, change, and no-change results — including the per-object unified
diff in the terminal and, in CI, a collapsible **Kubernetes Diff** block in the job summary
(`Secret` objects are omitted). See [`atmos kubernetes diff`](/cli/commands/kubernetes/diff) for
example output.

This gives Kubernetes components a Terraform-shaped preview command while keeping the implementation simple. Future versions can extend `plan` with structured plan files or approval artifacts without changing `diff`.

## Example

```shell
atmos kubernetes plan argocd -s plat-ue2-dev
```

Plan components filtered by tags or labels (composes with `--all`/`--affected` to narrow the selected set further):

```shell
atmos kubernetes plan --all --tags production,tier-1
atmos kubernetes plan --affected --labels cost-center=platform
```

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--all` (optional)**
  Plan all Kubernetes components in dependency order.
- **`--affected` (optional)**
  Plan affected Kubernetes components and their dependencies.
- **`--include-dependents` (optional)**
  With 
  `--affected`
  , include dependent Kubernetes components.
- **`--tags` (optional)**

  Filter by tags (comma-separated, matches any): `--tags=production,tier-1`. Composes with `--all`/`--affected` to narrow the selected set further; cannot be combined with a single component argument.
- **`--labels` (optional)**

  Filter by labels (comma-separated `key=value` or `key:value` pairs, matches all): `--labels=cost-center=platform,compliance=sox`. Composes with `--all`/`--affected`/`--tags`; cannot be combined with a single component argument.

---

## atmos kubernetes render

See exactly what Kubernetes YAML Atmos will send before anything touches the
cluster. `render` resolves stack values, inline manifests, generated files,
and provider inputs into the final manifests you can inspect, save, or pass to
another tool.

**Configure Render Inputs**

Put manifest files, Kustomize paths, inline objects, variables, environment,
and default output paths in the Kubernetes component stack configuration.

Stack Configuration[Read more](/stacks/components/kubernetes)

## Usage

```shell
atmos kubernetes render  --stack  [options]
```

`render` resolves the component stack configuration, runs generation when
enabled, loads inline `manifests`, renders provider inputs, and writes the final
Kubernetes YAML. It does not contact the Kubernetes API server.

## Output

By default, `render` writes a multi-document YAML stream to stdout:

```shell
atmos kubernetes render argocd -s plat-ue2-dev
```

Write a single multi-document YAML file:

```shell
atmos kubernetes render argocd -s plat-ue2-dev \
  --output rendered/argocd.yaml
```

Write one file per Kubernetes object:

```shell
atmos kubernetes render argocd -s plat-ue2-dev \
  --output-dir rendered/argocd \
  --split
```

Configure a default render output in the component:

```yaml
components:
  kubernetes:
    argocd:
      render:
        output:
          path: rendered/clusters/{{ .vars.cluster }}/argocd
          split: true
```

When `split: true`, `path` is treated as an output directory. Otherwise,
`path` is treated as a single output file.

Render all Kubernetes components in dependency order:

```shell
atmos kubernetes render --all -s plat-ue2-dev
```

Render affected Kubernetes components:

```shell
atmos kubernetes render --affected --base origin/main
```

Render components filtered by tags or labels (composes with `--all`/`--affected` to narrow the selected set further):

```shell
atmos kubernetes render --all --tags production,tier-1
atmos kubernetes render --affected --labels cost-center=platform
```

`--output` and `--output-dir` are only supported when rendering one component.
For `--all` or `--affected`, configure component-level `render.output` paths.

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--output` (optional)**
  Write rendered manifests to a single multi-document YAML file.
- **`--output-dir` (optional)**

  Write rendered manifests to a directory. Without `--split`, Atmos writes
  `manifest.yaml` in that directory.
- **`--split` (optional)**
  Write one YAML file per Kubernetes object. Requires 
  `--output-dir`
  .
- **`--all` (optional)**
  Render all Kubernetes components in dependency order.
- **`--affected` (optional)**
  Render affected Kubernetes components and their dependencies.
- **`--include-dependents` (optional)**
  With 
  `--affected`
  , include dependent Kubernetes components.
- **`--tags` (optional)**

  Filter by tags (comma-separated, matches any): `--tags=production,tier-1`. Composes with `--all`/`--affected` to narrow the selected set further; cannot be combined with a single component argument.
- **`--labels` (optional)**

  Filter by labels (comma-separated `key=value` or `key:value` pairs, matches all): `--labels=cost-center=platform,compliance=sox`. Composes with `--all`/`--affected`/`--tags`; cannot be combined with a single component argument.

---

## atmos kubernetes

Manage raw Kubernetes manifests and Kustomize overlays with the same
stack-based workflow you use for the rest of your Atmos project. Render,
preview, deploy, and delete Kubernetes objects from one configuration model,
with credentials, hooks, and dependency ordering handled by Atmos.

**Configure Kubernetes Components**

Set the default component location in `atmos.yaml`, then describe what each
stack should deploy with `provider`, `paths`, `manifests`, `vars`, `env`,
hooks, and dependencies.

atmos.yaml Configuration[Read more](/cli/configuration/components/kubernetes)
Stack Configuration[Read more](/stacks/components/kubernetes)

## Usage

```shell
atmos kubernetes render  --stack 
atmos kubernetes validate  --stack 
atmos kubernetes diff  --stack 
atmos kubernetes plan  --stack 
atmos kubernetes apply  --stack 
atmos kubernetes deploy  --stack 
atmos kubernetes delete  --stack 

atmos kubernetes plan --affected --base origin/main
atmos kubernetes apply --all --stack 
```

## Examples

Render manifests to a file, then validate them offline:

```shell
atmos kubernetes render argocd -s plat-ue2-dev --output /tmp/argocd.yaml
atmos kubernetes validate argocd -s plat-ue2-dev
```

Apply every component in a stack in dependency order:

```shell
atmos kubernetes apply --all -s plat-ue2-dev --include-dependents
```

Plan only components affected by changes against `origin/main`:

```shell
atmos kubernetes plan --affected --base origin/main
```

Apply only affected components that are also tagged `production`:

```shell
atmos kubernetes apply --affected --tags production
```

Deploy to an EKS cluster after Atmos Auth writes kubeconfig:

```shell
ATMOS_COMPONENT=argocd atmos kubernetes deploy argocd -s plat-ue2-prod
```

## Native CI

When `ci.enabled: true` and Atmos runs in a supported CI provider, Kubernetes commands write a
compact job summary to the provider summary file (`$GITHUB_STEP_SUMMARY` on GitHub Actions).
The summary includes object action counts and an error section on failure. For `plan`/`diff` it
also adds a collapsible **Kubernetes Diff** block with the per-object unified diff (`Secret`
objects are omitted).

Kubernetes CI support is summary-only in v1. It does not emit `$GITHUB_OUTPUT` variables, commit
statuses, PR comments, or stored artifacts.

## Providers

Choose the provider that matches how your Kubernetes files are organized:

- **`kubectl`**
  Loads plain YAML or JSON manifests and applies kubectl-compatible manifest behavior through Kubernetes Go clients.
- **`kustomize`**
  Renders Kustomize directories through the Kustomize Go API, then uses the same Kubernetes Go clients.

These provider names describe manifest behavior. Both providers are implemented
with Go SDKs, so Atmos does not require the matching `kubectl` or `kustomize`
CLI binary to be installed.

## Component Configuration

```yaml
components:
  kubernetes:
    argocd:
      provider: kustomize
      paths:
        - overlays/dev
      vars:
        namespace: argocd
      env:
        KUBECONFIG: /tmp/kubeconfig
```

`paths` can reference manifest files or directories relative to the component
directory. `manifests` can define inline Kubernetes objects that Atmos renders
and deploys with the files from `paths`.

```yaml
components:
  kubernetes:
    namespace:
      provider: kubectl
      manifests:
        - apiVersion: v1
          kind: Namespace
          metadata:
            name: "{{ .vars.namespace }}"
```

## Auth

Kubernetes operations use the kubeconfig and client configuration visible to the
Go Kubernetes client. Atmos Auth runs before the SDK client is created, so an
identity can prepare credentials, write kubeconfig, or export environment
variables used by the Kubernetes client.

For local clusters, use an ambient identity to pass through the existing
environment:

```yaml
auth:
  identities:
    local-k3s:
      kind: ambient

components:
  kubernetes:
    app:
      env:
        KUBECONFIG: /path/to/kubeconfig
```

For EKS, model authentication as an Atmos Auth integration linked to the
identity used for the command. For example, an AWS identity can use an
`aws/eks` integration to resolve the cluster and write a kubeconfig before
Atmos renders, diffs, applies, deploys, or deletes the Kubernetes component.

```yaml
auth:
  identities:
    platform-admin:
      kind: aws

  integrations:
    prod/eks:
      kind: aws/eks
      via:
        identity: platform-admin
      spec:
        cluster:
          name: acme-prod-eks
          region: us-east-1
          kubeconfig:
            path: /tmp/acme-prod-kubeconfig
            update: replace
```

See the [EKS kubeconfig authentication tutorial](/tutorials/eks-kubeconfig-authentication)
and [`atmos aws eks update-kubeconfig`](/cli/commands/aws/eks/update-kubeconfig)
for the full integration contract.

## Bulk And Affected Runs

Run Kubernetes changes across more than one component when you need a full
environment rollout or a CI job scoped to changed components:

- **`--all`**
  Process all Kubernetes components in dependency order. Use 
  `--stack`
   to scope execution to one stack.
- **`--affected`**

  Select changed Kubernetes components using affected detection, include their
  dependencies, then process them in dependency order.
- **`--include-dependents`**
  When used with 
  `--affected`
  , also include downstream Kubernetes components that depend on the affected components.
- **`--tags`**

  Filter by tags (comma-separated, matches any): `--tags=production,tier-1`. Components are tagged via an optional `metadata.tags: [...]` list in stack manifests. Composes with `--all`/`--affected` to narrow the selected set further; cannot be combined with a single component argument.
- **`--labels`**

  Filter by labels (comma-separated `key=value` or `key:value` pairs, matches all): `--labels=cost-center=platform,compliance=sox`. Components are labeled via an optional `metadata.labels: {...}` map in stack manifests. Composes with `--all`/`--affected`/`--tags`; cannot be combined with a single component argument.

Affected detection supports `--base`, `--ref`, `--sha`, `--repo-path`,
`--clone-target-ref`, `--ssh-key`, and `--ssh-key-password`, matching
`atmos describe affected`.

## Hooks

Kubernetes commands support dotted lifecycle hook events:

```yaml
components:
  kubernetes:
    argocd:
      hooks:
        notify:
          events:
            - after.kubernetes.apply
          command: echo "applied argocd"
```

Supported events include `before.kubernetes.render`, `after.kubernetes.render`,
`before.kubernetes.validate`, `after.kubernetes.validate`,
`before.kubernetes.plan`, `after.kubernetes.plan`, `before.kubernetes.diff`,
`after.kubernetes.diff`, `before.kubernetes.apply`, `after.kubernetes.apply`,
`before.kubernetes.deploy`, `after.kubernetes.deploy`,
`before.kubernetes.delete`, and `after.kubernetes.delete`.

---

## atmos kubernetes validate

Catch broken manifests before they reach the cluster. `validate` renders the
component and checks the resulting Kubernetes objects — confirming every object
has a valid `apiVersion`/`kind`, a present and well-formed `metadata.name`, and
a resolvable group/version/kind. With `--server`, it additionally validates the
objects against the live cluster using a server-side dry-run apply.

**Configure Kubernetes Components**

`validate` uses the same stack inputs as the rest of the lifecycle:
`provider`, `paths`, `manifests`, `vars`, `env`, Auth, hooks, and
dependencies.

Stack Configuration[Read more](/stacks/components/kubernetes)

## Usage

```shell
atmos kubernetes validate  --stack  [options]
atmos kubernetes validate --affected --base origin/main
```

`validate` resolves the component stack configuration, renders the final
Kubernetes manifests (the same way [`render`](/cli/commands/kubernetes/render)
does), and then validates the rendered objects.

By default, validation is **offline** — it does not contact the Kubernetes API
server. It reports every invalid object in a single run (it does not stop at the
first failure) and exits non-zero if any object fails.

When every object fails fast matters most: the same offline structural checks
run automatically before [`apply`](/cli/commands/kubernetes/apply) and
[`deploy`](/cli/commands/kubernetes/deploy), so a malformed manifest is rejected
before anything is sent to the cluster or delivered to a provision target.

## Examples

Validate a single component offline:

```shell
atmos kubernetes validate argocd -s plat-ue2-dev
```

Validate against the live cluster with a server-side dry-run apply:

```shell
atmos kubernetes validate argocd -s plat-ue2-dev --server
```

Validate all Kubernetes components in dependency order:

```shell
atmos kubernetes validate --all -s plat-ue2-dev
```

Validate affected Kubernetes components:

```shell
atmos kubernetes validate --affected --base origin/main
```

Validate components filtered by tags or labels (composes with `--all`/`--affected` to narrow the selected set further):

```shell
atmos kubernetes validate --all --tags production,tier-1
atmos kubernetes validate --affected --labels cost-center=platform
```

## What is validated

Offline (default):

- `apiVersion` and `kind` are present (also enforced during rendering).
- `metadata.name` is present and is a valid DNS-1123 subdomain (with a
  [Kustomize-specific exemption](#kustomize-config-objects) for `Kustomization`/`Component` objects).
- The object resolves to a non-empty group/version/kind.

With `--server`:

- Each object is sent to the Kubernetes API server as a server-side dry-run
  apply, surfacing schema errors (unknown or mistyped fields) and missing
  Custom Resource Definitions authoritatively. Requires a reachable cluster and
  a configured kubeconfig.

:::caution Bootstrapping a namespace with `--server`
Each object's server-side dry-run is evaluated independently against the
cluster's _currently persisted_ state. If a manifest set creates its own
namespace and also delivers objects into that namespace in the same batch —
the common pattern for a first deploy — `--server` reports the dependent
objects as invalid (`namespaces "my-namespace" not found`) even though the
manifest set is entirely valid and `apply`/`deploy` (a real, non-dry-run
apply) succeeds without issue. This is inherent to how the Kubernetes API
server evaluates dry-run requests, not an Atmos-specific limitation. If you
hit this, apply the namespace first (or without `--server`) and re-run
`--server` once it exists.
:::

### Kustomize config objects

Kustomize's own `Kustomization` and `Component` objects are not Kubernetes API
resources — they are local input consumed by the `kustomize` build tool
itself, and Kustomize does not require (or, historically, even permit) a
`metadata.name` on them. `validate` recognizes these two exact, versioned
pairs and does not require `metadata.name` for them:

- `apiVersion: kustomize.config.k8s.io/v1beta1`, `kind: Kustomization`
- `apiVersion: kustomize.config.k8s.io/v1alpha1`, `kind: Component`

A `Kustomization`/`Component` object at a different `apiVersion` is not
recognized and still requires `metadata.name`. Every other offline check (a
resolvable group/version/kind, and DNS-1123 validity for a name that _is_
given) still applies regardless. See
[Generating a Kustomize component for GitOps](/stacks/components/kubernetes#generating-a-kustomize-component-for-gitops)
for the pattern this supports.

### Disabling validation

Set `validate: false` on a component to opt out of both the offline checks
above and the automatic pre-apply/deploy gate for that component entirely:

```yaml
components:
  kubernetes:
    legacy-manifests:
      validate: false
      manifests:
        - apiVersion: v1
          kind: ConfigMap
          # ...
```

This is a manual override for manifests Atmos has no reserved-kind knowledge
of — for example objects owned by another tool's format. It does not affect
`--server`, which validates against the live cluster's own API rather than
Atmos's offline opinion.

## Flags

- **`--stack`, `-s` (required)**
  Atmos stack.
- **`--server` (optional)**

  Also validate the rendered manifests against the live cluster using a
  server-side dry-run apply. Requires a reachable cluster and kubeconfig.
- **`--all` (optional)**
  Validate all Kubernetes components in dependency order.
- **`--affected` (optional)**
  Validate affected Kubernetes components and their dependencies.
- **`--base` (optional)**
  Base branch, tag, or commit used when selecting affected components.
- **`--include-dependents` (optional)**
  With 
  `--affected`
  , include dependent Kubernetes components.
- **`--tags` (optional)**

  Filter by tags (comma-separated, matches any): `--tags=production,tier-1`. Composes with `--all`/`--affected` to narrow the selected set further; cannot be combined with a single component argument.
- **`--labels` (optional)**

  Filter by labels (comma-separated `key=value` or `key:value` pairs, matches all): `--labels=cost-center=platform,compliance=sox`. Composes with `--all`/`--affected`/`--tags`; cannot be combined with a single component argument.

---

## atmos list affected

Use this command to list affected Atmos components and stacks between Git commits. View results in multiple formats including tables, JSON, YAML, CSV, or hierarchical trees with custom column selection, filtering, and sorting.

> ⚠️ Experimental

## Usage

Execute the `list affected` command like this:

```shell
atmos list affected
```

To compare against a specific Git reference:

```shell
atmos list affected --ref refs/heads/main
```

:::tip
Run `atmos list affected --help` to see all the available options
:::

## Examples

List all affected components:

```shell
atmos list affected
```

Compare against specific branch or commit:

```shell
# Compare against main branch
atmos list affected --ref refs/heads/main

# Compare against specific commit
atmos list affected --sha abc123def456
```

Output in different formats:

```shell
# JSON format
atmos list affected --format json

# YAML format
atmos list affected --format yaml

# CSV format for scripting
atmos list affected --format csv
```

Filter and sort results:

```shell
# Filter by specific stack
atmos list affected --stack prod-us-east-1

# Sort by component name
atmos list affected --sort component:asc

# Sort by multiple columns
atmos list affected --sort "stack:asc,component:desc"
```

Include dependent components:

```shell
# Show components that depend on affected components
atmos list affected --include-dependents
```

Filter by deletion status:

```shell
# Show only deleted components (for destroy workflows)
atmos list affected --format json | jq '[.[] | select(.deleted == true)]'

# Show only modified components (for apply workflows)
atmos list affected --format json | jq '[.[] | select(.deleted != true)]'
```

Custom columns:

```shell
# Simple field names
atmos list affected --columns component,stack,affected

# Named columns with templates
atmos list affected --columns "Component={{ .component }},Stack={{ .stack }}"
```

Working with private repositories:

```shell
# Use SSH key for cloning
atmos list affected --ssh-key ~/.ssh/id_rsa --clone-target-ref

# Use pre-cloned repository (for CI/CD)
atmos list affected --repo-path /tmp/target-repo
```

## Flags

- **`--ref`**
  Git reference to compare against (e.g., 
  `refs/heads/main`
  ). Defaults to 
  `refs/remotes/origin/HEAD`
  .
  Environment variable: 
  `ATMOS_AFFECTED_REF`
- **`--sha`**
  Git commit SHA to compare against. Takes precedence over 
  `--ref`
  .
  Environment variable: 
  `ATMOS_AFFECTED_SHA`
- **`--format` / `-f`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
   (default: 
  `table`
  ).
  Environment variable: 
  `ATMOS_LIST_FORMAT`
- **`--columns`**
  Columns to display. Supports simple field names (e.g., 
  `component,stack`
  ), named columns with templates (e.g., 
  `"Name={{ .component }}"`
  ), or field references (e.g., 
  `"MyStack=stack"`
  ).
  Environment variable: 
  `ATMOS_LIST_COLUMNS`
- **`--sort`**
  Sort by column:order (e.g., 
  `stack:asc,component:desc`
  ). Multiple sort columns separated by comma.
  Environment variable: 
  `ATMOS_LIST_SORT`
- **`--delimiter`**
  Delimiter for CSV/TSV output (default: 
  `,`
   for CSV, 
  `\t`
   for TSV).
  Environment variable: 
  `ATMOS_LIST_DELIMITER`
- **`--stack`**
  Filter results to a specific stack.
  Environment variable: 
  `ATMOS_STACK`
- **`--include-dependents`**
  Include dependent components as separate rows with depth indicators.
  Environment variable: 
  `ATMOS_AFFECTED_INCLUDE_DEPENDENTS`
- **`--exclude-locked`**
  Exclude components with 
  `metadata.locked: true`
   from results.
  Environment variable: 
  `ATMOS_AFFECTED_EXCLUDE_LOCKED`
- **`--repo-path`**
  Path to already-cloned target repository. Conflicts with 
  `--ref`
  , 
  `--sha`
  , 
  `--ssh-key`
  , and 
  `--ssh-key-password`
  .
  Environment variable: 
  `ATMOS_AFFECTED_REPO_PATH`
- **`--ssh-key`**
  Path to PEM-encoded private key for SSH cloning.
  Environment variable: 
  `ATMOS_AFFECTED_SSH_KEY`
- **`--ssh-key-password`**
  Password for encrypted PEM key.
  Environment variable: 
  `ATMOS_AFFECTED_SSH_KEY_PASSWORD`
- **`--clone-target-ref`**
  Clone target reference instead of checking it out locally (default: 
  `false`
  ). Required for private repositories when SSH credentials are provided.
  Environment variable: 
  `ATMOS_AFFECTED_CLONE_TARGET_REF`
- **`--process-templates`**
  Enable/disable Go template processing (default: 
  `true`
  ).
  Environment variable: 
  `ATMOS_AFFECTED_PROCESS_TEMPLATES`
- **`--process-functions`**
  Enable/disable YAML function processing (default: 
  `true`
  ).
  Environment variable: 
  `ATMOS_AFFECTED_PROCESS_FUNCTIONS`
- **`--skip`**
  Skip specific YAML functions. Use multiple 
  `--skip`
   flags or comma-separated values.
  Environment variable: 
  `ATMOS_AFFECTED_SKIP`
- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before listing affected components. Required when YAML functions need credentials.
  Environment variable: 
  `ATMOS_IDENTITY`

## Default Columns

The default table output shows:

| Column | Description |
|--------|-------------|
| Status | Visual indicator: `●` (enabled), `◐` (locked), `○` (disabled) |
| Component | Affected component name |
| Stack | Affected stack name |
| Type | Component type (`terraform`, `helmfile`, etc.) |
| Affected | Reason for being affected (`component`, `file`, `folder`, `stack.vars`, etc.) |
| File | External file that changed (if applicable) |

## Available Fields

All fields from [`atmos describe affected`](/cli/commands/describe/affected) are available for custom columns:

- `component` - Component name
- `component_type` - Component type (terraform/helmfile/packer)
- `component_path` - Filesystem path to component
- `stack` - Stack name
- `stack_slug` - Stack-component concatenation
- `namespace`, `tenant`, `environment`, `stage` - Stack naming parts
- `affected` - Primary reason for being affected
- `affected_all` - All reasons (comma-separated)
- `file` - External file that changed
- `folder` - External folder that changed
- `spacelift_stack` - Spacelift stack name
- `atlantis_project` - Atlantis project name
- `enabled` - Component enabled status
- `locked` - Component locked status
- `status` - Visual status indicator
- `is_dependent` - Whether component is a dependent
- `depth` - Dependency nesting depth
- `dependents_count` - Number of dependent components
- `deleted` - Whether the component was deleted (boolean)
- `deletion_type` - Type of deletion: `component` or `stack`

## Detecting Deleted Components

The `list affected` command automatically detects components and stacks that exist in the BASE branch
but have been deleted in HEAD. This enables CI/CD pipelines to trigger `terraform destroy` for removed infrastructure.

Deleted components are identified by:

- `affected: deleted` - Component was removed from a stack
- `affected: deleted.stack` - Entire stack was deleted
- `deleted: true` - Boolean flag for easy filtering
- `deletion_type: component` or `deletion_type: stack` - Type of deletion

See [`atmos describe affected`](/cli/commands/describe/affected#detecting-deleted-components-in-affected-stacks) for detailed documentation on deleted detection.

## Comparison with `atmos describe affected`

| Feature | `list affected` | `describe affected` |
|---------|-----------------|---------------------|
| **Default format** | Table | JSON |
| **Formats supported** | table, json, yaml, csv, tsv | json, yaml |
| **Custom columns** | Yes | No |
| **Sorting** | Yes (multi-column) | No |
| **Status indicator** | Yes (●/◐/○) | No |
| **Dependencies** | Flattened rows | Nested structure |
| **Use case** | Quick overview, human-readable | Full details, machine processing |

Use `list affected` when you want a quick, scannable view of what changed. Use `describe affected` when you need the complete data structure for automation or detailed analysis.

## Example Output

```shell
┌────────┬─────────────────────┬──────────────────┬──────────┬──────────┬────────────────────────────────────────┐
│ Status │ Component           │ Stack            │ Type     │ Affected │ File                                   │
├────────┼─────────────────────┼──────────────────┼──────────┼──────────┼────────────────────────────────────────┤
│ ●      │ vpc                 │ plat-ue2-dev     │ terraform│ component│                                        │
│ ●      │ vpc                 │ plat-ue2-prod    │ terraform│ component│                                        │
│ ●      │ eks                 │ plat-ue2-dev     │ terraform│ stack.vars│                                       │
│ ◐      │ rds                 │ plat-ue2-staging │ terraform│ file     │ modules/rds/main.tf                    │
└────────┴─────────────────────┴──────────────────┴──────────┴──────────┴────────────────────────────────────────┘
```

## Related Commands

- [`atmos describe affected`](/cli/commands/describe/affected) - Get detailed affected components in JSON/YAML format
- [`atmos list stacks`](/cli/commands/list/stacks) - List all stacks
- [`atmos list components`](/cli/commands/list/components) - List all components
- [`atmos list instances`](/cli/commands/list/list-instances) - List all component instances

---

## atmos list components

Use this command to list all components in your Atmos configuration, optionally filtering by stack. View components in multiple output formats including tables, JSON, YAML, and CSV.

_\[Video: atmos list components]_

## Usage

Execute the `list components` command like this:

```shell
atmos list components
```

This command lists Atmos components in all stacks or in a specified stack:

```shell
atmos list components -s 
```

:::tip
Run `atmos list components --help` to see all the available options
:::

## Examples

```shell
atmos list components
atmos list components -s tenant1-ue2-dev
```

Filter by component type:

```shell
# List only real (non-abstract) components
atmos list components --type real

# List abstract components
atmos list components --type abstract

# List all components including abstract
atmos list components --type all
```

Filter by enabled/locked status:

```shell
# List only enabled components
atmos list components --enabled=true

# List only locked components
atmos list components --locked=true
```

Filter by tags and labels:

```shell
# List components tagged production or tier-1 (matches any)
atmos list components --tags production,tier-1

# List components labeled cost-center=platform (matches all given labels)
atmos list components --labels cost-center=platform

# Combine tags and labels
atmos list components --tags production,tier-1 --labels cost-center=platform
```

Include abstract components:

```shell
# Include abstract components (normally hidden by default)
atmos list components --abstract

# Equivalent: show all component types
atmos list components --type all
```

Output in different formats:

```shell
# JSON format
atmos list components --format json

# YAML format
atmos list components --format yaml

# CSV format with custom delimiter
atmos list components --format csv
```

Custom columns:

```shell
# Simple field names (auto-generates templates)
atmos list components --columns component,stack,type

# Named columns with custom templates
atmos list components --columns "Name={{ .component }},Stack={{ .stack }}"

# Named columns with simple field reference
atmos list components --columns "MyStack=stack,MyType=type"

# Mix of formats
atmos list components --columns component,"Status={{ if .enabled }}Active{{ else }}Disabled{{ end }}"
```

Sort results:

```shell
# Sort by component name ascending
atmos list components --sort component:asc

# Sort by multiple columns
atmos list components --sort "stack:asc,component:desc"
```

Query with YQ expressions:

```shell
# Simplified syntax - just use select() directly
atmos list components --query 'select(.locked == true)'

# Filter to only terraform components
atmos list components --query 'select(.kind == "terraform")'

# Get components in prod stacks
atmos list components --query 'select(.stack | test("prod"))'

# Get components by name
atmos list components --query 'select(.component == "vpc")'

# Output query results as JSON
atmos list components --query 'select(.kind == "terraform")' --format json

# You can also use the verbose syntax if preferred
atmos list components --query '[.[] | select(.locked == true)]'
```

:::tip Simplified Query Syntax
Atmos automatically normalizes YQ queries for convenience:

- `select(.locked == true)` → automatically becomes `[.[] | select(.locked == true)]`
- `.[] | select(...)` → automatically wrapped in `[...]` to prevent YAML parsing issues

This means you can write simple `select()` expressions without worrying about the verbose syntax.

**Note:** Scalar extraction queries like `.[].component` are not supported. Use `atmos describe component --query` for field extraction, or use `select(...)` to filter components.
:::

## Flags

- **`--stack` / `-s`**
  Filter by stack name pattern (supports glob patterns like 
  `plat-*-prod`
  ).
  Environment variable: 
  `ATMOS_STACK`
- **`--format` / `-f`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
  , 
  `tree`
  . Overrides 
  `components.list.format`
   configuration in atmos.yaml (default: 
  `table`
  ).
  Environment variable: 
  `ATMOS_LIST_FORMAT`
- **`--columns`**
  Columns to display. Supports simple field names (e.g., 
  `component,stack,type`
  ), named columns with templates (e.g., 
  `"Name={{ .component }}"`
  ), or named with field reference (e.g., 
  `"MyStack=stack"`
  ). Overrides 
  `components.list.columns`
   configuration in atmos.yaml. Environment variable: 
  `ATMOS_LIST_COLUMNS`
- **`--type` / `-t`**
  Component type: 
  `real`
  , 
  `abstract`
  , 
  `all`
   (default: 
  `real`
  ).
  Environment variable: 
  `ATMOS_COMPONENT_TYPE`
- **`--abstract`**
  Include abstract components in output. Equivalent to 
  `--type all`
  .
  Environment variable: 
  `ATMOS_ABSTRACT`
- **`--enabled`**
  Filter by enabled status (omit for all, 
  `--enabled=true`
   for enabled only, 
  `--enabled=false`
   for disabled only). Because 
  `list components`
   returns each component deduplicated across stacks, a component is reported as 
  `enabled=false`
   if it is disabled in 
  **any**
   of its stack instances. To see per-stack-instance state, use 
  [`atmos list instances`](/cli/commands/list/list-instances)
  .
  Environment variable: 
  `ATMOS_COMPONENT_ENABLED`
- **`--locked`**
  Filter by locked status (omit for all, 
  `--locked=true`
   for locked only, 
  `--locked=false`
   for unlocked only). A deduplicated component is reported as 
  `locked=true`
   if it is locked in 
  **any**
   of its stack instances. Use 
  [`atmos list instances`](/cli/commands/list/list-instances)
   for per-stack-instance state.
  Environment variable: 
  `ATMOS_COMPONENT_LOCKED`
- **`--sort`**
  Sort by column:order (e.g., 
  `component:asc,stack:desc`
  ). Multiple sort columns separated by comma.
  Environment variable: 
  `ATMOS_LIST_SORT`
- **`--query` / `-q`**
  Filter results using YQ expressions. Supports simplified syntax like 
  `select(.locked == true)`
   which is automatically normalized. See the 
  [YQ documentation](https://mikefarah.gitbook.io/yq)
   for expression syntax.
  Environment variable: 
  `ATMOS_LIST_QUERY`
- **`--tags`**
  Filter by tags (comma-separated, matches any): 
  `--tags=production,tier-1`
  . Components are tagged via an optional 
  `metadata.tags: [...]`
   list in stack manifests.
  Environment variable: 
  `ATMOS_COMPONENT_TAGS`
- **`--labels`**
  Filter by labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--labels=cost-center=platform,compliance:sox`
  . Components are labeled via an optional 
  `metadata.labels: {...}`
   map in stack manifests.
  Environment variable: 
  `ATMOS_COMPONENT_LABELS`
- **`--include-dependencies`**
  Expand the listing to everything the selected components depend on (their prerequisites) — previewing the exact set a terraform bulk command with the same selection flags would execute. The closure covers Terraform components (the dependency graph the scheduler executes) and is evaluated with this command's own 
  `--process-templates`
  /
  `--process-functions`
   settings. Accepts an optional depth (for example, 
  `--include-dependencies=1`
   for direct dependencies only).
  `atmos list components --labels=env=dev --include-dependencies`
  Environment variable: 
  `ATMOS_INCLUDE_DEPENDENCIES`
- **`--include-dependents`**
  Expand the listing to everything that depends on the selected components — previewing the exact set a terraform bulk command with the same selection flags would execute. Accepts an optional depth (for example, 
  `--include-dependents=2`
   for two levels).
  Environment variable: 
  `ATMOS_INCLUDE_DEPENDENTS`
- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before listing components.
  This is required when stack configurations use YAML template functions
  (e.g., 
  `!terraform.state`
  , 
  `!terraform.output`
  ) that require authentication.
  `atmos list components --identity my-aws-identity`
  Environment variable: 
  `ATMOS_IDENTITY`
- **`--process-templates`**
  Enable/disable Go template processing in Atmos stack manifests (default 
  `true`
  ). Go template functions include 
  `atmos.Component(...)`
  .
  Environment variable: 
  `ATMOS_PROCESS_TEMPLATES`
- **`--process-functions`**
  Enable/disable YAML functions processing in Atmos stack manifests (default 
  `true`
  ). YAML functions include 
  `!terraform.state`
  , 
  `!terraform.output`
  , 
  `!store`
  , 
  `!aws.*`
  , etc. This is distinct from Go template functions like 
  `atmos.Component(...)`
  , which are controlled by 
  `--process-templates`
  .
  Environment variable: 
  `ATMOS_PROCESS_FUNCTIONS`
- **`--skip`**
  Skip executing a specific YAML function in the Atmos stack manifests when processing stacks. Repeat the flag to skip multiple functions (for example, 
  `--skip terraform.state --skip terraform.output`
  ). Use this to bypass a single function (such as a backend-resolving 
  `!terraform.state`
   call) while leaving other YAML functions enabled.
  Environment variable: 
  `ATMOS_SKIP`
- **`--on-error`**
  How to handle a recoverable YAML-function error, such as a Terraform backend that has not been provisioned yet (default 
  `strict`
  ). 
  `strict`
   fails the command on the first such error. 
  `warn`
   substitutes 
  `null`
   for the unresolved value, prints a warning naming the stack/component/function, and continues processing the rest of the components. Errors unrelated to backend provisioning (auth failures, malformed YAML, etc.) still fail the command in either mode.
  Environment variable: 
  `ATMOS_LIST_ON_ERROR`

## Configuration

You can customize the default output format and columns displayed by `atmos list components` in your `atmos.yaml`:

### Default Format

```yaml
# atmos.yaml
components:
  list:
    format: yaml  # Default format: table, json, yaml, csv, tsv
```

**Precedence**: CLI `--format` flag > Config file > Environment variable `ATMOS_LIST_FORMAT` > Default (`table`)

### Custom Columns

```yaml
# atmos.yaml
components:
  list:
    format: table
    columns:
      - name: Component
        value: "{{ .component }}"
      - name: Stack
        value: "{{ .stack }}"
      - name: Kind
        value: "{{ .kind }}"
      - name: Type
        value: "{{ .type }}"
      - name: Description
        value: "{{ .metadata.description }}"
```

### Available Template Fields

Column `value` fields support Go template syntax with access to:

- **`.component`**
  Component name
- **`.stack`**
  Stack name
- **`.kind`**
  Component runner type (
  `terraform`
  , 
  `helmfile`
  , 
  `packer`
  )
- **`.type`**
  Component type from metadata (
  `real`
   or 
  `abstract`
  )
- **`.component_path`**
  Filesystem path to the component source directory
- **`.status`**
  Colored status indicator (● green=enabled, red=locked, gray=disabled)
- **`.status_text`**
  Status as text (
  `enabled`
  , 
  `disabled`
  , 
  `locked`
  )
- **`.enabled`**
  Whether component is enabled (boolean)
- **`.locked`**
  Whether component is locked (boolean)
- **`.vars`**
  All component variables (e.g., 
  `.vars.region`
  , 
  `.vars.tenant`
  )
- **`.settings`**
  Component settings
- **`.metadata`**
  Full metadata map (e.g., 
  `.metadata.description`
  , 
  `.metadata.component`
  )
- **`.data`**
  Full component data for advanced templates

### Template Functions

Columns support template functions for data transformation:

```yaml
components:
  list:
    columns:
      - name: Component (Upper)
        value: "{{ .component | upper }}"
      - name: Region
        value: "{{ .vars.region | default \"N/A\" }}"
      - name: Status
        value: "{{ if .enabled }}Enabled{{ else }}Disabled{{ end }}"
      - name: Short Description
        value: "{{ .metadata.description | truncate 50 }}"
```

Available functions:

- `upper`, `lower` - String case conversion
- `truncate` - Truncate string with ellipsis
- `len` - Length of arrays/strings
- `default` - Provide default value if empty
- `toString` - Convert value to string
- `ternary` - Conditional expression

### Override Columns via CLI

Override configured columns using the `--columns` flag. The flag supports multiple formats:

**Simple field names** (auto-generates templates and title-case names):

```shell
# Display component, stack, and type columns
atmos list components --columns component,stack,type

# Include enabled status
atmos list components --columns component,stack,enabled
```

**Named columns with templates** (full control over display name and value):

```shell
# Custom column names with templates
atmos list components --columns "Name={{ .component }},Stack={{ .stack }}"

# Complex templates with conditionals
atmos list components --columns "Status={{ if .enabled }}Active{{ else }}Disabled{{ end }}"
```

**Named columns with field reference** (auto-wraps field in template):

```shell
# Shorthand: Name=field becomes Name={{ .field }}
atmos list components --columns "MyComponent=component,MyStack=stack"
```

**Mixed formats**:

```shell
# Combine simple fields and named columns
atmos list components --columns component,"CustomType={{ .type | upper }}"
```

## Related Commands

- [`atmos list instances`](/cli/commands/list/list-instances) - List all component instances across stacks
- [`atmos list stacks`](/cli/commands/list/stacks) - List all stacks
- [`atmos describe component`](/cli/commands/describe/component) - Get detailed component configuration

---

## atmos list dependencies

Use this command to visualize the dependency relationships between Atmos components across stacks. By default it renders a tree showing both directions for every component — what each component depends on, and what depends on it. Dependencies are read from both `dependencies.components` (preferred) and the legacy `settings.depends_on`, so the output stays consistent with [`atmos describe dependents`](/cli/commands/describe/dependents).

## Usage

Execute the `list dependencies` command like this:

```shell
atmos list dependencies
```

Focus on a single component in a stack by passing the component as a positional argument:

```shell
atmos list dependencies  --stack 
```

:::tip
Run `atmos list dependencies --help` to see all the available options.
:::

## Examples

List dependencies for every component (tree, both directions):

```shell
atmos list dependencies
```

Limit to a single stack:

```shell
atmos list dependencies --stack plat-ue2-dev
```

Focus on a single component:

```shell
atmos list dependencies vpc --stack plat-ue2-dev
```

Show only one direction:

```shell
# What the component depends on (its prerequisites)
atmos list dependencies vpc --stack plat-ue2-dev --direction forward

# What depends on the component (its dependents)
atmos list dependencies vpc --stack plat-ue2-dev --direction reverse
```

Output as structured data:

```shell
# JSON
atmos list dependencies --format json

# YAML
atmos list dependencies --format yaml
```

## Tree Output

The default `tree` format keeps stack context next to the component dependency
tree. The `Component` column carries the hierarchy, while `Type` is secondary
metadata on the right. Repeated metadata is omitted on child rows unless the
stack or type changes.

```text
Dependencies
Stack         Component                 Type
plat-ue2-dev  app-config                terraform
              ├──depends on ↓
              │  ├──▶ dynamodb-table
              │  └──▶ kms-key
              └──required by ↑
                 └──(none)

plat-ue2-dev  kms-key                   terraform
              ├──depends on ↓
              │  └──(none)
              └──required by ↑
                 └──◀ app-config
```

The filled triangle marker shows edge direction:

- `▶` marks a forward dependency edge: the selected component depends on that child.
- `◀` marks a reverse dependency edge: that child depends on the selected component.

When you select a single direction with `--direction forward` or
`--direction reverse`, the dependency subtree is attached directly under each
component (no `depends on` / `required by` branch labels).

Circular dependencies are detected and marked with `(circular reference)` so the
tree always terminates instead of recursing forever — unlike the execution
dependency graph used by `terraform --affected`, this command tolerates cycles so
they can be inspected.

## Levels Output

The `levels` format shows each component's shortest dependency-graph distance
from the selected root(s). It lists every reachable component ordered by distance:

```text
Level  Stack         Component       Type
0      plat-ue2-dev  app             terraform
1      plat-ue2-dev  database        terraform
1      plat-ue2-dev  vpc             terraform
2      plat-ue2-dev  kms-key         terraform
```

With `--direction forward`, each level is a deployment prerequisite distance.
`--direction reverse` follows dependents. With the default `--direction both`,
`Level` is the shortest path in either direction. Components selected by
the positional `component` argument, `--stack`, `--tags`, or `--labels` start
at level `0`.

## Arguments

- **`component` (optional)**
  Limit the top-level entries to a single component. Combine with 
  `--stack`
   to target one specific component instance.

## Flags

- **`--direction` / `-d`**
  Dependency direction to show: 
  `both`
   (default), 
  `forward`
   (what the component depends on), or 
  `reverse`
   (what depends on the component).
  Environment variable: 
  `ATMOS_LIST_DIRECTION`
- **`--stack` / `-s`**
  Filter the top-level entries to a single stack. Cross-stack dependency edges are still resolved and displayed.
  Environment variable: 
  `ATMOS_STACK`
- **`--tags`**
  Filter by component tags (comma-separated, matches any): 
  `--tags=production,tier-1`
  . Components are tagged via an optional 
  `metadata.tags: [...]`
   list in stack manifests. Like 
  `--stack`
  , this scopes which components appear as top-level entries; dependency subtrees still traverse the full graph.
  Environment variable: 
  `ATMOS_COMPONENT_TAGS`
- **`--labels`**
  Filter by component labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--labels=cost-center=platform,compliance:sox`
  . Components are labeled via an optional 
  `metadata.labels: {...}`
   map in stack manifests. Like 
  `--stack`
  , this scopes which components appear as top-level entries; dependency subtrees still traverse the full graph.
  Environment variable: 
  `ATMOS_COMPONENT_LABELS`
- **`--format` / `-f`**
  Output format: 
  `tree`
   (default), 
  `json`
  , 
  `yaml`
  , or 
  `levels`
  .
  Environment variable: 
  `ATMOS_LIST_DEPENDENCIES_FORMAT`
- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before listing dependencies.
  This is required when stack configurations use YAML template functions
  (e.g., 
  `!terraform.state`
  , 
  `!terraform.output`
  ) that require authentication.
  Environment variable: 
  `ATMOS_IDENTITY`
- **`--process-templates`**
  Enable/disable Go template processing in Atmos stack manifests (default 
  `true`
  ). Templates are processed so that cross-stack dependency references using templated stack names (e.g., 
  `stack: "ue1-{{ .vars.stage }}"`
  ) resolve correctly.
  Environment variable: 
  `ATMOS_PROCESS_TEMPLATES`
- **`--process-functions`**
  Enable/disable YAML functions processing in Atmos stack manifests (default 
  `true`
  ). YAML functions include 
  `!terraform.state`
  , 
  `!terraform.output`
  , 
  `!store`
  , 
  `!aws.*`
  , etc.
  Environment variable: 
  `ATMOS_PROCESS_FUNCTIONS`
- **`--skip`**
  Skip executing a specific YAML function when processing stacks. Repeat the flag to skip multiple functions (for example, 
  `--skip terraform.state --skip terraform.output`
  ).
  Environment variable: 
  `ATMOS_SKIP`

## Related Commands

- [`atmos describe dependents`](/cli/commands/describe/dependents) - Show the components that depend on a given component
- [`atmos list components`](/cli/commands/list/components) - List all components
- [`atmos list stacks`](/cli/commands/list/stacks) - List all stacks, optionally as a tree with import provenance

---

## atmos list editions

Use this command to list the editions journal: every change to a previously shipped default, with the date it changed, the old and new values, and a description of the change. Use `--from` and `--to` to diff two editions and see exactly which defaults differ between them.

> ⚠️ Experimental

**Pin Your Project to an Edition**

Learn how to pin your project's defaults to a date anchor with the top-level `edition` setting in your atmos.yaml.

Configuration Reference[Read more](/cli/configuration/edition)

## Usage

```shell
atmos list editions [flags]
```

## Description

Atmos journals every change to a previously shipped default. The `atmos list editions` command displays that journal, newest first, where each row shows:

- **Date** — the day the default changed
- **Key** — the configuration key whose default changed
- **Old** — the default before the change (what an earlier-pinned project gets)
- **New** — the default after the change
- **Description** — what the change means in practice

This command is useful for:

- Deciding which [`edition`](/cli/configuration/edition) to pin your project to
- Reviewing what changes when you unpin (or move your pin forward): `atmos list editions --from=`
- Diffing two editions before an upgrade: `atmos list editions --from= --to=`

## Flags

- **`--from string`**
  Show only changes after this edition anchor. Anchors accept 
  `YYYY`
  , 
  `YYYY-MM`
  , or 
  `YYYY-MM-DD`
  ; partial dates round to the end of the period they name.
- **`--to string`**
  Show only changes up to and including this edition anchor. Same anchor formats as 
  `--from`
  .
- **`--format, -f string`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
   (default: 
  `table`
  )

## Examples

List every journaled default change:

```shell
atmos list editions
```

Diff two editions — show only the default changes that took effect between the 2025 and 2026 editions:

```shell
atmos list editions --from=2025 --to=2026
```

Answer "what changes if I unpin?" — show everything that changed after your current pin:

```shell
atmos list editions --from=2025-09
```

Output in machine-readable formats:

```shell
atmos list editions --format=json
atmos list editions --format=yaml
atmos list editions --format=csv
```

## Example Output

```shell
> atmos list editions

 Date        Key                            Old          New          Description
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 2026-07-17  describe.component.filter      full         schema       Component descriptions show only stack-manifest sections; set the filter to full for computed internals.
 2026-07-16  describe.provenance            false        true         Component descriptions include provenance annotations (which stack file set each value) by default.
 2026-07-13  list.error_mode                strict       warn         List commands substitute (computed) for unresolved YAML function values and continue instead of aborting.
 2026-07-13  describe.error_mode            strict       warn         Describe commands substitute (computed) for unresolved YAML function values and continue instead of aborting.
 2026-07-06  settings.terminal.help.filter  false        true         Bare --help shows a focused view without the GLOBAL FLAGS section; --help=all prints the full output.
 2026-02-10  components.helmfile.use_eks    true         false        Helmfile EKS integration is opt-in; kubeconfig is no longer downloaded automatically before Helmfile commands.
 2025-12-06  stacks.inherit.metadata        false        true         Component metadata is inherited from base components like vars and settings; previously metadata was per-component only.
 2025-10-16  settings.terminal.pager        true         false        The built-in pager is disabled by default; long output prints directly to the terminal.
 2025-09-23  logs.level                     Info         Warning      The default log level is Warning; informational log messages are hidden unless requested.
 2025-02-11  logs.file                      /dev/stdout  /dev/stderr  Logs are written to stderr so they never contaminate pipeable command output on stdout.

10 default changes journaled. Pin with `edition:` in atmos.yaml.
```

:::tip
Use `atmos describe edition` to see which of these changes an active pin actually rolls back for your project.
:::

## See Also

- [`edition` configuration](/cli/configuration/edition) — Pin your project's defaults to a date anchor
- [`atmos describe edition`](/cli/commands/describe/edition) — Show the active pin and every default it rolls back

---

## atmos list instances

Use this command to list all component instances across stacks, showing each unique component-stack combination. Upload instance metadata to Atmos Pro for centralized tracking and management.

## Usage

```shell
atmos list instances [flags]
```

## Description

The `atmos list instances` command displays all component instances defined across your Atmos stacks. Each instance represents a unique combination of a component and stack. This command is useful for:

- Getting an overview of all deployed/configured infrastructure
- Finding specific component instances across stacks
- Filtering instances by stack pattern or custom criteria
- Uploading instance inventory to Atmos Pro for centralized management

## Flags

- **`--format` / `-f`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
  , 
  `tree`
  , 
  `matrix`
  . Overrides 
  `list.instances.format`
   configuration in atmos.yaml (default: 
  `table`
  ).
- **`--delimiter`**
  Delimiter for CSV/TSV output (default: tab for tsv, comma for csv)
- **`--provenance`**
  Show import provenance in tree format. Only works with 
  `--format=tree`
  . Displays the import hierarchy showing which files each component inherits from.
- **`--columns`**
  Columns to display (comma-separated). Overrides 
  `components.list.columns`
   configuration in atmos.yaml
- **`--stack` / `-s`**
  Filter by stack pattern (supports glob patterns, e.g., 
  `plat-*-prod`
  )
- **`--tags`**
  Filter by component tags (comma-separated, matches any): 
  `--tags=production,tier-1`
  . Components are tagged via an optional 
  `metadata.tags: [...]`
   list in stack manifests. Not supported with 
  `--format=tree`
  , 
  `--format=matrix`
  , or 
  `--upload`
   (the Atmos Pro inventory upload is always unfiltered).
  Environment variable: 
  `ATMOS_COMPONENT_TAGS`
- **`--labels`**
  Filter by component labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--labels=cost-center=platform,compliance:sox`
  . Components are labeled via an optional 
  `metadata.labels: {...}`
   map in stack manifests. Not supported with 
  `--format=tree`
  , 
  `--format=matrix`
  , or 
  `--upload`
   (the Atmos Pro inventory upload is always unfiltered).
  Environment variable: 
  `ATMOS_COMPONENT_LABELS`
- **`--include-dependencies`**
  Expand the listing to the instance rows for everything the selected components depend on (their prerequisites) — previewing the exact set a terraform bulk command with the same selection flags would execute. The closure covers Terraform components (the dependency graph the scheduler executes) and is evaluated with this command's own 
  `--process-templates`
  /
  `--process-functions`
   settings. Accepts an optional depth (for example, 
  `--include-dependencies=1`
   for direct dependencies only). Not supported with 
  `--upload`
  , 
  `--format=matrix`
  , or 
  `--format=tree`
  .
  `atmos list instances --labels=env=dev --include-dependencies`
  Environment variable: 
  `ATMOS_INCLUDE_DEPENDENCIES`
- **`--include-dependents`**
  Expand the listing to the instance rows for everything that depends on the selected components — previewing the exact set a terraform bulk command with the same selection flags would execute. Accepts an optional depth (for example, 
  `--include-dependents=2`
   for two levels). Not supported with 
  `--upload`
  , 
  `--format=matrix`
  , or 
  `--format=tree`
  .
  Environment variable: 
  `ATMOS_INCLUDE_DEPENDENTS`
- **`--filter`**
  YQ predicate evaluated against each instance row. Rows where the expression is truthy are kept (e.g., 
  `.vars.region == "us-east-1"`
  , 
  `.component == "vpc"`
  ). Not supported with 
  `--format=tree`
   or 
  `--format=matrix`
  .
- **`--query` / `-q`**
  YQ expression that projects each row. Scalar results land in a 
  `value`
   column; map results become row keys addressable via 
  `--columns`
   templates (e.g., 
  `--query '.vars.region'`
  , 
  `--query '{"region": .vars.region, "tenant": .vars.tenant}'`
  ). Not supported with 
  `--format=tree`
   or 
  `--format=matrix`
  .
- **`--sort`**
  Sort by column:order (e.g., 
  `stack:asc,component:desc`
  )
- **`--upload`**
  Upload instances to Atmos Pro API (requires Pro configuration). Every real (non-abstract) instance is uploaded — including disabled ones, so Atmos Pro can show them as disabled rather than losing track of them. Before upload, Atmos collapses the enabled hierarchy (
  `metadata.enabled`
   \> 
  `settings.pro.enabled`
   \> 
  `settings.pro.drift_detection.enabled`
  ), so the uploaded values already reflect any outer disable.
- **`--output-file` / `-o`**
  Write output to file in 
  `key=value`
   format (for 
  `$GITHUB_OUTPUT`
  ). Only supported with 
  `--format=matrix`
  .
- **`--process-templates`**
  Enable/disable Go template processing in Atmos stack manifests (default 
  `true`
  ). Go template functions include 
  `atmos.Component(...)`
  .
  Environment variable: 
  `ATMOS_PROCESS_TEMPLATES`
- **`--process-functions`**
  Enable/disable YAML functions processing in Atmos stack manifests (default 
  `true`
  ). YAML functions include 
  `!terraform.state`
  , 
  `!terraform.output`
  , 
  `!store`
  , 
  `!aws.*`
  , etc. This is distinct from Go template functions like 
  `atmos.Component(...)`
  , which are controlled by 
  `--process-templates`
  .
  Environment variable: 
  `ATMOS_PROCESS_FUNCTIONS`
- **`--skip`**
  Skip executing a specific YAML function in the Atmos stack manifests when processing stacks. Repeat the flag to skip multiple functions (for example, 
  `--skip terraform.state --skip terraform.output`
  ). Useful with 
  `--upload`
   to bypass backend-resolving functions while leaving other YAML functions like 
  `!template`
   enabled.
  Environment variable: 
  `ATMOS_SKIP`

## Examples

List all instances (rendered as a table by default):

```shell
atmos list instances
```

List all instances as a tree:

```shell
atmos list instances --format=tree
```

Filter instances by stack pattern:

```shell
# List instances in all production stacks
atmos list instances --stack "*-prod"

# List instances in a specific stack
atmos list instances --stack tenant1-ue2-dev
```

Output in different formats:

```shell
# JSON format for machine processing
atmos list instances --format json

# YAML format for configuration review
atmos list instances --format yaml

# CSV format for spreadsheet compatibility
atmos list instances --format csv
```

Filter instances using YQ expressions:

```shell
# Find all instances in us-east-1 region
atmos list instances --filter '.vars.region == "us-east-1"'

# Find enabled VPC components
atmos list instances --filter '.component == "vpc" and .enabled == true'
```

Project specific fields with `--query`:

```shell
# Pull the region for every instance (scalar projection → `value` column)
atmos list instances --query '.vars.region' --columns 'Stack={{ .stack }},Region={{ .value }}'

# Project multiple fields at once (map projection → addressable keys)
atmos list instances \
  --query '{"region": .vars.region, "tenant": .vars.tenant}' \
  --columns 'Stack={{ .stack }},Tenant={{ .tenant }},Region={{ .region }}'
```

:::note
`--filter` and `--query` operate per-row and require `--format=table`,
`json`, `yaml`, `csv`, or `tsv`. They are rejected with `--format=tree` and
`--format=matrix`, which render different shapes (import hierarchy and
GitHub-Actions matrix JSON, respectively).
:::

Sort instances:

```shell
# Sort by stack name ascending
atmos list instances --sort stack:asc

# Multi-column sort
atmos list instances --sort "stack:asc,component:desc"
```

Upload instances to Atmos Pro:

```shell
atmos list instances --upload
```

The upload includes **every real (non-abstract) instance**, regardless of whether Atmos Pro is enabled for it, so Atmos Pro can keep disabled instances visible (e.g., grey them out in the UI) rather than losing track of them.

Before upload, Atmos collapses the enabled hierarchy so the values it sends already reflect any outer disable:

```
metadata.enabled  >  settings.pro.enabled  >  settings.pro.drift_detection.enabled
```

An outer disable always wins: a component disabled with `metadata.enabled: false` is uploaded as `settings.pro.enabled: false` and `settings.pro.drift_detection.enabled: false`, so Atmos Pro never dispatches it for drift detection.

```yaml
components:
  terraform:
    vpc:
      settings:
        pro:
          enabled: true            # Uploaded as enabled
          drift_detection:
            enabled: true          # Uploaded with drift enabled
    app:
      settings:
        pro:
          enabled: false           # Uploaded as disabled
    db:
      # No pro config — uploaded as disabled
    monitoring:
      metadata:
        enabled: false             # Disabled component…
      settings:
        pro:
          enabled: true
          drift_detection:
            enabled: true          # …collapsed to enabled:false, drift:false before upload
```

On completion, the command reports a tally:

```
Successfully uploaded 4 instances to Atmos Pro API (1 enabled, 3 disabled, 1 drift enabled).
```

The tally reflects the **effective** state — the same values that are uploaded. The `drift enabled` count requires the instance to be effectively Pro-enabled, so an instance with `settings.pro.enabled: false` (or `metadata.enabled: false`) is never counted as drift-enabled even if `settings.pro.drift_detection.enabled: true`.

If the repository has no instances at all, the command prints `No instances found; nothing to upload.` and exits successfully.

View instances in tree format:

```shell
# Tree view without import details
atmos list instances --format tree

# Tree view with import provenance (shows inheritance chain)
atmos list instances --format tree --provenance
```

Output as GitHub Actions matrix JSON:

```shell
# Print matrix JSON to stdout
atmos list instances --format matrix

# Write to $GITHUB_OUTPUT for use in GitHub Actions
atmos list instances --format matrix --output-file=$GITHUB_OUTPUT
```

## Tree Format with Import Provenance

The `tree` format provides a hierarchical view of your component instances organized by stack. When combined with the `--provenance` flag, it shows the complete import chain for each component, making it easy to understand configuration inheritance.

### Tree Format Structure

The tree format displays:

- **Stacks** as top-level nodes
- **Components** as child nodes under each stack
- **Import hierarchy** (when `--provenance` is enabled) showing the chain of stack configuration files

### Import Provenance

When you enable `--provenance`, each component shows its import chain - the sequence of stack configuration files it inherits from. This is particularly useful for:

- **Debugging configuration** - See exactly where each component's configuration comes from
- **Understanding inheritance** - Visualize the complete import chain
- **Auditing changes** - Track which base configurations affect which components
- **Documentation** - Generate visual representations of stack dependencies

Example tree output with provenance:

```
Component Instances
│
├── tenant1-ue2-dev
│   ├── vpc
│   │   ├── stacks/tenant1/ue2/dev
│   │   ├── stacks/tenant1/ue2/_defaults
│   │   └── stacks/catalog/vpc
│   └── eks
│       ├── stacks/tenant1/ue2/dev
│       ├── stacks/tenant1/ue2/_defaults
│       └── stacks/catalog/eks
```

The import chain is shown from most specific (top) to most general (bottom), reflecting how Atmos merges configurations.

## Matrix Format for CI/CD

The `matrix` format produces GitHub Actions-compatible JSON for driving parallel CI/CD jobs. It outputs the same `{"include":[...]}` structure as [`atmos describe affected --format=matrix`](/cli/commands/describe/affected), but includes all instances rather than only changed ones.

Each entry contains `stack`, `component`, `component_path`, and `component_type`:

```shell
atmos list instances --format matrix
```

```json
{"include":[{"stack":"ue1-dev","component":"vpc","component_path":"components/terraform/vpc","component_type":"terraform"}]}
```

### Writing to `$GITHUB_OUTPUT`

Use `--output-file` to write results in `key=value` format for GitHub Actions:

```shell
atmos list instances --format matrix --output-file=$GITHUB_OUTPUT
```

This writes two keys to the file:

```
matrix={"include":[...]}
count=42
```

When `ci.enabled` is true in `atmos.yaml`, `--output-file` is not provided, and `GITHUB_OUTPUT` is available (for example, in GitHub Actions), the output is written to `$GITHUB_OUTPUT` automatically. Outside of GitHub Actions, or when `GITHUB_OUTPUT` is unset, output falls back to stdout.

### GitHub Actions Example

```yaml
jobs:
  enumerate:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.instances.outputs.matrix }}
    steps:
      - uses: actions/checkout@v6
      - name: List all instances
        id: instances
        run: atmos list instances --format=matrix --output-file=$GITHUB_OUTPUT

  deploy:
    needs: enumerate
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJson(needs.enumerate.outputs.matrix) }}
    steps:
      - run: echo "Deploying ${{ matrix.component }} to ${{ matrix.stack }}"
```

## Custom Columns Configuration

You can customize the columns displayed by `atmos list instances` in your `atmos.yaml`:

```yaml
# atmos.yaml
components:
  list:
    columns:
      - name: Stack
        value: "{{ .stack }}"
      - name: Component
        value: "{{ .component }}"
      - name: Tenant
        value: "{{ .vars.tenant }}"
      - name: Environment
        value: "{{ .vars.environment }}"
      - name: Stage
        value: "{{ .vars.stage }}"
      - name: Region
        value: "{{ .vars.region }}"
      - name: Description
        value: "{{ .metadata.description }}"
      - name: Enabled
        value: "{{ .enabled }}"
```

### Available Template Fields

Column `value` fields support Go template syntax with access to:

- `.stack` - Stack name (e.g., `tenant1-ue2-dev`)
- `.component` - Component name (e.g., `vpc`)
- `.atmos_component` - Atmos component identifier
- `.atmos_component_type` - Component type (`terraform`, `helmfile`, etc.)
- `.vars` - All component variables (e.g., `.vars.region`, `.vars.tenant`)
- `.settings` - Component settings (e.g., `.settings.spacelift.workspace_enabled`)
- `.metadata` - Component metadata (e.g., `.metadata.description`)
- `.env` - Environment variables
- `.enabled` - Whether component is enabled (boolean)
- `.locked` - Whether component is locked (boolean)
- `.abstract` - Whether component is abstract (boolean)

### Template Functions

Columns support template functions for data transformation:

```yaml
components:
  list:
    columns:
      - name: Region (Upper)
        value: "{{ .vars.region | upper }}"
      - name: Short Description
        value: "{{ .metadata.description | truncate 50 }}"
      - name: Has Monitoring
        value: "{{ if .vars.monitoring_enabled }}Yes{{ else }}No{{ end }}"
```

### Override Columns via CLI

Override configured columns using the `--columns` flag:

```shell
# Display only stack and component columns
atmos list instances --columns stack,component

# Display custom subset
atmos list instances --columns "stack,component,vars.region,enabled"
```

:::tip

- Use `--format tree --provenance` to visualize component import hierarchies
- Use the `--filter` flag for complex filtering with YQ syntax
- Combine `--stack` (glob pattern) with `--filter` (YQ expression) for precise filtering
- The `--upload` flag sends every real instance to Atmos Pro; the effective `settings.pro.enabled` (collapsed from `metadata.enabled` > `pro.enabled`) is included in the payload so Atmos Pro shows enabled vs. disabled state
- Use `--format json` or `--format yaml` for programmatic processing
  :::

## Related Commands

- [`atmos list components`](/cli/commands/list/components) - List all components
- [`atmos list stacks`](/cli/commands/list/stacks) - List all stacks
- [`atmos describe component`](/cli/commands/describe/component) - Get detailed component configuration

---

## atmos list metadata

Use this command to list component metadata across all stacks, displaying custom metadata fields in a table. Filter and sort metadata to quickly find components with specific attributes.

## Usage

```shell
atmos list metadata [flags]
```

## Description

The `atmos list metadata` command displays component metadata across all stacks in a tabular format. Each row represents a component instance, showing metadata fields like:

- Component type (`abstract` or `real`)
- Enabled/disabled status
- Locked status
- Base component name
- Inheritance chain
- Description

This command is useful for:

- Auditing component types across environments
- Finding enabled/disabled components
- Understanding component inheritance patterns
- Verifying locked components

## Flags

- **`--format` / `-f`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
   (default: 
  `table`
  )
- **`--columns`**
  Columns to display (comma-separated). Overrides 
  `components.list.columns`
   configuration in atmos.yaml
- **`--stack` / `-s`**
  Filter by stack pattern (supports glob patterns, e.g., 
  `plat-*-prod`
  )
- **`--tags`**
  Filter by component tags (comma-separated, matches any): 
  `--tags=production,tier-1`
  . Components are tagged via an optional 
  `metadata.tags: [...]`
   list in stack manifests.
  Environment variable: 
  `ATMOS_COMPONENT_TAGS`
- **`--labels`**
  Filter by component labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--labels=cost-center=platform,compliance:sox`
  . Components are labeled via an optional 
  `metadata.labels: {...}`
   map in stack manifests.
  Environment variable: 
  `ATMOS_COMPONENT_LABELS`
- **`--filter`**
  Reserved. The flag is accepted for consistency with other 
  `list`
   commands, but a YQ filter expression is 
  not yet applied
   to metadata listings — use 
  `--tags`
  /
  `--labels`
   (or post-process 
  `--format json`
   output with an external tool) to narrow results.
- **`--sort`**
  Sort by column:order (e.g., 
  `stack:asc,component:desc`
  )
- **`--delimiter`**
  Delimiter for csv/tsv output (default: 
  `,`
   for csv, 
  `\t`
   for tsv)
- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before listing metadata.
  This is required when stack configurations use YAML template functions
  (e.g., 
  `!terraform.state`
  , 
  `!terraform.output`
  ) that require authentication.
  `atmos list metadata --identity my-aws-identity`
  Can also be set via 
  `ATMOS_IDENTITY`
   environment variable.
- **`--process-templates`**
  Enable/disable Go template processing in Atmos stack manifests (default 
  `true`
  ). Go template functions include 
  `atmos.Component(...)`
  .
  Environment variable: 
  `ATMOS_PROCESS_TEMPLATES`
- **`--process-functions`**
  Enable/disable YAML functions processing in Atmos stack manifests (default 
  `true`
  ). YAML functions include 
  `!terraform.state`
  , 
  `!terraform.output`
  , 
  `!store`
  , 
  `!aws.*`
  , etc. This is distinct from Go template functions like 
  `atmos.Component(...)`
  , which are controlled by 
  `--process-templates`
  .
  Environment variable: 
  `ATMOS_PROCESS_FUNCTIONS`
- **`--skip`**
  Skip executing a specific YAML function in the Atmos stack manifests when processing stacks. Repeat the flag to skip multiple functions (for example, 
  `--skip terraform.state --skip terraform.output`
  ). Use this to bypass a single function (such as a backend-resolving 
  `!terraform.state`
   call) while leaving other YAML functions enabled.
  Environment variable: 
  `ATMOS_SKIP`

## Examples

List all component metadata:

```shell
atmos list metadata
```

Filter by stack pattern:

```shell
# List metadata for production stacks
atmos list metadata --stack '*-prod'

# List metadata for specific environment
atmos list metadata --stack 'plat-ue2-*'
```

Filter by metadata fields with an external tool (the `--filter` flag is not yet applied to metadata listings):

```shell
# Find all enabled components
atmos list metadata --format json | jq '.[] | select(.enabled == true)'

# Find all abstract components
atmos list metadata --format json | jq '.[] | select(.type == "abstract")'

# Find locked components
atmos list metadata --format json | jq '.[] | select(.locked == true)'
```

Output in different formats:

```shell
# JSON format for machine processing
atmos list metadata --format json

# YAML format for configuration review
atmos list metadata --format yaml

# CSV format for spreadsheet analysis
atmos list metadata --format csv
```

Sort metadata:

```shell
# Sort by stack name ascending
atmos list metadata --sort stack:asc

# Multi-column sort
atmos list metadata --sort "type:desc,stack:asc,component:asc"
```

Custom column selection:

```shell
# Show only essential fields
atmos list metadata --columns stack,component,type,enabled

# Show inheritance information
atmos list metadata --columns "component,component_base,inherits"
```

## Custom Columns Configuration

You can customize the columns displayed by `atmos list metadata` in your `atmos.yaml`:

```yaml
# atmos.yaml
components:
  list:
    columns:
      - name: Stack
        value: "{{ .stack }}"
      - name: Component
        value: "{{ .component }}"
      - name: Type
        value: "{{ .type }}"
      - name: Enabled
        value: "{{ .enabled }}"
      - name: Locked
        value: "{{ .locked }}"
      - name: Base Component
        value: "{{ .component_base }}"
      - name: Inherits
        value: "{{ .inherits }}"
      - name: Description
        value: "{{ .description }}"
```

### Available Template Fields

Column `value` fields support Go template syntax with access to:

- `.stack` - Stack name
- `.component` - Component name
- `.component_type` - Component type (`terraform`, `helmfile`, etc.)
- `.type` - Metadata type (`abstract`, `real`)
- `.enabled` - Whether component is enabled (boolean)
- `.locked` - Whether component is locked (boolean)
- `.component_base` - Base Terraform/Helmfile component
- `.inherits` - Comma-separated list of inherited components
- `.description` - Component description
- `.metadata` - Full metadata map for advanced templates

### Template Functions

Columns support template functions for data transformation:

```yaml
components:
  list:
    columns:
      - name: Component (Upper)
        value: "{{ .component | upper }}"
      - name: Status
        value: "{{ if .enabled }}✓ Enabled{{ else }}✗ Disabled{{ end }}"
      - name: Type Badge
        value: "{{ if eq .type \"abstract\" }}[A]{{ else }}[R]{{ end }}"
      - name: Has Inherits
        value: "{{ if .inherits }}Yes{{ else }}No{{ end }}"
```

Available functions:

- `upper`, `lower` - String case conversion
- `truncate` - Truncate string with ellipsis
- `len` - Length of arrays/strings
- `toString` - Convert value to string
- `ternary` - Conditional expression
- `eq`, `ne` - Equality comparison

### Override Columns via CLI

Override configured columns using the `--columns` flag:

```shell
# Display only stack, component, and type
atmos list metadata --columns stack,component,type

# Display custom subset
atmos list metadata --columns "stack,component,type,enabled,locked"
```

## Example Output

```shell
> atmos list metadata
┌─────────────────┬───────────┬──────────┬─────────┬────────┬────────────────┬──────────────┬────────────────────┐
│     Stack       │ Component │   Type   │ Enabled │ Locked │ Base Component │   Inherits   │    Description     │
├─────────────────┼───────────┼──────────┼─────────┼────────┼────────────────┼──────────────┼────────────────────┤
│ plat-ue2-dev    │ vpc       │ real     │ true    │ false  │ vpc            │ vpc/defaults │ Development VPC    │
│ plat-ue2-dev    │ eks       │ real     │ true    │ false  │ eks            │ eks/defaults │ Development EKS    │
│ plat-ue2-prod   │ vpc       │ real     │ true    │ true   │ vpc            │ vpc/defaults │ Production VPC     │
│ plat-ue2-prod   │ eks       │ real     │ true    │ true   │ eks            │ eks/defaults │ Production EKS     │
└─────────────────┴───────────┴──────────┴─────────┴────────┴────────────────┴──────────────┴────────────────────┘
```

:::tip

- Use `--tags`/`--labels` to narrow by component metadata, and `--stack` (glob) to narrow by stack
- The `--sort` flag supports multi-column sorting for organized output
- Metadata is component-level configuration (use [`atmos list settings`](/cli/commands/list/settings) for settings data)
  :::

## Related Commands

- [`atmos list instances`](/cli/commands/list/list-instances) - List all component instances with full configuration
- [`atmos list components`](/cli/commands/list/components) - List all components
- [`atmos list settings`](/cli/commands/list/settings) - List component settings
- [`atmos describe component`](/cli/commands/describe/component) - Get detailed component configuration

---

## atmos list values

Use this command to list component configuration values across all stacks in a comparison table. View how a component's configuration varies between environments to spot differences and validate settings.

## Usage

```shell
atmos list values [component] [flags]
```

## Description

The `atmos list values` command helps you inspect component values across different stacks. It provides a tabular view where:

- Each column represents a stack (e.g., dev-ue1, staging-ue1, prod-ue1)
- Each row represents a key in the component's configuration
- Cells contain the values for each key in each stack

The command is particularly useful for:

- Comparing component configurations across different environments
- Verifying values are set correctly in each stack
- Understanding how a component is configured across your infrastructure

## Flags

- **`--query string`**
  Dot-notation path query to filter values (e.g., 
  `.vars.enabled`
  ). Uses a simplified path syntax, not full JMESPath.
- **`--abstract`**
  Include abstract components in the output
- **`--max-columns int`**
  Maximum number of columns to display (default: 
  `10`
  )
- **`--format string`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `csv`
  , 
  `tsv`
   (default: 
  `table`
  )
- **`--delimiter string`**
  Delimiter for csv/tsv output (default: 
  `,`
   for csv, 
  `\t`
   for tsv)
- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before listing values.
  This is required when stack configurations use YAML template functions
  (e.g., 
  `!terraform.state`
  , 
  `!terraform.output`
  ) that require authentication.
  `atmos list values vpc --identity my-aws-identity`
  Can also be set via 
  `ATMOS_IDENTITY`
   environment variable.
- **`--process-templates`**
  Enable/disable Go template processing in Atmos stack manifests (default 
  `true`
  ). Go template functions include 
  `atmos.Component(...)`
  .
  Environment variable: 
  `ATMOS_PROCESS_TEMPLATES`
- **`--process-functions`**
  Enable/disable YAML functions processing in Atmos stack manifests (default 
  `true`
  ). YAML functions include 
  `!terraform.state`
  , 
  `!terraform.output`
  , 
  `!store`
  , 
  `!aws.*`
  , etc. This is distinct from Go template functions like 
  `atmos.Component(...)`
  , which are controlled by 
  `--process-templates`
  .
  Environment variable: 
  `ATMOS_PROCESS_FUNCTIONS`

## Examples

List all values for a component:

```shell
atmos list values vpc
```

List only variables for a component (using the alias):

```shell
atmos list vars vpc
```

List values with a custom path query:

```shell
# Query specific variables
atmos list values vpc --query .vars.enabled

# Query environment settings
atmos list values vpc --query .vars.environment

# Query network configuration
atmos list values vpc --query .vars.ipv4_primary_cidr_block
```

Include abstract components:

```shell
atmos list values vpc --abstract
```

Limit the number of columns:

```shell
atmos list values vpc --max-columns 5
```

Output in different formats:

```shell
# JSON format for machine processing
atmos list values vpc --format json

# CSV format for spreadsheet compatibility
atmos list values vpc --format csv

# TSV format with tab delimiters
atmos list values vpc --format tsv

# Note: Use JSON or CSV formats when dealing with wide datasets
# The table format will show a width error if the data is too wide for your terminal
```

### Custom Column using Stack Name

You can use available variables like `.stack_name` in your column definitions:

```yaml
# In atmos.yaml, under the appropriate scope (values, vars, settings, or metadata)
list:
  columns:
    - name: "Stack"
      value: "{{ .stack_name }}"
    - name: "Key"
      value: "{{ .key }}"
    - name: "Value"
      value: "{{ .value }}"
```

## Example Output

```shell
> atmos list vars vpc
┌──────────────┬──────────────┬──────────────┬──────────────┐
│              │   dev-ue1    │  staging-ue1 │   prod-ue1   │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ enabled      │ true         │ true         │ true         │
│ name         │ dev-vpc      │ staging-vpc  │ prod-vpc     │
│ cidr_block   │ 10.0.0.0/16  │ 10.1.0.0/16  │ 10.2.0.0/16  │
│ environment  │ dev          │ staging      │ prod         │
│ namespace    │ example      │ example      │ example      │
│ stage        │ dev          │ staging      │ prod         │
│ region       │ us-east-1    │ us-east-1    │ us-east-1    │
└──────────────┴──────────────┴──────────────┴──────────────┘
```

### Nested Object Display

When listing values that contain nested objects:

1. In table format, nested objects appear as `{...}` placeholders
2. Use `--format json` or `--format yaml` to see the complete nested structure
3. You can query specific nested paths using the dot notation: `--query .vars.tags.Environment`

Example JSON output with nested objects:

```json
{
  "dev-ue1": {
    "cidr_block": "10.0.0.0/16",
    "tags": {
      "Environment": "dev",
      "Team": "devops"
    },
    "subnets": [
      "10.0.1.0/24",
      "10.0.2.0/24"
    ]
  }
}
```

## Related Commands

- [atmos list components](/cli/commands/list/components) - List available components
- [atmos describe component](/cli/commands/describe/component) - Show detailed information about a component

---

## atmos list vars

Use this command to list component variables across all stacks in a comparison table. View how Terraform variables vary between environments to quickly identify configuration differences and validate consistency.

_\[Video: atmos list vars]_

## Usage

```shell
atmos list vars  [flags]
```

## Description

The `atmos list vars` command helps you inspect component variables across different stacks. It provides a tabular view where:

- Each column represents a stack (e.g., dev-ue1, staging-ue1, prod-ue1)
- Each row represents a variable in the component's configuration
- Cells contain the variable values for each stack

This command is an alias for `atmos list values --query .vars` and is useful for:

- Comparing component variables across different environments
- Verifying configuration consistency across stacks
- Troubleshooting configuration issues

## Arguments

- **`component`**
  The component to list variables for

## Flags

- **`--query string`**
  Filter the results using YQ expressions (default: 
  `.vars`
  )
- **`--abstract`**
  Include abstract components
- **`--max-columns int`**
  Maximum number of columns to display (default: 
  `50`
  )
- **`--format string`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
   (default: 
  `table`
  )
- **`--delimiter string`**
  Delimiter for csv/tsv output (default: 
  `,`
   for csv, 
  `\t`
   for tsv)
- **`--stack string`**
  Filter by stack pattern (e.g., 
  `*-dev-*`
  , 
  `prod-*`
  , 
  `*-{dev,staging}-*`
  )
- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before listing vars.
  This is required when stack configurations use YAML template functions
  (e.g., 
  `!terraform.state`
  , 
  `!terraform.output`
  ) that require authentication.
  `atmos list vars vpc --identity my-aws-identity`
  Can also be set via 
  `ATMOS_IDENTITY`
   environment variable.
- **`--process-templates`**
  Enable/disable Go template processing in Atmos stack manifests (default 
  `true`
  ). Go template functions include 
  `atmos.Component(...)`
  .
  Environment variable: 
  `ATMOS_PROCESS_TEMPLATES`
- **`--process-functions`**
  Enable/disable YAML functions processing in Atmos stack manifests (default 
  `true`
  ). YAML functions include 
  `!terraform.state`
  , 
  `!terraform.output`
  , 
  `!store`
  , 
  `!aws.*`
  , etc. This is distinct from Go template functions like 
  `atmos.Component(...)`
  , which are controlled by 
  `--process-templates`
  .
  Environment variable: 
  `ATMOS_PROCESS_FUNCTIONS`

## Examples

List all variables for a component:

```shell
atmos list vars vpc
```

List specific variables using query:

```shell
# List specific variable
atmos list vars vpc --query .vars.tags

# List a nested variable
atmos list vars vpc --query .vars.settings.vpc
```

Filter by stack pattern:

```shell
# List variables for dev stacks
atmos list vars vpc --stack '*-dev-*'

# List variables for production stacks
atmos list vars vpc --stack 'prod-*'
```

Output in different formats:

```shell
# JSON format for machine processing
atmos list vars vpc --format json

# YAML format for configuration files
atmos list vars vpc --format yaml

# CSV format for spreadsheet compatibility
atmos list vars vpc --format csv

# TSV format with tab delimiters
atmos list vars vpc --format tsv
```

Include abstract components:

```shell
atmos list vars vpc --abstract
```

Filter by stack and specific variables:

```shell
atmos list vars vpc --stack '*-ue2-*' --query .vars.region
```

### Custom Column using Stack Name

You can use available variables like `.stack_name` in your column definitions:

```yaml
# In atmos.yaml, under the appropriate scope (values, vars, settings, or metadata)
list:
  columns:
    - name: "Stack"
      value: "{{ .stack_name }}"
    - name: "Variable"
      value: "{{ .key }}"
    - name: "Value"
      value: "{{ .value }}"
```

## Example Output

```shell
> atmos list vars vpc
┌─────────────┬──────────────┬──────────────┬──────────────┐
│             │   dev-ue1    │  staging-ue1 │   prod-ue1   │
├─────────────┼──────────────┼──────────────┼──────────────┤
│ name        │ platform-vpc │ platform-vpc │ platform-vpc │
│ region      │ us-east-1    │ us-east-1    │ us-east-1    │
│ environment │ dev          │ staging      │ prod         │
└─────────────┴──────────────┴──────────────┴──────────────┘
```

:::tip

- For wide tables, try using more specific queries or reduce the number of stacks
- Stack patterns support glob matching (e.g., `*-dev-*`, `prod-*`, `*-{dev,staging}-*`)
- Use `--abstract` to include abstract components in the results
  :::

---

## atmos list vendor

Use this command to list all components and modules configured for vendoring in your Atmos project. View vendor sources, types, and target folders to understand what external dependencies are managed by Atmos vendoring.

_\[Video: atmos vendor]_

## Usage

```shell
atmos list vendor [flags]
```

## Description

The `atmos list vendor` command displays all vendored components and modules defined in your vendor configuration files (`vendor.yaml`). It provides a tabular view where each row represents a vendored item with information about:

- Component/module name
- Source location (GitHub, local, HTTP, etc.)
- Version or Git reference
- Target destination path
- Vendor configuration file

This command is useful for:

- Getting an overview of all vendored dependencies
- Verifying vendoring configuration before running `atmos vendor pull`
- Finding specific vendored components
- Auditing external dependencies in your infrastructure

## Flags

- **`--format` / `-f`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
  . Overrides 
  `vendor.list.format`
   configuration in atmos.yaml (default: 
  `table`
  )
- **`--delimiter`**
  Delimiter for CSV/TSV output (default: tab for tsv, comma for csv)
- **`--columns`**
  Columns to display (comma-separated). Overrides 
  `vendor.list.columns`
   configuration in atmos.yaml
- **`--stack` / `-s`**
  Filter by stack pattern (supports glob patterns)
- **`--tags`**
  Filter by the manifest's own declared tags (comma-separated, matches any): 
  `--tags=networking,storage`
  . These are the 
  `tags`
   declared on vendor manifest sources (
  `vendor.yaml`
  ) — not the 
  `metadata.tags`
   of components in stack manifests, which other 
  `list`
   commands filter on. Vendor manifests have no labels concept, so 
  `--labels`
   is not available on this command.
  Environment variable: 
  `ATMOS_VENDOR_TAGS`
- **`--filter`**
  Filter expression using YQ syntax
- **`--sort`**
  Sort by column:order (e.g., 
  `component:asc,source:desc`
  )

## Examples

List all vendored items:

```shell
atmos list vendor
```

Output in different formats:

```shell
# JSON format for machine processing
atmos list vendor --format json

# YAML format for configuration review
atmos list vendor --format yaml

# CSV format for dependency auditing
atmos list vendor --format csv
```

Filter vendored items:

```shell
# Filter by specific source pattern
atmos list vendor --filter '.source | contains("github.com/cloudposse")'

# Find specific component
atmos list vendor --filter '.component == "vpc"'
```

Sort vendored items:

```shell
# Sort by component name
atmos list vendor --sort component:asc

# Multi-column sort
atmos list vendor --sort "source:asc,component:asc"
```

## Configuration

You can customize the default output format and columns displayed by `atmos list vendor` in your `atmos.yaml`:

### Default Format

```yaml
# atmos.yaml
vendor:
  list:
    format: table  # Default format: table, json, yaml, csv, tsv
```

**Precedence**: CLI `--format` flag > Config file > Environment variable `ATMOS_LIST_FORMAT` > Default (`table`)

### Custom Columns

```yaml
# atmos.yaml
vendor:
  list:
    format: table
    columns:
      - name: Component
        value: "{{ .component }}"
      - name: Source
        value: "{{ .source }}"
      - name: Version
        value: "{{ .version }}"
      - name: Target
        value: "{{ .targets | join \", \" }}"
      - name: File
        value: "{{ .atmos_vendor_file }}"
```

### Available Template Fields

Column `value` fields support Go template syntax with access to:

- `.component` - Component/module name
- `.source` - Source URL or path (GitHub, HTTP, local, etc.)
- `.version` - Version, tag, or Git ref to vendor
- `.targets` - Array of target destination paths
- `.included_paths` - Glob patterns for files to include
- `.excluded_paths` - Glob patterns for files to exclude
- `.tags` - Array of tags associated with the vendored item
- `.atmos_vendor_file` - Path to vendor.yaml file containing this item
- `.atmos_vendor_type` - Type of vendor source (git, http, local, etc.)
- `.atmos_vendor_target` - Primary target path

### Template Functions

Columns support template functions for data transformation:

```yaml
vendor:
  list:
    columns:
      - name: Component (Upper)
        value: "{{ .component | upper }}"
      - name: Short Source
        value: "{{ .source | truncate 50 }}"
      - name: Target Count
        value: "{{ .targets | len }}"
      - name: Has Tags
        value: "{{ if .tags }}Yes{{ else }}No{{ end }}"
```

Available functions:

- `upper`, `lower` - String case conversion
- `truncate` - Truncate string with ellipsis
- `len` - Length of arrays/strings
- `join` - Join array elements with delimiter
- `toString` - Convert value to string
- `ternary` - Conditional expression

### Override Columns via CLI

Override configured columns using the `--columns` flag:

```shell
# Display only component and source columns
atmos list vendor --columns component,source

# Display custom subset
atmos list vendor --columns "component,source,version,atmos_vendor_file"
```

## Example Output

```shell
> atmos list vendor
┌────────────────┬─────────────────────────────────────────┬─────────┬──────────────────────┬─────────────────┐
│   Component    │                 Source                  │ Version │       Target         │      File       │
├────────────────┼─────────────────────────────────────────┼─────────┼──────────────────────┼─────────────────┤
│ vpc            │ github.com/cloudposse/terraform-aws-vpc │ 1.5.0   │ components/vpc       │ vendor.yaml     │
│ eks            │ github.com/cloudposse/terraform-aws-eks │ 2.0.0   │ components/eks       │ vendor.yaml     │
│ rds            │ github.com/cloudposse/terraform-aws-rds │ 0.45.0  │ components/rds       │ vendor.yaml     │
└────────────────┴─────────────────────────────────────────┴─────────┴──────────────────────┴─────────────────┘
```

:::tip

- Use `atmos vendor pull` to download vendored components after reviewing the list
- The `--filter` flag supports full YQ syntax for complex queries
- Use `--format json` to pipe vendor information to other tools for analysis
- Vendor configuration files can be split across multiple `vendor.yaml` files in `vendor.d/` directory
  :::

## Related Commands

- [`atmos vendor pull`](/cli/commands/vendor/pull) - Download vendored components
- [`atmos list components`](/cli/commands/list/components) - List all components (including vendored)

---

## atmos list workflows

Use this command to list all workflows defined in your project's workflow manifests. View workflow names, descriptions, and source files to discover automation available for your infrastructure.

## Usage

```shell
atmos list workflows [flags]
```

## Description

The `atmos list workflows` command helps you inspect all Atmos workflows defined in your project's workflow manifests. It provides a tabular view where:

- Each row represents a workflow
- Columns show the file, workflow name, and description

This command is useful for:

- Getting an overview of all available workflows
- Finding workflows for specific tasks
- Understanding workflow organization in your project

## Flags

- **`--file, -f string`**
  Filter workflows by file (e.g., 
  `atmos list workflows -f workflow1`
  )
- **`--format string`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
  . Overrides 
  `workflows.list.format`
   configuration in atmos.yaml (default: 
  `table`
  )
- **`--delimiter string`**
  Delimiter for csv/tsv output (default: 
  `\t`
  )
- **`--columns string`**
  Columns to display (comma-separated). Overrides 
  `workflows.list.columns`
   configuration in atmos.yaml
- **`--sort string`**
  Sort by column:order (e.g., 
  `name:asc,file:desc`
  )

## Examples

List all workflows:

```shell
atmos list workflows
```

Filter workflows by file:

```shell
atmos list workflows -f networking.yaml
```

Output in different formats:

```shell
# JSON format for machine processing
atmos list workflows --format json

# YAML format for configuration files
atmos list workflows --format yaml

# CSV format for spreadsheet compatibility
atmos list workflows --format csv

# TSV format with tab delimiters
atmos list workflows --format tsv
```

Specify delimiter for CSV output:

```shell
atmos list workflows --format csv --delimiter ','
```

## Example Output

```shell
> atmos list workflows
┌────────────────┬─────────────────────────────┬─────────────────────────────────────────┐
│      File      │          Workflow           │               Description               │
├────────────────┼─────────────────────────────┼─────────────────────────────────────────┤
│ compliance.yaml│ deploy/aws-config/global    │ Deploy AWS Config Global                │
│ networking.yaml│ apply-all-components        │ Apply all networking components         │
│ networking.yaml│ plan-all-vpc                │ Plan all VPC changes                    │
│ datadog.yaml   │ deploy/datadog-integration  │ Deploy Datadog integration              │
└────────────────┴─────────────────────────────┴─────────────────────────────────────────┘
```

:::tip

- Use the `--file` flag to filter workflows from a specific manifest file
- The `describe workflows` command provides more detailed information about workflows
  :::

## Configuration

You can customize the default output format and columns displayed by `atmos list workflows` in your `atmos.yaml`:

### Default Format

```yaml
# atmos.yaml
workflows:
  list:
    format: table  # Default format: table, json, yaml, csv, tsv
```

**Precedence**: CLI `--format` flag > Config file > Environment variable `ATMOS_LIST_FORMAT` > Default (`table`)

### Custom Columns

```yaml
# atmos.yaml
workflows:
  list:
    format: table
    columns:
      - name: Workflow
        value: "{{ .name }}"
      - name: File
        value: "{{ .file }}"
      - name: Description
        value: "{{ .description }}"
      - name: Steps
        value: "{{ .steps | len }} steps"
```

### Available Template Fields

Column `value` fields support Go template syntax with access to:

- `.name` - Workflow name
- `.file` - Workflow definition file path
- `.description` - Workflow description
- `.steps` - Array of workflow steps
- `.stack` - Stack name (if workflow is stack-specific)

### Template Functions

Columns support template functions for data transformation:

```yaml
workflows:
  list:
    columns:
      - name: Workflow (Upper)
        value: "{{ .name | upper }}"
      - name: Short File
        value: "{{ .file | truncate 30 }}"
      - name: Step Count
        value: "{{ .steps | len }}"
      - name: Has Description
        value: "{{ if .description }}Yes{{ else }}No{{ end }}"
```

Available functions:

- `upper`, `lower` - String case conversion
- `truncate` - Truncate string with ellipsis
- `len` - Length of arrays/strings
- `join` - Join array elements with delimiter
- `toString` - Convert value to string
- `ternary` - Conditional expression

### Override Columns via CLI

Override configured columns using the `--columns` flag:

```shell
# Display only name and file columns
atmos list workflows --columns name,file

# Display custom subset
atmos list workflows --columns "name,file,description"
```

---

## atmos list settings

Use this command to list component settings across all stacks in a comparison table. View how settings vary between environments to quickly spot configuration differences and validate consistency.

## Usage

```shell
atmos list settings [flags]
```

## Description

The `atmos list settings` command helps you inspect component settings across different stacks. It provides a tabular view where:

- Each column represents a stack (e.g., dev-ue1, staging-ue1, prod-ue1)
- Each row represents a key in the component's settings
- Cells contain the settings values for each key in each stack (only scalars at this time)

The command is particularly useful for:

- Comparing component settings across different environments
- Verifying settings are configured correctly in each stack
- Understanding component configuration patterns across your infrastructure

## Flags

- **`--query string`**
  Dot-notation path query to filter settings (e.g., 
  `.settings.templates`
  ). Uses a simplified path syntax, not full JMESPath.
- **`--max-columns int`**
  Maximum number of columns to display (default: 
  `50`
  )
- **`--format string`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
   (default: 
  `table`
  )
- **`--delimiter string`**
  Delimiter for csv/tsv output (default: 
  `,`
   for csv, 
  `\t`
   for tsv)
- **`--stack string`**
  Filter by stack by wildcard pattern (e.g., 
  `*-dev-*`
  , 
  `prod-*`
  , 
  `*-{dev,staging}-*`
  )
- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before listing settings.
  This is required when stack configurations use YAML template functions
  (e.g., 
  `!terraform.state`
  , 
  `!terraform.output`
  ) that require authentication.
  `atmos list settings --identity my-aws-identity`
  Can also be set via 
  `ATMOS_IDENTITY`
   environment variable.
- **`--process-templates`**
  Enable/disable Go template processing in Atmos stack manifests (default 
  `true`
  ). Go template functions include 
  `atmos.Component(...)`
  .
  Environment variable: 
  `ATMOS_PROCESS_TEMPLATES`
- **`--process-functions`**
  Enable/disable YAML functions processing in Atmos stack manifests (default 
  `true`
  ). YAML functions include 
  `!terraform.state`
  , 
  `!terraform.output`
  , 
  `!store`
  , 
  `!aws.*`
  , etc. This is distinct from Go template functions like 
  `atmos.Component(...)`
  , which are controlled by 
  `--process-templates`
  .
  Environment variable: 
  `ATMOS_PROCESS_FUNCTIONS`
- **`--on-error`**
  How to handle a recoverable YAML-function error, such as a Terraform backend that has not been provisioned yet (default 
  `strict`
  ). 
  `strict`
   fails the command on the first such error. 
  `warn`
   substitutes 
  `null`
   for the unresolved value, prints a warning naming the stack/component/function, and continues processing the rest of the stacks. Errors unrelated to backend provisioning (auth failures, malformed YAML, etc.) still fail the command in either mode.
  Environment variable: 
  `ATMOS_LIST_ON_ERROR`

## Examples

List all settings:

```shell
atmos list settings
```

List settings for specific stacks:

```shell
# List settings for dev stacks
atmos list settings --stack '*-dev-*'

# List settings for production stacks
atmos list settings --stack 'prod-*'
```

List specific settings using path queries:

```shell
# Query template settings
atmos list settings --query '.settings.templates'

# Query validation settings
atmos list settings --query '.settings.validation'

# Query specific template configurations
atmos list settings --query '.settings.templates.gomplate'
```

Output in different formats:

```shell
# JSON format for machine processing
atmos list settings --format json

# YAML format for configuration files
atmos list settings --format yaml

# CSV format for spreadsheet compatibility
atmos list settings --format csv

# TSV format with tab delimiters
atmos list settings --format tsv
```

### Custom Column using Stack Name

You can use available variables like `.stack_name` in your column definitions:

```yaml
# In atmos.yaml, under the appropriate scope (values, vars, settings, or metadata)
list:
  columns:
    - name: "Stack"
      value: "{{ .stack_name }}"
    - name: "Setting"
      value: "{{ .key }}"
    - name: "Value"
      value: "{{ .value }}"
```

## Example Output

```shell
> atmos list settings
┌──────────────┬──────────────┬──────────────┬──────────────┐
│              │   dev-ue1    │  staging-ue1 │   prod-ue1   │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ templates    │ {...}        │ {...}        │ {...}        │
│ validation   │ {...}        │ {...}        │ {...}        │
└──────────────┴──────────────┴──────────────┴──────────────┘
```

:::tip

- For wide tables, try using more specific queries or reduce the number of stacks
- Stack patterns support glob matching (e.g., `*-dev-*`, `prod-*`, `*-{dev,staging}-*`)
- Settings are typically found under component configurations
  :::

---

## atmos list sources

Use this command to list all components with `source` configuration across all component types (Terraform, Helmfile, Packer) in a unified view. This provides a single table showing which components can be vendored using the source provisioner.

**Source-Based Version Pinning**

Learn how to configure the `source` field for per-environment version control.

Design Pattern[Read more](/design-patterns/version-management/source-based-versioning)

## Usage

```shell
atmos list sources [component] [flags]
```

List all components with source configuration across all stacks:

```shell
atmos list sources
```

Filter to a specific stack:

```shell
atmos list sources --stack dev
```

Filter to a specific component across all stacks:

```shell
atmos list sources vpc
```

:::tip
Run `atmos list sources --help` to see all available options.
:::

## Description

The `list sources` command provides a unified view of all components that have `source` configured, regardless of component type. Unlike the type-specific commands (`atmos terraform source list`, `atmos helmfile source list`, `atmos packer source list`), this command shows all types together with a "Type" column.

This helps identify:

- All components configured for JIT vendoring across your entire infrastructure
- The source URIs and versions for each component
- Which component types (terraform, helmfile, packer) have source configurations
- Components that may need to be vendored before use

## Examples

### List All Sources Across All Stacks

```shell
atmos list sources
```

Example output:

```
STACK              TYPE        COMPONENT    URI                                                           VERSION
plat-ue2-dev       terraform   eks          github.com/cloudposse/terraform-aws-components//modules/eks   1.450.0
plat-ue2-dev       terraform   vpc          github.com/cloudposse/terraform-aws-components//modules/vpc   1.450.0
plat-ue2-dev       helmfile    nginx        github.com/cloudposse/helmfile-components//charts/nginx               1.0.0
plat-ue2-prod      terraform   eks          github.com/cloudposse/terraform-aws-components//modules/eks   1.451.0
plat-ue2-prod      terraform   vpc          github.com/cloudposse/terraform-aws-components//modules/vpc   1.451.0
```

### List Sources in a Specific Stack

```shell
atmos list sources --stack plat-ue2-dev
```

When filtering by stack, the Stack column is omitted:

```
TYPE        COMPONENT    URI                                                           VERSION
terraform   eks          github.com/cloudposse/terraform-aws-components//modules/eks   1.450.0
terraform   vpc          github.com/cloudposse/terraform-aws-components//modules/vpc   1.450.0
helmfile    nginx        github.com/cloudposse/helmfile-components//charts/nginx               1.0.0
```

### List Sources for a Specific Component

```shell
atmos list sources vpc
```

Shows the component across all stacks where it has source configured:

```
STACK              TYPE        COMPONENT    URI                                                           VERSION
plat-ue2-dev       terraform   vpc          github.com/cloudposse/terraform-aws-components//modules/vpc   1.450.0
plat-ue2-prod      terraform   vpc          github.com/cloudposse/terraform-aws-components//modules/vpc   1.451.0
plat-ue2-staging   terraform   vpc          github.com/cloudposse/terraform-aws-components//modules/vpc   1.450.0
```

### Output in Different Formats

```shell
# JSON format
atmos list sources --format json

# YAML format
atmos list sources --format yaml

# CSV format
atmos list sources --format csv
```

## Arguments

- **`component` (optional)**
  Filter results to a specific component name or folder (
  `metadata.component`
  ). When provided, only shows matching sources across all stacks.

## Flags

- **`--stack` / `-s` (optional)**
  Filter by stack name. When provided, only shows sources within that stack and omits the Stack column from output.
  Environment variable: 
  `ATMOS_STACK`
- **`--tags`**
  Filter by component tags (comma-separated, matches any): 
  `--tags=production,tier-1`
  . Components are tagged via an optional 
  `metadata.tags: [...]`
   list in stack manifests.
  Environment variable: 
  `ATMOS_COMPONENT_TAGS`
- **`--labels`**
  Filter by component labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--labels=cost-center=platform,compliance:sox`
  . Components are labeled via an optional 
  `metadata.labels: {...}`
   map in stack manifests.
  Environment variable: 
  `ATMOS_COMPONENT_LABELS`
- **`--format` / `-f`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
   (default: 
  `table`
  ).
  Environment variable: 
  `ATMOS_FORMAT`
- **`--process-templates`**
  Enable/disable Go template processing in Atmos stack manifests (default 
  `true`
  ). Go template functions include 
  `atmos.Component(...)`
  .
  Environment variable: 
  `ATMOS_PROCESS_TEMPLATES`
- **`--process-functions`**
  Enable/disable YAML functions processing in Atmos stack manifests (default 
  `true`
  ). YAML functions include 
  `!terraform.state`
  , 
  `!terraform.output`
  , 
  `!store`
  , 
  `!aws.*`
  , etc. This is distinct from Go template functions like 
  `atmos.Component(...)`
  , which are controlled by 
  `--process-templates`
  .
  Environment variable: 
  `ATMOS_PROCESS_FUNCTIONS`
- **`--skip`**
  Skip executing a specific YAML function in the Atmos stack manifests when processing stacks. Repeat the flag to skip multiple functions (for example, 
  `--skip terraform.state --skip terraform.output`
  ). Use this to bypass a single function (such as a backend-resolving 
  `!terraform.state`
   call) while leaving other YAML functions enabled.
  Environment variable: 
  `ATMOS_SKIP`

## Dynamic Columns

The command automatically adjusts columns based on context:

| Context | Columns Shown |
|---------|---------------|
| All stacks | Stack, Type, Component, Folder\*, URI, Version |
| Single stack (`--stack`) | Type, Component, Folder\*, URI, Version |

\*The Folder column only appears when any component uses `metadata.component` to specify a different folder name than the component instance name.

## Related Commands

For type-specific source listing:

- [`atmos terraform source list`](/cli/commands/terraform/source/list) - List only Terraform components with source
- [`atmos helmfile source list`](/cli/commands/helmfile/source/list) - List only Helmfile components with source
- [`atmos packer source list`](/cli/commands/packer/source/list) - List only Packer components with source

For source management:

- [`atmos terraform source pull`](/cli/commands/terraform/source/pull) - Vendor a Terraform component
- [`atmos terraform source describe`](/cli/commands/terraform/source/describe) - View source configuration details

## See Also

- [Source-Based Version Pinning](/design-patterns/version-management/source-based-versioning) - Design pattern for per-environment version control
- [`atmos list components`](/cli/commands/list/components) - List all components

---

## atmos list stacks

Use this command to list all stacks in your Atmos configuration, optionally filtering by component. View stacks in multiple formats including tables, JSON, YAML, or hierarchical trees with import provenance to understand configuration inheritance.

## Usage

Execute the `list stacks` command like this:

```shell
atmos list stacks
```

To view all stacks for a provided component, execute the `list stacks` command like this:

```shell
atmos list stacks -c 
```

:::tip
Run `atmos list stacks --help` to see all the available options
:::

## Examples

List all stacks:

```shell
atmos list stacks
```

List stacks for a specific component:

```shell
atmos list stacks -c vpc
atmos list stacks --component eks
```

Filter by component tags and labels:

```shell
# List stacks where any component is tagged production or tier-1 (matches any)
atmos list stacks --tags=production,tier-1

# List stacks where any component is labeled cost-center=platform (matches all given labels)
atmos list stacks --labels=cost-center=platform

# The `:` separator works too
atmos list stacks --labels=cost-center:platform

# Combine with --component to consult only that component's own tags/labels
atmos list stacks -c vpc --labels=team=network
```

Output in different formats:

```shell
# JSON format
atmos list stacks --format json

# YAML format
atmos list stacks --format yaml

# CSV format
atmos list stacks --format csv
```

Sort stacks:

```shell
# Sort by stack name ascending
atmos list stacks --sort stack:asc

# Sort by component name descending
atmos list stacks --component vpc --sort component:desc
```

Custom columns:

```shell
# Simple field names (auto-generates templates)
atmos list stacks --columns stack

# Named columns with custom templates
atmos list stacks --columns "Name={{ .stack }}"

# When filtering by component, show both stack and component
atmos list stacks --component vpc --columns stack,component
```

View stacks in tree format:

```shell
# Tree view without import details
atmos list stacks --format tree

# Tree view with import provenance (shows inheritance chain)
atmos list stacks --format tree --provenance

# Tree view with provenance for a specific component
atmos list stacks --component vpc --format tree --provenance
```

## Flags

- **`--component` / `-c`**
  Filter stacks by component name.
  Environment variable: 
  `ATMOS_COMPONENT`
- **`--tags`**
  Filter stacks by component tags (comma-separated, matches any): 
  `--tags=production,tier-1`
  . Components are tagged via an optional 
  `metadata.tags: [...]`
   list in stack manifests. Without 
  `--component`
  , a stack is listed when 
  **any**
   of its components matches; with 
  `--component`
  , only that component's own tags are consulted.
  Environment variable: 
  `ATMOS_COMPONENT_TAGS`
- **`--labels`**
  Filter stacks by component labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--labels=cost-center=platform,compliance:sox`
  . Components are labeled via an optional 
  `metadata.labels: {...}`
   map in stack manifests. When 
  `--tags`
   and 
  `--labels`
   are combined, a single component must satisfy both for its stack to match — a tag on one component and a label on another does not qualify the stack.
  Environment variable: 
  `ATMOS_COMPONENT_LABELS`
- **`--include-dependencies`**
  Expand the listing to the stacks touched by the dependency closure of the selected components — previewing the exact set of stacks a terraform bulk command with the same selection flags would execute in, including stacks that hold prerequisites of the selection. The closure covers Terraform components (the dependency graph the scheduler executes). Accepts an optional depth (for example, 
  `--include-dependencies=1`
   for direct dependencies only).
  `atmos list stacks --labels=env=dev --include-dependencies`
  Environment variable: 
  `ATMOS_INCLUDE_DEPENDENCIES`
- **`--include-dependents`**
  Expand the listing to the stacks touched by components that depend on the selection — previewing the exact set of stacks a terraform bulk command with the same selection flags would execute in. Accepts an optional depth (for example, 
  `--include-dependents=2`
   for two levels).
  Environment variable: 
  `ATMOS_INCLUDE_DEPENDENTS`
- **`--format` / `-f`**
  Output format: 
  `table`
  , 
  `json`
  , 
  `yaml`
  , 
  `csv`
  , 
  `tsv`
  , 
  `tree`
  . Overrides 
  `stacks.list.format`
   configuration in atmos.yaml (default: 
  `table`
  ).
  Environment variable: 
  `ATMOS_LIST_FORMAT`
- **`--columns`**
  Columns to display. Supports simple field names (e.g., 
  `stack`
  ), named columns with templates (e.g., 
  `"Name={{ .stack }}"`
  ), or named with field reference (e.g., 
  `"MyStack=stack"`
  ). Overrides 
  `stacks.list.columns`
   configuration in atmos.yaml. Environment variable: 
  `ATMOS_LIST_COLUMNS`
- **`--sort`**
  Sort by column:order (e.g., 
  `stack:asc,component:desc`
  ). Multiple sort columns separated by comma.
  Environment variable: 
  `ATMOS_LIST_SORT`
- **`--provenance`**
  Show import provenance in tree format. Only works with 
  `--format=tree`
  . Displays the import hierarchy showing which files each stack inherits from.
  Environment variable: 
  `ATMOS_PROVENANCE`
- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before listing stacks.
  This is required when stack configurations use YAML template functions
  (e.g., 
  `!terraform.state`
  , 
  `!terraform.output`
  ) that require authentication.
  `atmos list stacks --identity my-aws-identity`
  Environment variable: 
  `ATMOS_IDENTITY`
- **`--process-templates`**
  Enable/disable Go template processing in Atmos stack manifests (default 
  `true`
  ). Go template functions include 
  `atmos.Component(...)`
  .
  Environment variable: 
  `ATMOS_PROCESS_TEMPLATES`
- **`--process-functions`**
  Enable/disable YAML functions processing in Atmos stack manifests (default 
  `true`
  ). YAML functions include 
  `!terraform.state`
  , 
  `!terraform.output`
  , 
  `!store`
  , 
  `!aws.*`
  , etc. This is distinct from Go template functions like 
  `atmos.Component(...)`
  , which are controlled by 
  `--process-templates`
  .
  Environment variable: 
  `ATMOS_PROCESS_FUNCTIONS`
- **`--skip`**
  Skip executing a specific YAML function in the Atmos stack manifests when processing stacks. Repeat the flag to skip multiple functions (for example, 
  `--skip terraform.state --skip terraform.output`
  ). Use this to bypass a single function (such as a backend-resolving 
  `!terraform.state`
   call) while leaving other YAML functions enabled.
  Environment variable: 
  `ATMOS_SKIP`
- **`--error-mode`**
  How to handle a recoverable YAML-function error, such as a Terraform backend that has not been provisioned yet (default 
  `warn`
  ). 
  `strict`
   fails the command on the first such error. 
  `warn`
   substitutes 
  `(computed)`
   for the unresolved value, prints a warning naming the stack/component/function, and continues processing the rest of the stacks. 
  `silent`
   continues without the summary. Errors unrelated to backend provisioning (auth failures, malformed YAML, etc.) still fail the command in either mode.
  Environment variable: 
  `ATMOS_LIST_ERROR_MODE`

## Tree Format with Import Provenance

The `tree` format provides a hierarchical view of your stacks. When combined with the `--provenance` flag, it shows the complete import chain for each stack, making it easy to understand configuration inheritance.

### Tree Format Structure

The tree format displays:

- **Stack names** as top-level nodes
- **Import hierarchy** (when `--provenance` is enabled) showing the chain of stack configuration files that each stack imports

### Import Provenance

When you enable `--provenance`, each stack shows its import chain - the sequence of stack configuration files it inherits from. This is particularly useful for:

- **Debugging configuration** - See exactly where each stack's configuration comes from
- **Understanding inheritance** - Visualize the complete import chain
- **Auditing changes** - Track which base configurations affect which stacks
- **Documentation** - Generate visual representations of stack dependencies

Example tree output with provenance:

```
Stacks
│
├── tenant1-ue2-dev
│   ├── stacks/tenant1/ue2/dev
│   ├── stacks/tenant1/ue2/_defaults
│   ├── stacks/tenant1/_defaults
│   └── stacks/_defaults
│
├── tenant1-ue2-staging
│   ├── stacks/tenant1/ue2/staging
│   ├── stacks/tenant1/ue2/_defaults
│   ├── stacks/tenant1/_defaults
│   └── stacks/_defaults
```

The import chain is shown from most specific (top) to most general (bottom), reflecting how Atmos merges configurations.

When used with `--component`, the tree shows only stacks that contain the specified component:

```shell
atmos list stacks --component vpc --format tree --provenance
```

This filters the output to show only stacks where the `vpc` component is defined, along with their import chains.

## Configuration

You can customize the default output format and columns displayed by `atmos list stacks` in your `atmos.yaml`:

### Default Format

The default output format is `table`. To make `tree` the default instead, set `stacks.list.format` in your `atmos.yaml`:

```yaml
# atmos.yaml
stacks:
  list:
    format: tree  # Supported formats: table, json, yaml, csv, tsv, tree
```

**Precedence**: CLI `--format` flag > Environment variable `ATMOS_LIST_FORMAT` > Config file > Default (`table`)

Scripts that parse the output should always pass an explicit format (e.g., `--format=csv` or `--format=json`) rather than relying on the default.

### Custom Columns

```yaml
# atmos.yaml
stacks:
  list:
    format: table
    columns:
      - name: Stack
        value: "{{ .stack }}"
      - name: Namespace
        value: "{{ .vars.namespace }}"
      - name: Tenant
        value: "{{ .vars.tenant }}"
      - name: Environment
        value: "{{ .vars.environment }}"
      - name: Stage
        value: "{{ .vars.stage }}"
```

:::note
Column configuration for stacks is under the `stacks.list.columns` section in atmos.yaml, not `components.list.columns`.
:::

### Available Template Fields

Column `value` fields support Go template syntax with access to:

- `.stack` - Stack name
- `.vars` - Stack variables from component configuration (access fields via `.vars.fieldname`)
  - `.vars.namespace` - Namespace variable
  - `.vars.tenant` - Tenant variable
  - `.vars.environment` - Environment variable
  - `.vars.stage` - Stage variable
  - `.vars.` - Any other variable defined in your stack's vars section
- `.components` - Map of components in the stack
- `.file` - Stack configuration file path

:::tip
Variables are extracted from the first component in each stack. Since all components in a stack typically share
the same stack-level variables (namespace, tenant, environment, stage), this provides consistent access to
stack metadata regardless of which components are defined.
:::

### Template Functions

Columns support template functions for data transformation:

```yaml
stacks:
  list:
    columns:
      - name: Stack
        value: "{{ .stack }}"
      - name: Namespace
        value: "{{ .vars.namespace | upper }}"
      - name: Region
        value: "{{ .vars.region }}"
      - name: Env
        value: "{{ .vars.stage | upper }}"
```

Available functions:

- `upper`, `lower` - String case conversion
- `truncate` - Truncate string with ellipsis
- `len` - Length of arrays/strings
- `join` - Join array elements with delimiter
- `toString` - Convert value to string
- `ternary` - Conditional expression
- `get`, `getOr`, `has` - Safe map access (e.g., `{{ get .vars "region" }}`)

### Override Columns via CLI

Override configured columns using the `--columns` flag. The flag supports multiple formats:

**Simple field names** (auto-generates templates and title-case names):

```shell
# Display only stack column
atmos list stacks --columns stack

# When filtering by component, show both
atmos list stacks --component vpc --columns stack,component
```

**Named columns with templates** (full control over display name and value):

```shell
# Custom column names with templates
atmos list stacks --columns "StackName={{ .stack }}"

# Multiple named columns
atmos list stacks --columns "Name={{ .stack }},Components={{ .components | len }}"
```

**Named columns with field reference** (auto-wraps field in template):

```shell
# Shorthand: Name=field becomes Name={{ .field }}
atmos list stacks --columns "MyStack=stack"
```

## Related Commands

- [`atmos list components`](/cli/commands/list/components) - List all components
- [`atmos list instances`](/cli/commands/list/list-instances) - List all component instances across stacks
- [`atmos describe stacks`](/cli/commands/describe/stacks) - Get detailed stack configuration

---

## atmos list themes

Use this command to discover available terminal themes for customizing markdown output appearance in Atmos. Preview color schemes and syntax highlighting styles to enhance readability of command output.

## Usage

```shell
atmos list themes [flags]
```

## Description

This command lists available terminal themes that can be used for markdown rendering in Atmos.

By default, it shows only recommended themes that have been tested to work well with Atmos output. These themes provide excellent readability for infrastructure-related content.

Use the `--all` flag to see the complete list of all available themes imported from popular terminal theme collections.

## Examples

### Show recommended themes (default)

```shell
atmos list themes
```

```shell
Name                           Type     Rec  Source
================================================================================
   Catppuccin Latte               Light
   Catppuccin Mocha               Dark
   Dracula                        Dark          https://github.com/zenorocha/dracula-theme
   GitHub Dark                    Dark
   Gruvbox Light                  Light         https://github.com/morhetz
   Material                       Light         https://github.com/stoeffel/material-iterm
   default                        Dark          https://cloudposse.com
   nord                           Dark          https://github.com/Teraskull

8 themes (recommended). Use --all to see all themes.
```

### Show all available themes

```shell
atmos list themes --all
```

```shell
Name                           Type     Rec  Source
================================================================================
   3024 Day                       Light         https://github.com/0x3024
   3024 Night                     Dark          https://github.com/0x3024
   Aardvark Blue                  Dark
   Abernathy                      Dark
   Adventure                      Dark          https://github.com/hongzimao/iTerm2-Color-Schemes
...
   Zeonica                        Dark

349 themes available.
```

## Flags

- **`--all`**
  Show all available themes instead of just recommended ones (default: 
  `false`
  )

## Configuration

To use a theme, set it in your `atmos.yaml` configuration file:

```yaml
settings:
  terminal:
    theme: "dracula"  # Theme name from the list
```

The theme will be applied to all markdown output, including:

- Command help text
- Error messages
- Documentation output
- Workflow descriptions

## Theme Precedence

When determining which theme to use, Atmos follows this precedence:

1. **Theme setting** - If `settings.terminal.theme` is set, that theme is used
2. **Custom colors** - If no theme is set but custom markdown colors are configured, those are used
3. **Default theme** - If neither theme nor custom colors are set, the "default" theme is used

### Combining Themes with Custom Colors

You can override specific colors of a theme by setting custom markdown colors in your configuration:

```yaml
settings:
  terminal:
    theme: "dracula"
  markdown:
    h1:
      color: "#FF79C6"  # Override H1 color while keeping other theme colors
```

## Recommended Themes

The following themes are recommended for use with Atmos as they provide excellent readability for infrastructure output:

### Dark Themes

- **default** - Atmos native theme optimized for infrastructure output
- **Dracula** - High contrast dark theme with vibrant colors
- **Catppuccin Mocha** - Modern pastel dark theme, easy on the eyes
- **Tokyo Night** - Clean theme inspired by Tokyo city lights
- **Nord** - Arctic-inspired color palette
- **Gruvbox Dark** - Retro groove theme with warm colors
- **GitHub Dark** - GitHub's familiar dark mode
- **One Dark** - Atom's iconic dark theme
- **Solarized Dark** - Scientifically-designed colors for reduced eye strain
- **Material** - Google's Material Design colors

### Light Themes

- **Catppuccin Latte** - Modern pastel light theme
- **Gruvbox Light** - Retro groove light theme with warm colors
- **GitHub Light** - GitHub's familiar light mode
- **Solarized Light** - Precision colors for comfortable daylight viewing

## Related Commands

- [`atmos docs`](/cli/commands/docs/usage) - Generate documentation with themed markdown
- [`atmos describe component`](/cli/commands/describe/component) - View component details with themed output

## Theme Credits and Attribution

The terminal themes included in Atmos are sourced from the [charmbracelet/vhs](https://github.com/charmbracelet/vhs) project, which has curated an extensive collection of terminal themes from the community.

### License

The theme collection is used under the MIT License:

- Copyright (c) 2022 Charmbracelet, Inc
- Original source: https://github.com/charmbracelet/vhs

### Individual Theme Credits

Each theme preserves its original creator's attribution in the `meta.credits` field. When you list themes, you can see the original source or creator for each theme. These themes come from various sources including:

- [iTerm2 Color Schemes](https://github.com/mbadolato/iTerm2-Color-Schemes)
- Individual theme creators and their repositories
- Terminal emulator projects.

The "default" theme is an original creation by Cloud Posse, optimized specifically for Atmos output.

We gratefully acknowledge all the theme creators whose work enhances the terminal experience for Atmos users.

---

## atmos list

Use these subcommands to list sections of Atmos configurations.

**Configure Stacks**

Learn how to configure stacks, components, and the list command column customization.

Configuration Reference[Read more](/cli/configuration/stacks)

## Usage

Atmos provides a powerful feature to customize the columns displayed by various `atmos list` commands (e.g., `atmos list stacks`, `atmos list components`, `atmos list workflows`). This allows you to tailor the tabular output to show precisely the information you need for different contexts.

Column customization is configured within your `atmos.yaml` file using Go template expressions, enabling dynamic values based on stack, component, or workflow data. This guide explains how to configure and use this feature.

## Subcommands

## Supported List Commands

| Command                    | Description                                                                                   |
|---------------------------|-----------------------------------------------------------------------------------------------|
| `atmos list stacks`       | Lists all defined **stacks** in your project. A _stack_ is a named configuration representing a deployment environment (e.g., `dev/us-east-1`, `prod/eu-west-1`). |
| `atmos list components`   | Lists all available **components** (Terraform, Helmfile, etc.) defined in the project. Components are reusable infrastructure building blocks. |
| `atmos list workflows`    | Lists all defined **workflows**, which are custom command sequences defined in `atmos.yaml` to streamline repetitive tasks. |
| `atmos list values`       | Displays the fully resolved **configuration values** for a specified component in a stack, after inheritance and imports are applied. |
| `atmos list vars`         | Lists the **Terraform `vars`** (input variables) that will be passed to a component for a given stack. Useful for debugging variable resolution. |
| `atmos list settings`     | Shows the **`settings` block**, typically used for configuring a component’s behavior (e.g., module version, backend type). |
| `atmos list metadata`     | Displays the **`metadata` block** associated with a component in a stack, including attributes like `stage`, `tenant`, `environment`, and `namespace`. |

You can define custom columns for each of these commands individually in your `atmos.yaml`.

## How Column Customization Works

To customize columns for a specific `list` command, navigate to the relevant section (e.g., `stacks`, `components`, `workflows`) in your `atmos.yaml` configuration file. Within that section, define a `list` block.

Inside the `list` block:

1. Specify the output `format` (optional, defaults to `table`). Other options include `json`, `yaml`, `csv`, `tsv`.
2. Define a `columns` array. Each element in this array represents a column in the output table and must have:
   - `name`: The string that will appear as the column header.
   - `value`: A Go template string that dynamically determines the value for each row in that column.

**Example Structure:**

```yaml
# In atmos.yaml
stacks: # Or components, workflows, etc.
  list:
    format: table # Optional
    columns:
      - name: "Header 1"
        value: "{{ .some_template_variable }}"
      - name: "Header 2"
        value: "Static Text or {{ .another_variable }}"
      # ... more columns
```

## YAML Template Syntax

The `value` field in each column definition supports Go templates. The available variables within the template depend on the specific `atmos list` command being customized:

### For `atmos list stacks`:

```yaml
{{ .stack_name }}  # Name of the stack
{{ .stack_path }}  # Filesystem path to the stack configuration file
```

### For `atmos list components`:

```yaml
{{ .component_name }}  # Name of the component
{{ .component_type }}  # Type of the component (e.g., terraform, helmfile)
{{ .component_path }}  # Filesystem path to the component directory
```

### For `atmos list workflows`:

```yaml
{{ .name }}      # The name of the workflow
{{ .file }}      # The manifest name
{{ .description }}  # The description provided for the workflow
```

### For `atmos list values`, `atmos list vars`, `atmos list settings`, and `atmos list metadata`:

```yaml
{{ .stack_name }}  # Name of the stack context
{{ .key }}         # The key or property name being listed
{{ .value }}       # The corresponding value for the key
```

## Full Reference: atmos.yaml Structure

Here's the general structure for defining custom list columns in `atmos.yaml`:

```yaml
: # e.g., stacks, components, workflows, values, vars, settings, metadata
  list:
    format: table|json|csv|yaml|tsv  # Optional, default is 'table'
    columns:
      - name: ""
        value: ""
      # ... add more column definitions as needed
```

- Replace `` with the specific scope corresponding to the `atmos list` command you want to customize (e.g., `stacks` for `atmos list stacks`).
- The `columns` array is mandatory if you want to override the default columns. If `columns` is omitted, the command uses its default output columns.

### Custom Columns for Workflows

```yaml
# In atmos.yaml
workflows:
  list:
    columns:
      - name: Workflow
        value: "{{ .name }}"  # Corresponds to the workflow key in the manifest
      - name: Manifest Name
        value: "{{ .file }}"   # Corresponds to the 'name' field within the manifest file
      - name: Description
        value: "{{ .description }}" # Corresponds to the 'description' field for the workflow
```

:::info
Note that `{{ .file }}` in this context refers to the value of the top-level `name` attribute within the workflow manifest file itself, not the path to the file.
:::

## Display Behavior

### TTY vs Non-TTY Output

The appearance of the output table depends on whether `atmos` detects an interactive terminal (TTY) or not:

- **TTY Output (e.g., running in your terminal)**
  - Displays a formatted table with borders and styling.
  - Attempts to fit within the terminal width.
  - Uses standard padding between columns (TableColumnPadding = 3).
  - Defaults to `format: table` if not specified.

- **Non-TTY Output (e.g., redirecting to a file, piping to another command)**
  - Produces a simpler, machine-readable format suitable for scripting or automation.
  - Ensures consistent structure for programmatic parsing.

## Authentication

All `atmos list` subcommands support authentication via the `--identity` flag or `ATMOS_IDENTITY` environment variable. This is useful when stack configurations use YAML template functions (e.g., `!terraform.state`, `!terraform.output`) that require cloud provider credentials.

### Using the `--identity` Flag

```shell
# Use a specific identity
atmos list stacks --identity my-aws-identity
atmos list components -s tenant1-ue2-dev -i my-aws-identity

# Disable authentication explicitly (use AWS SDK defaults)
atmos list instances --identity=false
```

### Using Environment Variables

```shell
# Set identity via environment variable
ATMOS_IDENTITY=my-aws-identity atmos list stacks

# Using the environment variable with a different command
ATMOS_IDENTITY=my-aws-identity atmos list components
```

### Stack-Level Default Identity

When neither the `--identity` flag nor environment variable is set, Atmos automatically loads your stack configuration files to find a default identity configured with `default: true`:

```yaml
# In stacks/orgs/acme/_defaults.yaml
auth:
  identities:
    my-default-identity:
      default: true  # This identity will be used automatically
```

For more details on authentication, refer to [Authentication](/cli/commands/auth/usage).

## Global Flags

All `atmos list` subcommands support these flags inherited from the parent command:

- **`--identity` / `-i` (optional)**
  Authenticate with a specific identity before executing the command.
  This is required when YAML template functions need to access remote resources requiring authentication.
  `atmos list stacks --identity my-aws-identity`

## Troubleshooting & Tips

- **Blank Columns:** If a column appears empty, double-check the template variable name (`{{ .variable }}`) against the [YAML Template Syntax](#yaml-template-syntax) section for the specific command. Ensure the data context actually contains that variable for the items being listed.
- **Inspecting Available Data:** Use the `describe` command with `--format json` or `--format yaml` (e.g., `atmos describe stacks --format json`) to see the raw data structure and available fields you can use in your templates.
- **Wide Tables:** If the table is too wide for your terminal or you encounter errors about content width:
  - Reduce the number of columns defined in your `atmos.yaml`.
  - Use a different output format like `json` or `yaml`.
  - Some `list` commands might support a `--max-columns` flag (check command help).
- **Filtering:** Use command-specific flags like `--stacks 'pattern'` for `atmos list stacks` to filter the rows, which can indirectly simplify the output. Query flags (`--query`) might also help narrow down data.

---

## atmos lsp start

Use this command to start the Atmos Language Server Protocol (LSP) server for IDE integration. The LSP server provides syntax validation, auto-completion, and hover documentation for Atmos stack files.

> ⚠️ Experimental

**Configure LSP**

Learn how to configure LSP server and client settings in your atmos.yaml.

Configuration Reference[Read more](/cli/configuration/lsp)

## Description

The `atmos lsp start` command starts the Atmos LSP server, enabling IDE integration for Atmos stack files.

The LSP server provides:

- Syntax validation and diagnostics
- JSON Schema diagnostics for `atmos.yaml` (including `atmos.d` and profile fragments), using the same generated schema as [`atmos validate schema`](/cli/commands/validate/schema)
- Auto-completion for Atmos keywords and components
- Hover documentation for Atmos-specific syntax

The server supports multiple transport protocols for different use cases:

- **stdio** — Standard input/output (default, for IDE integration)
- **tcp** — TCP server for remote connections
- **websocket** — WebSocket server for web-based editors

## Usage

```shell
atmos lsp start [flags]
```

### Flags

- **`--transport`**
  Transport protocol to use: 
  `stdio`
  , 
  `tcp`
  , or 
  `websocket`
   (default: 
  `stdio`
  )
- **`--address`**
  Address for tcp/websocket transports in 
  `host:port`
   format (default: 
  `localhost:7777`
  )

### Examples

```shell
# Start LSP server with stdio transport (default, for IDE integration)
atmos lsp start

# Start LSP server with TCP transport
atmos lsp start --transport=tcp

# Start LSP server with TCP on a custom address
atmos lsp start --transport=tcp --address=localhost:9999

# Start LSP server with WebSocket transport
atmos lsp start --transport=websocket --address=localhost:8080
```

## Editor Setup

### VS Code

Configure your VS Code LSP client to run the Atmos LSP server:

```json
{
  "atmos.lsp.command": "atmos",
  "atmos.lsp.args": ["lsp", "start", "--transport=stdio"]
}
```

### Other Editors

For detailed editor-specific setup instructions, see the [LSP Server Guide](/lsp/lsp-server).

## Related

---

## atmos mcp add

Use this command to add an MCP server to `mcp.servers` in `atmos.yaml` without hand-editing YAML.

> ⚠️ Experimental

## Description

The `atmos mcp add` command writes a new entry under `mcp.servers` in `atmos.yaml`. The target can be:

- A **built-in preset** — `self` (Atmos's own MCP server) or `atmos-pro` (the Atmos Pro MCP server).
- An **`http(s)://` URL** — added as a remote HTTP server.
- A **stdio command** — added as a local subprocess server, optionally with arguments (e.g. `"npx -y @org/mcp-server --flag value"`).

Running `atmos mcp add` with no target defaults to `atmos mcp add self`.

Only `atmos.yaml` is written — `add` never touches an AI client's config file. Use `--install` to also push the new server into detected AI clients in the same step, or run [`atmos mcp install`](/cli/commands/mcp/install) separately.

If the target resolves to the `self` preset and `mcp.enabled` is `false` in `atmos.yaml`, `add` prompts (when running interactively) to enable it — the `self` entry won't work at runtime until Atmos can run as an MCP server itself. Non-interactively, it errors with the exact `atmos config set` command to run instead.

## Usage

```shell
atmos mcp add [preset-name|url|command] [flags]
```

- **`preset-name|url|command`**
  Optional. A built-in preset name (
  `self`
  , 
  `atmos-pro`
  ), an 
  `http(s)://`
   URL, or a stdio command. Defaults to 
  `self`
   when omitted.
- **`--name`, `-n`**
  Server name. Auto-inferred from the URL or command if not provided (presets use their own default name, e.g. 
  `self`
   → 
  `atmos`
  ).
- **`--transport`, `-t`**
  Transport for a URL target. Only 
  `http`
   is supported today; other values (e.g. 
  `sse`
  ) are rejected.
- **`--env`**
  Environment variable for a stdio server, 
  `KEY=VALUE`
   (repeatable).
- **`--header`, `-H`**
  HTTP header for a remote server, 
  `"Key: Value"`
   (repeatable).
- **`--description`**
  Human-readable description shown in 
  `atmos mcp list`
  /
  `status`
  .
- **`--identity`**
  Atmos Auth identity (from the 
  `auth`
   section) for credential injection.
- **`--timeout`**
  Connection timeout, as a Go duration string (e.g. 
  `30s`
  ).
- **`--auto-start`**
  Start the server automatically when Atmos starts.
- **`--install`**
  Also install into detected AI clients immediately after adding.
- **`--yes`, `-y`**
  Skip confirmation prompts.
- **`--force`**
  Overwrite an existing entry without prompting.

### Examples

```shell
# Add Atmos's own MCP server (also the default target with no argument).
atmos mcp add self

# Add the Atmos Pro MCP server.
atmos mcp add atmos-pro

# Add a remote HTTP server with an auth header.
atmos mcp add https://mcp.example.com/mcp --header "Authorization: Bearer ${TOKEN}"

# Add a local stdio server and immediately install it into detected AI clients.
atmos mcp add "uvx awslabs.aws-documentation-mcp-server@latest" --install

# Add a server with an explicit name, description, and Atmos Auth identity.
atmos mcp add uvx --name aws-docs --description "AWS Documentation" --identity readonly
```

---

## atmos mcp export

Use this command to export a `.mcp.json` file from the MCP servers configured in `atmos.yaml`. This enables Claude Code, Cursor, and other MCP-compatible IDEs to use the same servers.

> ⚠️ Experimental

## Description

The `atmos mcp export` command reads all servers from `mcp.servers` in `atmos.yaml` and exports a `.mcp.json` file in the standard format used by Claude Code, Codex CLI, and other MCP-compatible tools.

Servers with `identity` are automatically wrapped with `atmos auth exec -i  --` so that credentials are injected when the IDE starts the server process.

## Usage

```shell
atmos mcp export [flags]
```

- **`--output`, `-o`**
  Output file path. Default: 
  `.mcp.json`

### Examples

```shell
# Export .mcp.json in the current directory
atmos mcp export

# Export to a custom path (e.g., for Cursor)
atmos mcp export --output .cursor/mcp.json
```

### Example Output

Given this `atmos.yaml`:

```yaml
mcp:
  servers:
    aws-docs:
      command: uvx
      args: ["awslabs.aws-documentation-mcp-server@latest"]
      description: "AWS Documentation"
    aws-security:
      command: uvx
      args: ["awslabs.well-architected-security-mcp-server@latest"]
      env:
        AWS_REGION: "us-east-1"
      identity: "security-audit"   # Atmos Auth identity (from the auth section)
```

The exported `.mcp.json` will be:

```json
{
  "mcpServers": {
    "aws-docs": {
      "command": "uvx",
      "args": ["awslabs.aws-documentation-mcp-server@latest"]
    },
    "aws-security": {
      "command": "atmos",
      "args": ["auth", "exec", "-i", "security-audit", "--",
               "uvx", "awslabs.well-architected-security-mcp-server@latest"],
      "env": { "AWS_REGION": "us-east-1" }
    }
  }
}
```

Note how `aws-security` is wrapped with `atmos auth exec` because it has `identity` set, while `aws-docs` uses the command directly.

---

## atmos mcp install

Use this command to install the MCP servers configured in `atmos.yaml` directly into your AI client's config files — Claude Code, Claude Desktop, Cursor, VS Code, Cline, Cline CLI, Codex, GitHub Copilot CLI, Gemini, Goose, OpenCode, Windsurf, Zed, Antigravity, and MCPorter.

> ⚠️ Experimental

## Description

The `atmos mcp install` command reads servers from `mcp.servers` in `atmos.yaml` and writes the appropriate client config across 15 supported AI clients: Claude Code, Claude Desktop, Cursor, VS Code, Cline, Cline CLI, Codex, GitHub Copilot CLI, Gemini, Goose, OpenCode, Windsurf, Zed, Antigravity, and MCPorter. Both local stdio servers and remote HTTP servers are supported.

Unlike `atmos mcp export`, which produces a single `.mcp.json` file, `install` writes directly into each detected (or explicitly selected) client's own config format and location — project-scoped or user-scoped.

## Usage

```shell
atmos mcp install [server-name...] [flags]
```

- **`server-name...`**
  Optional list of specific server names to install. When omitted, all servers configured under 
  `mcp.servers`
   are installed.
- **`--client`, `-c`**
  MCP client to install into (repeatable): 
  `claude-code`
  , 
  `cursor`
  , 
  `vscode`
  , 
  `codex`
  , 
  `gemini`
  , 
  `claude-desktop`
  , 
  `windsurf`
  , 
  `cline`
  , 
  `cline-cli`
  , 
  `zed`
  , 
  `opencode`
  , 
  `goose`
  , 
  `copilot-cli`
  , 
  `antigravity`
  , 
  `mcporter`
  . When omitted, Atmos detects clients already configured in the project.
- **`--all-clients`**
  Install into all supported MCP clients instead of just detected or explicitly selected ones.
- **`--scope`**
  Install scope: 
  `project`
   (writes into the current project, default) or 
  `user`
   (writes into the client's user-level/global config). When omitted (along with 
  `--global`
  ) in an interactive terminal, Atmos prompts you to choose; non-interactive runs (
  `--yes`
  , no TTY, or CI) fall back to 
  `project`
  .
- **`--global`, `-g`**
  Alias for 
  `--scope user`
  .
- **`--yes`, `-y`**
  Skip confirmation prompts.
- **`--dry-run`**
  Show what would be installed without writing any files.
- **`--force`**
  Overwrite existing server entries without prompting.
- **`--gitignore`**
  Add generated project-scoped config files to 
  `.gitignore`
  .

### Examples

```shell
# Install all configured MCP servers into detected project clients.
atmos mcp install

# Install one server into Cursor and Claude Code project configs.
atmos mcp install aws-docs --client cursor --client claude-code

# Install into user-level config.
atmos mcp install --scope user --client codex

# Alias for --scope user.
atmos mcp install --global --client claude-code

# Preview what would be installed without writing files.
atmos mcp install --dry-run
```

## Troubleshooting

Writing the config file is only half the story — the client still has to notice it. `atmos mcp install` reports the server as `Added`, but whether it's actually usable depends on how (and when) that particular client rereads its own config:

- **Restart required:** Claude Code, Claude Desktop, and Windsurf only read their MCP config at startup. After installing a new server, restart the app (for Claude Code, start a fresh session) before it shows up.
- **Auto-reload, no restart needed:** Zed watches its settings file and restarts the affected context server on save, so a newly installed entry becomes available without closing the editor.
- **Enable it manually:** Cursor adds the server to its config but leaves it toggled off — open **Settings → MCP** and switch it on. VS Code (and the GitHub Copilot extension) may similarly need a manual "Start" from its MCP servers view the first time it sees a new entry.
- **New session required:** Codex and Gemini CLI read their config when a session starts, so an already-running session won't pick up a server installed mid-session — start a new one.

If a server still isn't available after restarting or enabling it, run `atmos mcp install --dry-run` to confirm which file Atmos would write to, then check that file directly to make sure the entry landed where you expect.

---

## atmos mcp list

Use this command to list all external MCP servers configured in `atmos.yaml` under `mcp.servers`.

> ⚠️ Experimental

## Description

The `atmos mcp list` command displays a table of all configured MCP servers with their name, status, and description. Servers are not started — this shows configuration only. Use `atmos mcp status` to see live connection status.

## Usage

```shell
atmos mcp list
```

### Examples

```shell
# List all configured servers
atmos mcp list

# Example output:
# NAME              STATUS    DESCRIPTION
# aws-docs          stopped   AWS Documentation — search and fetch AWS docs
# aws-knowledge     stopped   AWS Knowledge — managed AWS knowledge base (remote)
# aws-pricing       stopped   AWS Pricing — real-time pricing and cost analysis
# aws-api           stopped   AWS API — direct AWS CLI access with security controls
# aws-security      stopped   AWS Security — Well-Architected security posture assessment
```

## Arguments

This command takes no arguments.

## Flags

This command has no command-specific flags.

## Configuration

Add servers under `mcp.servers` in `atmos.yaml`:

```yaml
mcp:
  servers:
    aws-docs:
      command: uvx
      args: ["awslabs.aws-documentation-mcp-server@latest"]
      description: "AWS Documentation — search and fetch AWS docs"
```

---

## atmos mcp remove

Use this command to remove an MCP server from `mcp.servers` in `atmos.yaml`.

> ⚠️ Experimental

## Description

The `atmos mcp remove` command deletes an entry from `mcp.servers` in `atmos.yaml`.

Only `atmos.yaml` is edited — `remove` never touches an AI client's config file. If the server was already pushed to a client via `atmos mcp install`, run [`atmos mcp uninstall`](/cli/commands/mcp/uninstall) separately to remove it there too. This mirrors [`atmos mcp add`](/cli/commands/mcp/add), which only writes `atmos.yaml` by default.

## Usage

```shell
atmos mcp remove  [flags]
```

- **`name`**
  Required. The server name to remove, as it appears under 
  `mcp.servers`
  .
- **`--yes`, `-y`**
  Skip the confirmation prompt.

### Examples

```shell
# Remove a server, with a confirmation prompt.
atmos mcp remove aws-docs

# Remove without prompting.
atmos mcp remove aws-docs --yes
```

---

## atmos mcp restart

Use this command to stop and restart an external MCP server. This is useful for picking up configuration changes or recovering from server errors.

> ⚠️ Experimental

## Usage

```shell
atmos mcp restart 
```

### Arguments

- **`name`**
  The name of the MCP server to restart (as shown in 
  `atmos mcp list`
  ).

## Flags

This command has no command-specific flags.

### Examples

```shell
# Restart a server
atmos mcp restart aws-docs

# Example output:
# ✓ Restarted MCP server "aws-docs" (4 tools available)
```

---

## atmos mcp start

Use this command to start the Atmos Model Context Protocol (MCP) server. The MCP server exposes Atmos AI tools through the [MCP open standard](https://modelcontextprotocol.io), enabling any compatible AI client — Claude Desktop, Claude Code, VS Code, Cursor, and [many others](https://modelcontextprotocol.io/clients) — to connect to your infrastructure tools.

> ⚠️ Experimental

**Configure MCP**

Learn how to configure MCP server and AI tool settings in your atmos.yaml.

Configuration Reference[Read more](/cli/configuration/mcp)

## Prerequisites

The MCP server must be explicitly enabled in `atmos.yaml`:

```yaml
mcp:
  enabled: true
ai:
  enabled: true
  tools:
    enabled: true
```

The MCP server is **disabled by default**. Enabling AI features alone (`ai.enabled: true`) does not enable MCP.

## Description

The `atmos mcp start` command starts the Atmos MCP server, enabling AI clients to query your infrastructure using Atmos tools.

The server supports two transport protocols:

- **stdio** (default) — Standard input/output for desktop apps like Claude Desktop, VS Code, and Cursor
- **http** — Streamable HTTP for remote access, shared servers, and CI/CD

## Usage

```shell
atmos mcp start [flags]
```

### Flags

- **`--transport`**
  Transport type: 
  `stdio`
   (default) or 
  `http`
- **`--host`**
  Host to bind HTTP server (HTTP transport only). Default: 
  `localhost`
- **`--port`**
  Port to bind HTTP server (HTTP transport only). Default: 
  `8080`

### Examples

```shell
# Start MCP server with stdio transport (default, for desktop clients)
atmos mcp start

# Start MCP server with HTTP transport
atmos mcp start --transport http

# Start HTTP server on custom host and port
atmos mcp start --transport http --host 127.0.0.1 --port 3000
```

## Transport Modes

- **How it works**
  **stdio (default):**
   Client spawns Atmos as a subprocess; communication over stdin/stdout. 
  **HTTP:**
   Atmos listens on an HTTP port using the Streamable HTTP transport.
- **Best for**
  **stdio:**
   Desktop apps, local development. 
  **HTTP:**
   Remote access, shared servers, CI/CD.
- **Multiple clients**
  **stdio:**
   No (1:1 with client). 
  **HTTP:**
   Yes.
- **Network exposure**
  **stdio:**
   None. 
  **HTTP:**
   Requires security measures.

### HTTP Endpoints

When running with `--transport http`, the server exposes:

- **`/` (all methods)**
  The MCP endpoint. Handles the full Streamable HTTP protocol -- 
  `POST`
   for client messages, 
  `GET`
   to optionally upgrade to a server-initiated stream, 
  `DELETE`
   to end a session -- all on this single path.
- **`GET /health`**
  Returns 
  `{"status":"healthy"}`
  . Use for monitoring and liveness probes.

:::warning HTTP Transport
When using `--transport http`, the server has no built-in authentication. Always add firewall rules, VPN, or a reverse proxy before exposing it on a network. The default **stdio** transport has no network exposure.
:::

## Related

---

## atmos mcp status

Use this command to check the live connection status of all configured MCP servers. Each server is started, tested for connectivity, and its tool count is displayed.

> ⚠️ Experimental

## Usage

```shell
atmos mcp status
```

### Examples

```shell
# Show status of all servers
atmos mcp status

# Example output:
# NAME              STATUS    TOOLS   DESCRIPTION
# aws-docs          running   4       AWS Documentation — search and fetch AWS docs
# aws-knowledge     running   2       AWS Knowledge — managed AWS knowledge base
# aws-pricing       running   7       AWS Pricing — real-time pricing and cost analysis
# aws-api           running   3       AWS API — direct AWS CLI access
# aws-security      error     0       AWS Security (credentials not configured)
```

### Status Values

- **`running`**
  Server started, handshake complete, ping successful.
- **`degraded`**
  Server started but ping failed.
- **`error`**
  Server failed to start (check credentials, command path, or network).

## Flags

This command has no command-specific flags.

---

## atmos mcp test

Use this command to test connectivity to an external MCP server. It performs a full connectivity check: starts the server, verifies the initialization handshake, lists available tools, and pings the server.

> ⚠️ Experimental

## Usage

```shell
atmos mcp test 
```

### Arguments

- **`name`**
  The name of the MCP server (as shown in 
  `atmos mcp list`
  ).

## Flags

This command has no command-specific flags.

### Examples

```shell
# Test the AWS documentation server (no credentials needed)
atmos mcp test aws-docs

# Example output:
# ✓ Server started successfully
# ✓ Initialization handshake complete
# ✓ 4 tools available
# ✓ Server responds to ping

# Test a server that requires credentials
atmos mcp test aws-security
```

---

## atmos mcp tools

Use this command to connect to an external MCP server and list the tools it exposes. This starts the server, performs the MCP initialization handshake, retrieves the tool list, then shuts down the server.

> ⚠️ Experimental

## Usage

```shell
atmos mcp tools 
```

### Arguments

- **`name`**
  The name of the MCP server (as shown in 
  `atmos mcp list`
  ).

## Flags

This command has no command-specific flags.

### Examples

```shell
# List tools from the AWS documentation server
atmos mcp tools aws-docs

# List tools from the AWS security server
atmos mcp tools aws-security
# Example output:
# TOOL                      DESCRIPTION
# CheckSecurityServices     Verify security services are enabled
# GetSecurityFindings       Retrieve security findings with severity filtering
# CheckStorageEncryption    Check encryption on S3, EBS, RDS, DynamoDB, EFS
# CheckNetworkSecurity      Check TLS/HTTPS on ELB, VPC, API Gateway
# ListServicesInRegion      List active AWS services in a region
```

---

## atmos mcp uninstall

Use this command to remove MCP servers previously installed into AI client config files.

> ⚠️ Experimental

## Description

The `atmos mcp uninstall` command removes matching server entries from each targeted client's config file. It's the mirror image of [`atmos mcp install`](/cli/commands/mcp/install) — it only touches client config files and never edits `atmos.yaml`.

With no server names given, it uninstalls everything currently declared under `mcp.servers`. If a client's config file becomes empty after removal, the file is left in place (not deleted) — matching `install`'s behavior of never deleting files.

## Usage

```shell
atmos mcp uninstall [server-name...] [flags]
```

- **`server-name...`**
  Optional list of specific server names to uninstall. When omitted, all servers configured under 
  `mcp.servers`
   are uninstalled.
- **`--client`, `-c`**
  MCP client to uninstall from (repeatable): 
  `claude-code`
  , 
  `cursor`
  , 
  `vscode`
  , 
  `codex`
  , 
  `gemini`
  , 
  `claude-desktop`
  , 
  `windsurf`
  , 
  `cline`
  , 
  `cline-cli`
  , 
  `zed`
  , 
  `opencode`
  , 
  `goose`
  , 
  `copilot-cli`
  , 
  `antigravity`
  , 
  `mcporter`
  . When omitted, Atmos detects clients already configured in the project.
- **`--all-clients`**
  Uninstall from all supported MCP clients instead of just detected or explicitly selected ones.
- **`--scope`**
  Uninstall scope: 
  `project`
   (default) or 
  `user`
  . When omitted (along with 
  `--global`
  ) in an interactive terminal, Atmos prompts you to choose; non-interactive runs (
  `--yes`
  , no TTY, or CI) fall back to 
  `project`
  .
- **`--global`, `-g`**
  Alias for 
  `--scope user`
  .
- **`--yes`, `-y`**
  Skip confirmation prompts.
- **`--dry-run`**
  Show what would be removed without writing any files.

### Examples

```shell
# Remove all configured servers from detected project clients.
atmos mcp uninstall

# Remove one server from Cursor and Claude Code project configs.
atmos mcp uninstall aws-docs --client cursor --client claude-code

# Remove from user-level config.
atmos mcp uninstall --scope user --client codex

# Preview what would be removed without writing files.
atmos mcp uninstall --dry-run
```

---

## atmos packer build

Use this command to process a Packer template configured for an Atmos component in a stack, and build it to generate a set of artifacts.
The builds specified within a template are executed in parallel, unless otherwise specified.
The artifacts that are created will be outputted at the end of the build, and a Packer manifest
(if configured in the Atmos component) will be updated with the results of the build.

## Usage

Execute the `packer build` command like this:

```shell
atmos packer build  --stack  [flags] -- [packer-options]
```

:::tip
For more details on the `packer build` command and options, refer to [Packer build command reference](https://developer.hashicorp.com/packer/docs/commands/build).
:::

## Arguments

- **`component` (required)**

  Atmos Packer component.

## Flags

- **`--stack` (alias `-s`)(required)**

  Atmos stack.
- **`--template` (alias `-t`)(optional)**

  Packer template file or directory path. Defaults to `.` (component working directory), which tells Packer
  to load all `*.pkr.hcl` files from the component directory.

  Can also be specified via `settings.packer.template` in the stack manifest.
  The command line flag takes precedence.

## Examples

### Directory Mode (Default)

When no template is specified, Packer loads all `*.pkr.hcl` files from the component directory.
This is the recommended approach for components with multiple HCL files:

```shell
# Uses all *.pkr.hcl files in the component directory (recommended)
atmos packer build aws/bastion --stack nonprod

# Explicit directory mode
atmos packer build aws/bastion --stack prod --template .
```

### Single File Mode

For components that require a specific template file:

```shell
atmos packer build aws/bastion -s prod --template main.pkr.hcl
atmos packer build aws/bastion -s nonprod -t main.nonprod.pkr.hcl
```

```shell
> atmos packer build aws/bastion --stack nonprod

amazon-ebs.al2023:

==> amazon-ebs.al2023: Prevalidating any provided VPC information
==> amazon-ebs.al2023: Prevalidating AMI Name: bastion-al2023-1754025080
==> amazon-ebs.al2023: Found Image ID: ami-0013ceeff668b979b
==> amazon-ebs.al2023: Setting public IP address to true on instance without a subnet ID
==> amazon-ebs.al2023: No VPC ID provided, Packer will use the default VPC
==> amazon-ebs.al2023: Inferring subnet from the selected VPC "vpc-xxxxxxx"
==> amazon-ebs.al2023: Set subnet as "subnet-xxxxxxx"
==> amazon-ebs.al2023: Creating temporary keypair: packer_688c4c79-f14a-b77e-ca1e-b5b4c17b4581
==> amazon-ebs.al2023: Creating temporary security group for this instance: packer_688c4c7b-3f16-69f9-0c39-88a3fcbe94fd
==> amazon-ebs.al2023: Authorizing access to port 22 from [0.0.0.0/0] in the temporary security groups...
==> amazon-ebs.al2023: Launching a source AWS instance...
==> amazon-ebs.al2023: changing public IP address config to true for instance on subnet "subnet-xxxxxxx"
==> amazon-ebs.al2023: Instance ID: i-0b621ca091aa4c240
==> amazon-ebs.al2023: Waiting for instance (i-0b621ca091aa4c240) to become ready...
==> amazon-ebs.al2023: Using SSH communicator to connect: 18.222.63.67
==> amazon-ebs.al2023: Waiting for SSH to become available...
==> amazon-ebs.al2023: Connected to SSH!
==> amazon-ebs.al2023: Provisioning with shell script: /var/folders/rt/fqmt0tmx3fs1qfzbf3qxxq700000gn/T/packer-shell653292668
==> amazon-ebs.al2023: Waiting for process with pid 2085 to finish.
==> amazon-ebs.al2023: Amazon Linux 2023 Kernel Livepatch repository   154 kB/s |  16 kB     00:00
==> amazon-ebs.al2023: Package jq-1.7.1-49.amzn2023.0.2.aarch64 is already installed.
==> amazon-ebs.al2023: Dependencies resolved.
==> amazon-ebs.al2023: Nothing to do.
==> amazon-ebs.al2023: Complete!
==> amazon-ebs.al2023: 17 files removed
==> amazon-ebs.al2023: Stopping the source instance...
==> amazon-ebs.al2023: Stopping instance
==> amazon-ebs.al2023: Waiting for the instance to stop...
==> amazon-ebs.al2023: Creating AMI bastion-al2023-1754025080 from instance i-0b621ca091aa4c240
==> amazon-ebs.al2023: Attaching run tags to AMI...
==> amazon-ebs.al2023: AMI: ami-0b2b3b68aa3c5ada8
==> amazon-ebs.al2023: Waiting for AMI to become ready...
==> amazon-ebs.al2023: Skipping Enable AMI deprecation...
==> amazon-ebs.al2023: Skipping Enable AMI deregistration protection...
==> amazon-ebs.al2023: Modifying attributes on AMI (ami-0b2b3b68aa3c5ada8)...
==> amazon-ebs.al2023: Modifying: ami org arns
==> amazon-ebs.al2023: Modifying attributes on snapshot (snap-09ad35550e1438fb2)...
==> amazon-ebs.al2023: Adding tags to AMI (ami-0b2b3b68aa3c5ada8)...
==> amazon-ebs.al2023: Tagging snapshot: snap-09ad35550e1438fb2
==> amazon-ebs.al2023: Creating AMI tags
==> amazon-ebs.al2023: Adding tag: "Stage": "nonprod"
==> amazon-ebs.al2023: Adding tag: "ScanStatus": "pending"
==> amazon-ebs.al2023: Adding tag: "SourceAMI": "ami-0013ceeff668b979b"
==> amazon-ebs.al2023: Adding tag: "SourceAMIDescription": "Amazon Linux 2023 AMI 2023.7.20250527.1 arm64 HVM kernel-6.12"
==> amazon-ebs.al2023: Adding tag: "SourceAMIName": "al2023-ami-2023.7.20250527.1-kernel-6.12-arm64"
==> amazon-ebs.al2023: Adding tag: "SourceAMIOwnerAccountId": "137112412989"
==> amazon-ebs.al2023: Creating snapshot tags
==> amazon-ebs.al2023: Terminating the source AWS instance...
==> amazon-ebs.al2023: Cleaning up any extra volumes...
==> amazon-ebs.al2023: No volumes to clean up, skipping
==> amazon-ebs.al2023: Deleting temporary security group...
==> amazon-ebs.al2023: Deleting temporary keypair...
==> amazon-ebs.al2023: Running post-processor:  (type manifest)
Build 'amazon-ebs.al2023' finished after 3 minutes 39 seconds.

==> Wait completed after 3 minutes 39 seconds

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs.al2023: AMIs were created:
us-east-2: ami-0b2b3b68aa3c5ada8

--> amazon-ebs.al2023: AMIs were created:
us-east-2: ami-0b2b3b68aa3c5ada8
```

---

## atmos packer init

Use this command to initialize Packer and install plugins according to an HCL template configuration for an Atmos component in a stack.

## Usage

Execute the `packer init` command like this:

```shell
atmos packer init  --stack  [flags] -- [packer-options]
```

:::tip
For more details on the `packer init` command and options, refer to [Packer init command reference](https://developer.hashicorp.com/packer/docs/commands/init).
:::

## Arguments

- **`component` (required)**

  Atmos Packer component.

## Flags

- **`--stack` (alias `-s`)(required)**

  Atmos stack.
- **`--template` (alias `-t`)(optional)**

  Packer template.
  It can be specified in the `settings.packer.template` section in the Atmos component manifest,
  or on the command line via the flag `--template