Migrating to Atmos
SkillFiles & storageThis 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.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Migrating to Atmos skill
What this skill tells your AI
The instructions your AI receives, as published by cloudposse/atmos in agent-skills/skills/atmos-migration/SKILL.md and read by ahel’s review.
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 and from-aqua.md in the routing table below.
For full tutorials for end users, see:
- Migrating from Native Terraform
- Migrating from Terraform Workspaces
- Migrating from Terragrunt -- see from-terragrunt.md for the agent-actionable recipes
- Migrating from Terramate -- covered by this skill via references/from-terramate.md (no atmos.tools tutorial yet)
- Migrating from Makefiles
- Migrating from Justfiles
- Migrating from Taskfile.yml
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.
- Migration is opt-in, not all-or-nothing. Atmos does not require a filesystem
reorganization. Point
base_pathat the user's existing layout (e.g.,base_path: "terraform"orbase_path: ".") when preserving layout lowers adoption risk. Thecomponents/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. - Existing
.tfvarsfiles may be kept during migration. Use!includeto 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. - No Terraform code changes are required. Don't rewrite providers, backends, or modules
during migration. Atmos generates
backend.tf.jsonand*.auto.tfvars.jsonat runtime. - Workspaces are not the enemy. If the user has
terraform.workspace-driven environments, Atmos can map onto their existing state viametadata.terraform_workspaceandworkspace_key_prefix. They do not have to abandon their workspace state to adopt Atmos. - Prefer YAML functions over Gomplate datasources. When both can express the same thing
(
!includevsgomplate.datasourcesfor files,!execvs templated shell,!envvsgomplate getenv,!storevs 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 and atmos-templates skills for the boundary. - Crawl → walk → run. Get the user to a working
atmos terraform planin 20 minutes; defer inheritance, catalogs, and multi-account hierarchies until they have a concrete need. - 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
atmoscommands 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, usingdependencies.commands/dependencies.workflowsfor 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 |
| Multiple TF root modules in scattered dirs | from-native-terraform.md |
terraform.workspace-driven environments with shared state backend | from-terraform-workspaces.md |
.tm.hcl files, stack.tm.hcl, generate_hcl blocks (Terramate project) | from-terramate.md |
| Need to read outputs from un-migrated TF (legacy or another repo) | remote-state-bridge.md |
| User has a Makefile driving builds/tests/deploys | from-makefile.md |
User has a Justfile (just command runner) | from-justfile.md |
| User has a Taskfile.yml (go-task) | from-taskfile.md |
cloudposse/github-action-atmos-component-updater | from-component-updater.md |
Terragrunt (terragrunt.hcl or terragrunt.stack.hcl) | from-terragrunt.md |
mise config (mise.toml, .mise.toml, .mise/config.toml, .tool-versions) for tool versions | from-mise.md |
aqua.yaml (Aqua CLI) for tool versions | 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-leveldependencies.commands/dependencies.workflows-- also concurrent by default -- is its direct match. Make and Just run dependencies one after another by default;make -jis required for concurrency. Do not describedependencies.commandsas 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 fordependencies.commandsthere 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'ssources:/generates:fields and non-.PHONYMake targets both skip the entire recipe/task when a file has not changed. Atmos's step-levelinputs.sources/artifacts.pathsfields are the direct match: with no explicitwhen:, declaring them implicitly meanswhen: 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 singleshell/scriptstep rather than spreadinginputs/artifactsacross several steps. This does not carry over on its own -- addinputs/artifactsto the migrated step yourself. Therequire/assertstep type does not replace this. It only checks that a file exists, not whether it is fresh. workflows.base_pathneeds to be set explicitly once the user has their ownatmos.yaml. Only fixed, multi-step orchestration across more than one component becomes an Atmos workflow (Principle 7) -- most target chains stay a custom command withdependencies.commandsinstead.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'satmos.yamlsnippets 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.
- Install Atmos. See
atmos.tools/install. - Create
atmos.yamlat the repo root, pointingbase_pathandcomponents.terraform.base_pathat the user's existing layout. Do not ask them to move files. - Create one stack file for one environment. Use
!includeof an existing.tfvarsfile so nothing has to be rewritten:# stacks/dev.yaml import: - _defaults components: terraform: vpc: vars: !include ../path/to/existing/dev.tfvars - Run
atmos terraform plan vpc -s devand confirm output matches whatterraform plan -var-file=dev.tfvarsproduced 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.
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 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.tffiles, because Atmos createsbackend.tf.json. Or you can keep the block and turn off backend generation inatmos.yaml. Both methods work. .tfvarsfiles. 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
.tffiles. Pass environment variables through stackenv:. Pass Terraform variables through stackvars:.
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.
- Build abstract components, inheritance, or catalog patterns. Use atmos-components.
- Use deep merging, imports, or overrides. Use atmos-stacks.
- Vendor third-party components. Use atmos-vendoring.
- Set up authentication or provider credentials. Use atmos-auth.
- Add validation policies, such as OPA or JSON Schema. Use atmos-validation.
- Set up CI/CD with affected-component detection. Use atmos-ci.
- Share data between components through a store. Use atmos-stores.
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 pointbase_pathat the current layout and reorganize later. - "You must rewrite all
.tfvarsfiles as YAML before you run Atmos." This is false. Native stack YAML is the best final format for inheritance and composition. But!includelets the user keep existing.tfvarsfiles during a step-by-step migration. - "Delete your workspace state and start over." This is false. Connect the existing state
with
metadata.terraform_workspaceand 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 ordependencies.commands-- it does not need a workflow. A Taskfile'sdeps:is different: Task runsdeps:concurrently by default, so it maps directly ontodependencies.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: steps for a plain Terraform migration, matched to each shape.
- References/from-terraform-workspaces.md: how to map workspaces to stacks without losing state.
- 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: 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 -- concept mapping and migration workflow for classic Terragrunt and Terragrunt Stacks.
- References/from-makefile.md: steps for a Makefile, matched to each shape.
- References/from-justfile.md: steps for a Justfile, matched to each shape.
- References/from-taskfile.md: steps for a Taskfile.yml (go-task) file, matched to each shape.
- References/from-mise.md -- migrating tool versions, tasks, and env vars from mise to the Atmos toolchain.
- References/from-aqua.md -- migrating tool versions from Aqua CLI's
aqua.yamlto the Atmos toolchain.
Signals
- GitHub stars
- 1k
- Forks
- 175
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
atmos-migration- Source
- github.com/cloudposse/atmos