# Atmos Migration

> This skill helps you migrate a repository to Atmos. It covers native Terraform, Terraform Workspaces, Terramate, Terragrunt, Makefiles, Justfiles, and Taskfiles. It gives minimum-disruption paths, file-layout options, workspace mapping, task-to-command mapping, generate_hcl/script decomposition, and the remote-state bridge for a step-by-step migration; also covers migrating CLI tool-version management from mise or Aqua CLI to the Atmos toolchain.

- Skill: `cloudposse/atmos-migration` (Agent Skill, multi-file: 12 files)
- Install (CLI): `npx skillmds@latest add cloudposse/atmos-migration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cloudposse/atmos-migration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: cloudposse (https://skillmd.com/u/cloudposse)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cloudposse/atmos-migration

---


# Migrating to Atmos

## Overview

This skill is a decision guide. Use it to migrate an existing Terraform repository to Atmos.
Atmos can adopt an existing repository without a reorganization. The `components/terraform/`
layout is a recommendation. It is not a requirement. Start with the smallest change that gives
value. Add more only when the user has a real need for it.

This skill also covers migrating CLI tool-version management from mise or Aqua CLI to the Atmos
toolchain -- see [from-mise.md](references/from-mise.md) and
[from-aqua.md](references/from-aqua.md) in the routing table below.

For full tutorials for end users, see:

- [Migrating from Native Terraform](https://atmos.tools/migration/native-terraform)
- [Migrating from Terraform Workspaces](https://atmos.tools/migration/terraform-workspaces)
- [Migrating from Terragrunt](https://atmos.tools/migration/terragrunt) -- see
  [from-terragrunt.md](references/from-terragrunt.md) for the agent-actionable recipes
- Migrating from Terramate -- covered by this skill via
  [references/from-terramate.md](references/from-terramate.md) (no atmos.tools tutorial yet)
- [Migrating from Makefiles](https://atmos.tools/migration/makefile)
- [Migrating from Justfiles](https://atmos.tools/migration/justfile)
- [Migrating from Taskfile.yml](https://atmos.tools/migration/taskfile)

## Terraform or OpenTofu

This skill applies the same way to Terraform and to OpenTofu. Atmos runs the binary set in
`components.terraform.command` in `atmos.yaml`. The default binary is `terraform`. The migration
steps, file layouts, and the remote-state bridge do not change based on the binary. Use the same
word the user uses. If the user says "OpenTofu," write "OpenTofu" in your response.

## Core Principles

These principles come before your normal instincts. Read them before you propose a change to the
user's repository.

1. **Migration is opt-in, not all-or-nothing.** Atmos does not require a filesystem
    reorganization. Point `base_path` at the user's existing layout (e.g., `base_path: "terraform"`
    or `base_path: "."`) when preserving layout lowers adoption risk. The `components/terraform/`
    convention is still the best-practice layout for new or fully migrated repos because Atmos
    supports multiple toolchains (Terraform, Helmfile, Packer, Ansible); it is not a prerequisite
    for adopting Atmos in Terraform-only repos.
2. **Existing `.tfvars` files may be kept during migration.** Use `!include` to pull them into
    stacks when the user wants minimal disruption. Converting values into native stack YAML remains
    the best-practice end state when the user wants deep-merge inheritance and richer stack
    composition, but it can happen progressively.
3. **No Terraform code changes are required.** Don't rewrite providers, backends, or modules
    during migration. Atmos generates `backend.tf.json` and `*.auto.tfvars.json` at runtime.
4. **Workspaces are not the enemy.** If the user has `terraform.workspace`-driven environments,
    Atmos can map onto their existing state via `metadata.terraform_workspace` and
    `workspace_key_prefix`. They do not have to abandon their workspace state to adopt Atmos.
5. **Prefer YAML functions over Gomplate datasources.** When both can express the same thing
    (`!include` vs `gomplate.datasources` for files, `!exec` vs templated shell, `!env` vs
    `gomplate getenv`, `!store` vs custom datasource URLs), reach for the YAML function first.
    YAML functions are type-safe, can't break YAML parsing, produce clear errors, and don't
    require enabling Gomplate. See the [atmos-yaml-functions](../atmos-yaml-functions/SKILL.md)
    and [atmos-templates](../atmos-templates/SKILL.md) skills for the boundary.
6. **Crawl → walk → run.** Get the user to a working `atmos terraform plan` in 20 minutes; defer
    inheritance, catalogs, and multi-account hierarchies until they have a concrete need.
7. **Task runners are not a blocker.** Atmos custom commands and workflows can replace the
    targets, recipes, and tasks that Make, Just, and Task provide. This doesn't have to happen all
    at once — a Makefile, Justfile, or Taskfile can stay as a thin wrapper around `atmos` commands
    during migration, the same incremental approach described in Principle 6. The end state turns
    each leaf target into a custom command; a target chain usually stays a custom command too,
    using `dependencies.commands`/`dependencies.workflows` for its prerequisites. Reserve
    workflows for fixed, multi-step orchestration across more than one component — not every
    dependency chain needs one.

## Decide the Migration Shape First

Find the user's source pattern before you propose any change. Each pattern points to a different
reference file:

| User has...                                                          | Use reference                                    |
|----------------------------------------------------------------------|--------------------------------------------------|
| One TF root module, env config via `.tfvars` or env vars             | [from-native-terraform.md](references/from-native-terraform.md) |
| Multiple TF root modules in scattered dirs                           | [from-native-terraform.md](references/from-native-terraform.md) |
| `terraform.workspace`-driven environments with shared state backend  | [from-terraform-workspaces.md](references/from-terraform-workspaces.md) |
| `.tm.hcl` files, `stack.tm.hcl`, `generate_hcl` blocks (Terramate project) | [from-terramate.md](references/from-terramate.md) |
| Need to read outputs from un-migrated TF (legacy or another repo)    | [remote-state-bridge.md](references/remote-state-bridge.md) |
| User has a Makefile driving builds/tests/deploys                     | [from-makefile.md](references/from-makefile.md) |
| User has a Justfile (`just` command runner)                          | [from-justfile.md](references/from-justfile.md) |
| User has a Taskfile.yml (go-task)                                    | [from-taskfile.md](references/from-taskfile.md) |
| `cloudposse/github-action-atmos-component-updater`                   | [from-component-updater.md](references/from-component-updater.md) |
| Terragrunt (`terragrunt.hcl` or `terragrunt.stack.hcl`)               | [from-terragrunt.md](references/from-terragrunt.md) |
| mise config (`mise.toml`, `.mise.toml`, `.mise/config.toml`, `.tool-versions`) for tool versions | [from-mise.md](references/from-mise.md) |
| `aqua.yaml` (Aqua CLI) for tool versions                             | [from-aqua.md](references/from-aqua.md) |

The remote-state-bridge pattern makes progressive migration possible. It lets a team migrate one
component at a time. Without it, the team must migrate everything at once. Use this pattern when
the user has existing Terraform state that a new Atmos component must read.

### Common Problems in Task-Runner Migration

These behaviors apply to every task runner. Check them before you open a reference file:

- **The default order can change, and it differs by source tool.** Task runs `deps:` at the same
  time by default, so command-level `dependencies.commands`/`dependencies.workflows` -- also
  concurrent by default -- is its direct match. Make and Just run dependencies one after another
  by default; `make -j` is required for concurrency. Do not describe `dependencies.commands` as
  matching Make's/Just's *default* -- it changes the order, and can introduce a race between
  prerequisites that were only ever sequential by accident, not by a declared dependency. For an
  ordinary Make/Just chain, ordered steps preserve the default; reach for `dependencies.commands`
  there only when the source used `-j`, the prerequisites are genuinely independent, or a
  prerequisite is shared by more than one caller (it dedups a shared dependency to a single run
  regardless of concurrency -- true for every one of these tools). Check the source tool's real
  default before you move it.
- **Freshness checks map to `inputs`/`artifacts`, not to plain steps -- and the scope is per
  step.** Task's `sources:`/`generates:` fields and non-`.PHONY` Make targets both skip the
  *entire* recipe/task when a file has not changed. Atmos's step-level `inputs.sources`/
  `artifacts.paths` fields are the direct match: with no explicit `when:`, declaring them
  implicitly means `when: checksum.changed`, and *that one step* is skipped when nothing has
  changed since its last successful run -- later steps in the same command still run regardless.
  If the source recipe/task runs more than one command and the freshness decision must gate all
  of them together, combine them into a single `shell`/`script` step rather than spreading
  `inputs`/`artifacts` across several steps. This does not carry over on its own -- add
  `inputs`/`artifacts` to the migrated step yourself. The `require`/`assert` step type does not
  replace this. It only checks that a file exists, not whether it is fresh.
- **`workflows.base_path` needs to be set explicitly once the user has their own `atmos.yaml`.**
  Only fixed, multi-step orchestration across more than one component becomes an Atmos workflow
  (Principle 7) -- most target chains stay a custom command with `dependencies.commands` instead.
  `atmos workflow <name>` fails with
  `'workflows.base_path' must be configured in 'atmos.yaml'` until you add it (for example,
  `workflows.base_path: "stacks/workflows"`). None of this skill's `atmos.yaml` snippets show it
  by default -- add it the moment the user's migration reaches its first workflow.

Each reference file has its own "Common Problems" section with the exact field names and steps
for that tool. This section is only a short summary.

## The Minimum-Viable Migration

Use this checklist when the user wants to try Atmos on an existing repository. Do not change the
order unless the user's setup requires it.

1. **Install Atmos.** See `atmos.tools/install`.
2. **Create `atmos.yaml`** at the repo root, pointing `base_path` and `components.terraform.base_path`
    at the user's existing layout. Do not ask them to move files.
3. **Create one stack file** for one environment. Use `!include` of an existing `.tfvars` file so
    nothing has to be rewritten:
    ```yaml
    # stacks/dev.yaml
    import:
      - _defaults
    components:
      terraform:
        vpc:
          vars: !include ../path/to/existing/dev.tfvars
    ```
4. **Run `atmos terraform plan vpc -s dev`** and confirm output matches what `terraform plan
    -var-file=dev.tfvars` produced before.

A working example of this shape is at `examples/native-terraform/` in the Atmos repository.

## File-Layout Options

Pick the layout that matches the user's goals. Atmos recommends the `components/terraform/`
layout, especially for a new repository or a multi-tool project. You can keep an existing layout
when the user wants less disruption.

| `base_path`                              | Use when                                                                |
|------------------------------------------|-------------------------------------------------------------------------|
| `base_path: "."`                         | TF root modules live at the repo root; user wants zero file moves       |
| `base_path: "terraform"`                 | TF-only repo with code already in `terraform/`; preserve dir name       |
| `base_path: "."` + `components.terraform.base_path: "components/terraform"` | Multi-toolchain or new repo; canonical Atmos layout |

For more organization patterns, such as multi-region, multi-account, and organization
hierarchies, see the skill [atmos-design-patterns](../atmos-design-patterns/SKILL.md).

## YAML Functions vs Gomplate Datasources

This is a common mistake: an agent chooses a Gomplate datasource when a YAML function is safer
and clearer. Use the option in the right column:

| Goal                          | Reach for (NOT this)                              | Use instead                              |
|-------------------------------|---------------------------------------------------|------------------------------------------|
| Include a file's contents     | `gomplate.datasources` with file URL              | `!include path/to/file`                  |
| Read an environment variable  | `gomplate getenv "FOO"`                           | `!env FOO`                               |
| Run a shell command           | Template + `gomplate exec`                        | `!exec "command"`                        |
| Read a store value            | Custom datasource URL                             | `!store store_name component stack key`  |
| Read Terraform output         | Templated remote-state datasource                 | `!terraform.state component output`      |
| Get current AWS account ID    | `gomplate.datasources` AWS plugin                 | `!aws.account_id`                        |

A YAML function checks its own types. It gives a clear error message. It works without Gomplate
turned on. It does not require the template text to stay valid YAML. Use a Go template only for
control flow, such as a conditional, a loop, or a dynamic key, that a YAML function cannot
express. See [atmos-templates](../atmos-templates/SKILL.md) for when to use a Go template.

## What Does NOT Need to Change

Tell the user this list first, if they are afraid of a large rewrite. None of these items must
change to adopt Atmos:

- **Terraform code.** Providers, resources, data sources, and modules stay the same.
- **Module sources.** A local path, such as `source = "../../modules/foo"`, or a registry
  source, keeps working.
- **Backend code.** You can delete the `backend "s3" {}` block from the `.tf` files, because
  Atmos creates `backend.tf.json`. Or you can keep the block and turn off backend generation in
  `atmos.yaml`. Both methods work.
- **`.tfvars` files.** Atmos reads them through `!include`. Convert them to YAML later, only if
  the user wants deep-merge inheritance.
- **Custom provider configuration.** Providers stay in the `.tf` files. Pass environment
  variables through stack `env:`. Pass Terraform variables through stack `vars:`.

## When to Escalate to Other Skills

After the minimum migration works, the user will often ask what to do next. Send each question
to the correct skill:

- **Organize many stacks**, such as by organization, tenant, account, or region. Use
  [atmos-design-patterns](../atmos-design-patterns/SKILL.md).
- **Build abstract components, inheritance, or catalog patterns.** Use
  [atmos-components](../atmos-components/SKILL.md).
- **Use deep merging, imports, or overrides.** Use [atmos-stacks](../atmos-stacks/SKILL.md).
- **Vendor third-party components.** Use [atmos-vendoring](../atmos-vendoring/SKILL.md).
- **Set up authentication or provider credentials.** Use [atmos-auth](../atmos-auth/SKILL.md).
- **Add validation policies, such as OPA or JSON Schema.** Use
  [atmos-validation](../atmos-validation/SKILL.md).
- **Set up CI/CD with affected-component detection.** Use [atmos-ci](../atmos-ci/SKILL.md).
- **Share data between components through a store.** Use
  [atmos-stores](../atmos-stores/SKILL.md).

## Anti-Patterns

Push back if a user or another agent proposes one of these methods during migration:

- **"You must move all Terraform into `components/terraform/` before you use Atmos."** This is
  false. That layout is a recommendation, not a requirement. Let the user pick: adopt the
  recommended layout now, or point `base_path` at the current layout and reorganize later.
- **"You must rewrite all `.tfvars` files as YAML before you run Atmos."** This is false. Native
  stack YAML is the best final format for inheritance and composition. But `!include` lets the
  user keep existing `.tfvars` files during a step-by-step migration.
- **"Delete your workspace state and start over."** This is false. Connect the existing state
  with `metadata.terraform_workspace` and the remote-state-bridge pattern.
- **"Add a Gomplate datasource for everything."** This is false. Use a YAML function first.
- **"Adopt the full multi-account organization hierarchy on day one."** This is false. Start
  with one stack file.
- **"Wrap atmos commands in a Makefile, Justfile, or Taskfile forever."** This is false. A
  wrapper is a good bridge while the user builds trust in Atmos. But it is not the final state.
  Change each leaf target to a custom command. An ordinary Make or Just dependency chain (for
  example, `deploy: build test`) stays a custom command with ordered steps or
  `dependencies.commands` -- it does not need a workflow. A Taskfile's `deps:` is different: Task
  runs `deps:` concurrently by default, so it maps directly onto `dependencies.commands` (also
  concurrent by default) on the custom command -- reach for ordered steps instead only when the
  user's dependency chain actually requires serial execution. Reserve workflows for fixed,
  multi-step orchestration across more than one component, not for an ordinary target chain.

## Additional Resources

- [References/from-native-terraform.md](references/from-native-terraform.md): steps for a plain
  Terraform migration, matched to each shape.
- [References/from-terraform-workspaces.md](references/from-terraform-workspaces.md): how to map
  workspaces to stacks without losing state.
- [References/remote-state-bridge.md](references/remote-state-bridge.md): the dummy-component and
  abstract-component patterns. Use them to read state from Terraform that is not yet migrated, or
  from an external repository.
- [References/from-terramate.md](references/from-terramate.md): construct-by-construct mapping
  from Terramate (`stack.tm.hcl`, globals, `generate_hcl`, `script{}`, tags/labels) to Atmos,
  including the one remaining known gap (`.tmtriggers`).
- [references/from-terragrunt.md](references/from-terragrunt.md) -- concept mapping and migration
  workflow for classic Terragrunt and Terragrunt Stacks.
- [References/from-makefile.md](references/from-makefile.md): steps for a Makefile, matched to
  each shape.
- [References/from-justfile.md](references/from-justfile.md): steps for a Justfile, matched to
  each shape.
- [References/from-taskfile.md](references/from-taskfile.md): steps for a Taskfile.yml (go-task)
  file, matched to each shape.
- [References/from-mise.md](references/from-mise.md) -- migrating tool versions, tasks, and env
  vars from mise to the Atmos toolchain.
- [References/from-aqua.md](references/from-aqua.md) -- migrating tool versions from Aqua CLI's
  `aqua.yaml` to the Atmos toolchain.

