--- name: atmos-terraform description: "Terraform and OpenTofu orchestration: plan/apply/deploy, workspace management, backend config, varfile generation, authentication, binary selection (terraform/tofu), mixed-binary setups" metadata: copyright: Copyright Cloud Posse, LLC 2026 version: "1.0.0" category: orchestrators --- # Atmos Terraform and OpenTofu Orchestration Atmos wraps the Terraform or OpenTofu CLI to provide stack-aware orchestration of infrastructure operations. Instead of manually managing workspaces, backends, variable files, and authentication for each component, Atmos resolves the full configuration from stack manifests and handles all of these concerns automatically. Everything in this skill applies identically to **Terraform** and **OpenTofu**. The `atmos terraform` command namespace is the same regardless of which binary is configured -- `atmos terraform plan` runs `tofu plan` when the binary is set to `tofu`. Use the user's terminology in responses (if they say "OpenTofu," say "OpenTofu"). ## Terraform or OpenTofu: Binary Selection Atmos defaults to the `terraform` binary. Switch to OpenTofu by setting `components.terraform.command: tofu` in `atmos.yaml`. The setting cascades through CLI > env > config > defaults precedence and can be overridden at multiple levels for mixed setups. ### Global (whole project on OpenTofu) ```yaml # atmos.yaml components: terraform: command: tofu # All atmos terraform commands invoke `tofu` instead of `terraform` base_path: components/terraform ``` Or via environment variable: ```bash export ATMOS_COMPONENTS_TERRAFORM_COMMAND=tofu atmos terraform plan vpc -s dev # Runs: tofu plan ``` ### Per-stack override Use `terraform.overrides.command` in a stack manifest to switch the binary for everything in that stack: ```yaml # stacks/orgs/acme/plat/prod/_defaults.yaml terraform: overrides: command: tofu ``` ### Per-component override Set `command` on an individual component to run a single component on a different binary than the rest of the stack (useful for legacy components that haven't been validated on OpenTofu, or new components testing OpenTofu-specific features): ```yaml components: terraform: legacy-vpc: command: terraform # This component stays on Terraform vars: ... new-eks: command: tofu # This component runs on OpenTofu vars: ... ``` ### Per-invocation override Pass `--terraform-command` on the CLI to override for a single command: ```bash atmos terraform plan vpc -s dev --terraform-command=tofu ``` ### Installing and Pinning the Binary via Toolchain The Atmos toolchain installs and pins both Terraform and OpenTofu so the same binary version runs on every developer machine and in CI. Binary **selection** (`command: terraform` vs `command: tofu`) and binary **version pinning** (`dependencies.tools.terraform` vs `dependencies.tools.opentofu`) are independent settings -- `command` says *which* binary Atmos invokes, `dependencies.tools.` says *which version* the toolchain installs. Always pin both together; setting `command: tofu` without a matching `opentofu` dependency leaves you at the mercy of whatever `tofu` is on PATH. Pinning can be applied at four scopes, in increasing precedence: 1. **Project-wide** -- `.tool-versions` (asdf-compatible) at the repo root for defaults everyone shares. 2. **Stack-wide** -- `dependencies.tools` in a stack default to pin a whole stack scope. 3. **Component-type** -- `terraform.dependencies.tools` to default every Terraform/OpenTofu component. 4. **Per-component** -- `dependencies.tools` on an individual component for migrations or one-offs. Atmos installs declared tools when running the component. Use `atmos toolchain install` only to pre-warm a cache, bootstrap a shell, or troubleshoot a specific binary. For full YAML examples, resolution order, ad-hoc installs, and the inspection workflow, see [references/toolchain-pinning.md](references/toolchain-pinning.md) and the [atmos-toolchain](../atmos-toolchain/SKILL.md) skill. ### OpenTofu-specific considerations - **State encryption** (OpenTofu 1.7+) -- a Terraform-incompatible feature; if enabled, state can no longer be read by `terraform`. Switching back is not zero-effort. - **`removed` blocks** -- supported by both binaries in recent versions; no Atmos-side difference. - **Provider/module registry** -- OpenTofu uses `registry.opentofu.org` by default; pinned modules sourced from `registry.terraform.io` still work in OpenTofu but consult the relevant registry availability when the user reports a missing module. - **`terraform.required_version`** -- OpenTofu respects this constraint; pin appropriately. - **`terraform { backend ... }` block** -- identical syntax in both binaries; no Atmos-side change needed. For users running mixed Terraform/OpenTofu deployments, the per-component or per-stack override pattern is the right tool. Do not propose a project-wide switch unless the user has validated all components against OpenTofu. ## How Atmos Orchestrates Terraform When you run any `atmos terraform` command, Atmos performs the following sequence: 1. **Resolves stack configuration** -- Reads and deep-merges all stack manifests to produce the fully resolved configuration for the target component in the target stack. 2. **Generates backend configuration** -- Writes a `backend.tf.json` file in the component directory with the correct backend settings (S3 bucket, key, region, etc.) derived from the stack config. 3. **Generates variable file** -- Writes a `terraform.tfvars.json` file containing all `vars` defined for the component in the stack. 4. **Provisions backend infrastructure** -- If `provision.backend.enabled: true`, creates the backend storage (e.g., S3 bucket) before Terraform init. 5. **Runs `terraform init`** -- Initializes the working directory with the generated backend config, skipping it automatically when nothing relevant changed (`init.mode`), and adding `-reconfigure`/`-upgrade` only when required (`init.reconfigure`/`init.upgrade`). Cleans `.terraform/environment` first. 6. **Selects or creates workspace** -- Calculates the Terraform workspace name from context variables and selects it (or creates it if it does not exist). 7. **Executes the requested command** -- Runs `terraform plan`, `apply`, `destroy`, etc. with the generated varfile and any additional flags. This means a single command like `atmos terraform plan vpc -s plat-ue2-dev` replaces what would normally require multiple manual steps: configuring the backend, writing tfvars, running init, selecting the workspace, and then running plan. ## Core Commands ### plan Generates a Terraform execution plan showing what changes would be made. ```shell atmos terraform plan -s ``` By default, Atmos saves the plan to a file using the naming convention `-.planfile`. This planfile can later be used with `--from-plan` to apply the exact reviewed changes. ```shell # Basic plan atmos terraform plan vpc -s plat-ue2-dev # Skip planfile generation (useful for Terraform Cloud) atmos terraform plan vpc -s dev --skip-planfile # Plan with custom output path atmos terraform plan vpc -s dev -out=/tmp/my-plan.tfplan # Plan only specific resources atmos terraform plan vpc -s dev -target=aws_subnet.private ``` ### apply Applies Terraform changes. Supports interactive approval, planfile-based apply, and auto-approve. ```shell atmos terraform apply -s ``` ```shell # Interactive apply (prompts for confirmation) atmos terraform apply vpc -s plat-ue2-dev # Apply from a previously generated plan atmos terraform plan vpc -s dev atmos terraform apply vpc -s dev --from-plan # Apply a specific planfile atmos terraform apply vpc -s dev --planfile /tmp/my-plan.tfplan # Auto-approved apply (no confirmation prompt) atmos terraform apply vpc -s dev -auto-approve ``` ### deploy Combines plan and apply with automatic approval. This is the most common command for CI/CD pipelines. ```shell atmos terraform deploy -s ``` Key differences from `apply`: - Automatically sets `-auto-approve` -- no interactive confirmation - Supports `--deploy-run-init` to control whether init runs - Designed for automated, non-interactive deployments ```shell # Deploy a component atmos terraform deploy vpc -s plat-ue2-dev # Deploy from a previously generated plan atmos terraform deploy vpc -s dev --from-plan # Deploy a specific planfile atmos terraform deploy vpc -s dev --planfile /tmp/vpc-plan.tfplan ``` ### destroy Destroys all resources managed by a component in a stack. Accepts `-auto-approve` and `-target=...` just like upstream Terraform: `atmos terraform destroy vpc -s dev`. ### init Atmos runs `terraform init` automatically before plan, apply, and deploy, but only when needed (`init.mode`), adding `-reconfigure`/`-upgrade` only when required (`init.reconfigure`/`init.upgrade`); `--skip-init` disables auto-init for one invocation. Upstream init flags pass through when invoked directly (`-reconfigure`, `-upgrade`, `-migrate-state`): `atmos terraform init vpc -s dev -reconfigure`. ### Streaming UI (`--ui`) `plan`, `apply`, `deploy`, `init`, and `destroy` support a `--ui` flag that renders a live, Docker-build-style progress view (spinners, a dependency tree, per-resource status) instead of raw Terraform output: ```shell atmos terraform apply vpc -s dev --ui ``` Enable it by default via `components.terraform.ui.enabled: true` in atmos.yaml or `ATMOS_TERRAFORM_UI=true`; pass `--ui=false` to override a config-enabled default for one run. The UI auto-disables when output is piped, in CI (`CI=true`), or on unsupported commands. `refresh` doesn't support it — Terraform's `refresh` doesn't emit the structured `-json` output the UI depends on; `--ui refresh` prints a warning and falls back to standard output. ## Multi-Component Operations Atmos supports executing Terraform commands across multiple components simultaneously using filter flags. These work with `plan`, `apply`, and `deploy`. ```shell # All components in all stacks atmos terraform plan --all # All components in a specific stack atmos terraform plan --stack prod # Specific components across stacks atmos terraform deploy --components vpc,eks # Only components affected by git changes (in dependency order) atmos terraform deploy --affected # Affected with dependents included atmos terraform deploy --affected --include-dependents # Filter by YQ query expression atmos terraform plan --query '.vars.tags.team == "eks"' # Combine filters atmos terraform plan --affected --stack prod # Always preview first with --dry-run atmos terraform deploy --all --dry-run ``` ## Workspace Management Atmos computes the Terraform workspace name from stack context (namespace, tenant, environment, stage, component), runs `terraform init -reconfigure` (always, regardless of `init.reconfigure`), and selects (or creates) the workspace on every invocation. Explicit management is also available: `atmos terraform workspace vpc -s plat-ue2-dev`. For stable workspace keys across component implementations, use `metadata.name` on an abstract base component (`name: vpc`, `component: vpc/v2`); for dynamic naming, `workspace_key_prefix` under `backend:` accepts Go templates. Toggle the runtime behavior via `components.terraform.init.reconfigure` (supersedes `init_run_reconfigure`) and `workspaces_enabled` in `atmos.yaml`. Full examples are in [references/backend-configuration.md](references/backend-configuration.md). ## Backend Configuration and Auto-Generation With `components.terraform.auto_generate_backend_file: true` in `atmos.yaml`, Atmos reads `backend_type` and `backend` from the resolved stack config, deep-merges through the stack hierarchy (org → tenant → environment → stage → component), and writes `backend.tf.json` into the component directory before `terraform init`. Add `backend.tf.json` to `.gitignore`. Regenerate manually with `atmos terraform generate backend vpc -s plat-ue2-dev`. For full examples covering S3, GCS, Azure, and remote backends, plus workspace key prefix patterns, see [references/backend-configuration.md](references/backend-configuration.md). ## Variable File Generation Atmos generates `terraform.tfvars.json` from the `vars` section in the stack configuration. This happens automatically before plan/apply/deploy, but can also be invoked manually: ```shell atmos terraform generate varfile vpc -s plat-ue2-dev # Output to a custom file atmos terraform generate varfile vpc -s plat-ue2-dev -f vars.json ``` ### Planfile Generation Generate planfiles in JSON or YAML format for review or integration with tools like Checkov: ```shell atmos terraform generate planfile vpc -s plat-ue2-dev atmos terraform generate planfile vpc -s dev --format=json atmos terraform generate planfile vpc -s dev --format=yaml --file=planfile.yaml ``` ## Authentication Configuration Terraform commands can use Atmos auth identities from `atmos.yaml` or component-level `auth.identity`. Keep detailed provider/profile setup in `atmos-auth`; this skill should only recommend the Terraform runtime controls: ```shell atmos terraform plan vpc -s prod --identity prod-admin atmos terraform plan vpc -s dev --identity "" ``` ## Backend Provisioning Backend configuration and backend provisioning are different: - `backend_type` and `backend` tell Terraform where state lives and generate `backend.tf.json`. - `provision.backend.enabled: true` tells Atmos to create the backend storage before first use. Set `provision.backend.enabled: true` in stack config to auto-provision backend infrastructure, solving the Terraform bootstrap problem. Manual provisioning is available via `atmos terraform backend create/list/describe/update/delete`. Backend provisioning currently applies to Terraform components. See [references/backend-configuration.md](references/backend-configuration.md) for details. ## Source Provisioning and Workdirs Use `source` for just-in-time component provisioning and pair it with `provision.workdir` for isolated per-instance execution. Workdirs prevent shared `.terraform`, lockfile, backend, and varfile collisions when the same component source is used by multiple stacks or runs. ```yaml components: terraform: vpc: source: uri: github.com/cloudposse-terraform-components/aws-vpc.git version: 1.450.0 provision: workdir: enabled: true ``` With workdirs enabled, Atmos stages the provisioned source into the instance workdir and runs Terraform there. Without workdirs, source provisioning targets the component path. ## Interactive Shell The `shell` command drops you into a shell pre-configured with all the context for a component in a stack. Varfiles, backend config, and workspace are all set up so you can run native Terraform commands directly. ```shell atmos terraform shell vpc -s plat-ue2-dev ``` Inside the shell: - The working directory is the component's folder - `terraform.tfvars.json` and `backend.tf.json` are generated - The correct workspace is selected - All required ENV vars are set - `ATMOS_SHLVL` tracks shell nesting level Customize the shell prompt in `atmos.yaml`: ```yaml components: terraform: shell: prompt: "atmos [{{.Stack}}] {{.Component}} $ " ``` ## Additional Commands Atmos supports `output`, `validate`, `state`, `clean`, `console`, `fmt`, `get`, `import`, `show`, `taint`/`untaint`, `force-unlock`, `refresh`, `graph`, and `providers` -- all standard Terraform subcommands with the same `atmos terraform -s ` syntax. For the complete reference, see [references/commands-reference.md](references/commands-reference.md). ```shell # Common examples atmos terraform output vpc -s dev vpc_id atmos terraform state list vpc -s dev atmos terraform clean vpc -s dev ``` ## Common Flags | Flag | Short | Description | |------|-------|-------------| | `--stack` | `-s` | Target Atmos stack (required for single-component) | | `--dry-run` | | Preview without executing | | `--skip-init` | | Skip automatic `terraform init` | | `--from-plan` | | Apply a previously generated planfile | | `--all` | | Target all components | | `--affected` | | Target git-affected components | | `--identity` | | Override authentication identity | Use `--` to pass flags directly to Terraform: `atmos terraform plan vpc -s dev -- -refresh=false`. For a default that should apply on every run instead of being retyped, declare it under `components.terraform.flags` (or a stack/component-level `flags:` block) — see [Configuration in atmos.yaml](#configuration-in-atmosyaml) below. For the complete flag reference, see [references/commands-reference.md](references/commands-reference.md). ## Path-Based Component Resolution You can use filesystem paths instead of component names: ```shell cd components/terraform/vpc atmos terraform plan . -s dev atmos terraform apply . -s dev ``` Supported path formats: `.`, `./component`, `../sibling`, `/absolute/path`. If a path matches multiple components, Atmos prompts for selection in interactive mode. ## Debugging - `atmos describe component vpc -s plat-ue2-dev` -- show the fully resolved configuration (merged vars, backend, workspace name, metadata, settings). - `atmos terraform plan vpc -s dev --dry-run` -- preview what Atmos will do without executing. - `TF_LOG=DEBUG atmos terraform plan vpc -s dev` -- enable upstream Terraform debug logging. ## Configuration in atmos.yaml Key settings under `components.terraform` include `auto_generate_backend_file`, `init.mode`, `init.reconfigure`, `init.upgrade` (see `init` above), `workspaces_enabled`, `deploy_run_init`, `apply_auto_approve`, and `plan.skip_planfile`. Each has a matching `ATMOS_COMPONENTS_TERRAFORM_*` environment variable override. See [references/backend-configuration.md](references/backend-configuration.md) for complete configuration details. `components.terraform.flags` sets fleet-wide defaults for terraform CLI execution flags (`lock_timeout`, `lock`, `parallelism`, `refresh`, `compact_warnings`) — e.g. `lock_timeout: "5m"` so concurrent runs retry a held state lock instead of failing on Terraform's `0s` default. The same `flags:` block can be set at the stack level (root-level `terraform:` block) and per component, each overriding the layer below field-by-field. See [Flags](https://atmos.tools/cli/configuration/components/terraform#flags). ## Best Practices 1. **Use the two-stage plan/apply workflow for production.** Run `plan` first, review the output, then `apply --from-plan` to ensure exactly the reviewed changes are applied. 2. **Use `deploy` for automated pipelines.** It combines plan and apply with auto-approve, ideal for CI/CD. 3. **Always preview multi-component operations with `--dry-run`** before executing `--all` or `--affected`. 4. **Let Atmos manage backend configuration.** Set `auto_generate_backend_file: true` and define backend settings in stack manifests rather than hardcoding in Terraform modules. 5. **Use `atmos describe component`** to debug configuration resolution issues. It shows the fully merged result of all stack manifest inheritance. 6. **Add generated files to .gitignore.** The `backend.tf.json` and `terraform.tfvars.json` files are generated at runtime and should not be committed. 7. **Use `atmos terraform shell`** for interactive debugging. It sets up the full context so you can run native terraform commands directly. 8. **Enable backend provisioning** (`provision.backend.enabled: true`) to solve the Terraform bootstrap problem and ensure backends exist before first use. 9. **Use source provisioning with workdirs** when components are pulled via `source`, especially in CI or any multi-stack workflow that can run concurrently. ## Additional Resources - For the complete list of all `atmos terraform` subcommands, see [references/commands-reference.md](references/commands-reference.md) - For backend configuration patterns (S3, GCS, Azure, remote), see [references/backend-configuration.md](references/backend-configuration.md) - For toolchain-based Terraform/OpenTofu version pinning at each scope, see [references/toolchain-pinning.md](references/toolchain-pinning.md)