Skip to main content

Manage Multiple Terraform/OpenTofu Versions

Learn how to install and manage multiple versions of Terraform and OpenTofu using the Atmos toolchain manager.

Overview​

Atmos includes a built-in toolchain manager that can install and manage multiple versions of Terraform, OpenTofu, and other infrastructure tools. This is useful when:

  • Different projects require different Terraform versions
  • You need to test compatibility with newer versions
  • Your team needs consistent tool versions across machines

Installing Tool Versions​

Install a Specific Version​

# Install a specific Terraform version
atmos toolchain install terraform@1.9.0

# Install a specific OpenTofu version
atmos toolchain install opentofu@1.8.0

Install the Latest Version​

# Install the latest Terraform
atmos toolchain install terraform@latest

# Install the latest OpenTofu
atmos toolchain install opentofu@latest

Using .tool-versions​

The .tool-versions file (compatible with asdf) allows you to pin tool versions per project:

.tool-versions
terraform 1.9.0
opentofu 1.8.0

When you run atmos terraform plan, Atmos uses .tool-versions as the project's default tool environment. With the default toolchain.install: auto policy, if the file pins the component's executable (terraform, or tofu when the component sets command: tofu), Atmos installs that version when it is missing. Other tools in the file are added to PATH only if they are already installed, for example with atmos toolchain install. If a tool lists multiple versions, the first is its default. Terraform output lookups, including !terraform.output and atmos.Component(), use these defaults too.

Every Atmos command inherits already-installed versions selected by the file on PATH. Cached tools absent from the file, and other cached versions, are not added. Explicit dependencies.tools override the baseline; installation policy controls downloads separately. Under auto:

  • Helmfile components also install helm when the file pins it.
  • Custom commands and hooks use only the tools that are already installed. Atmos installs just their own dependencies.tools.
  • Workflows install every tool listed in .tool-versions that is missing before running the first step, because a workflow can call any project tool.

Set toolchain.install to change how eagerly Atmos installs tools:

atmos.yaml
toolchain:
install: auto # never | declared | auto | always
  • never installs nothing. Tools are used only when already installed, and a missing dependencies.tools entry is an error.
  • declared installs only dependencies.tools (and, for workflows, every .tool-versions tool). Other runs inherit installed project selections without installing missing defaults. Projects pinned to an edition before 2026-10-09 get this value automatically.
  • auto is the default described above.
  • always installs every tool in .tool-versions for every run.

If the file does not pin the component's executable, Atmos installs nothing for it and uses the system PATH. Entries with the version system, ref:..., or path:..., a bare operator such as ~> (write ~>1.9.0 without a space), or a tool name Atmos cannot resolve are skipped without failing the run, as are lines that list a tool without a version (with a warning). Two entries for the same tool with different versions, such as tofu 1.12.6 and opentofu/opentofu 1.11.0, are an error.

Explicit dependencies.tools entries always override .tool-versions, even when they select a different version or use a version constraint. For example:

.tool-versions
hashicorp/terraform 1.9.0
stacks/deploy/prod.yaml
components:
terraform:
legacy-vpc:
dependencies:
tools:
terraform: "1.8.5"

Here, legacy-vpc uses Terraform 1.8.5; components without an override use 1.9.0. Atmos treats a short name or configured alias and its qualified name as the same tool when it compares the manifest with explicit dependencies, so terraform overrides hashicorp/terraform. Running the component does not change the project pin in .tool-versions.

Precedence is .tool-versions → stack-wide dependencies.tools → component-type dependencies → component-instance dependencies. Existing constraint checks between explicit stack scopes still apply. If neither the manifest nor stack configuration declares a tool, Atmos falls back to the system PATH.

Setting Versions​

# Set Terraform version for current project
atmos toolchain set terraform@1.9.0

# This creates or updates .tool-versions

Listing Installed Versions​

# List all installed tools
atmos toolchain list

# Search for available Terraform versions
atmos toolchain registry search terraform

Component-Level Versions in Stacks​

Different Terraform components can use different versions. This is useful when:

  • Legacy components require older Terraform versions
  • New components need features from newer versions
  • Gradual upgrades without breaking existing infrastructure

Global Tool Dependencies​

Define default versions at the root level of your stack:

stacks/orgs/acme/_defaults.yaml
# Global tool dependencies - applies to all components
dependencies:
tools:
terraform: "1.9.8"
kubectl: "1.28.0"

Component-Type Dependencies​

Override for all Terraform components:

stacks/orgs/acme/plat/prod/_defaults.yaml
terraform:
dependencies:
tools:
terraform: "1.9.8"
tflint: "0.44.1"

Per-Component Override​

Specify versions for individual components:

stacks/orgs/acme/plat/prod/us-east-1.yaml
components:
terraform:
vpc:
dependencies:
tools:
terraform: "1.9.8" # Use current version
aws-cli: "2.13.0"

legacy-app:
dependencies:
tools:
terraform: "1.5.0" # Legacy component needs older version
aws-cli: "2.10.0"

How It Works​

When you run a component, Atmos automatically:

  1. Resolves the tool version from the most specific level
  2. Installs the tool if not already cached
  3. Executes the command with that version
# Uses terraform 1.9.8 (from vpc component config)
atmos terraform plan vpc -s prod/us-east-1

# Uses terraform 1.5.0 (from legacy-app component config)
atmos terraform plan legacy-app -s prod/us-east-1

For complete documentation on component dependencies, see Configure Dependencies.

Best Practices​

  1. Pin exact versions in CI/CD: Use exact versions (e.g., 1.9.0) in pipelines for reproducibility
  2. Use constraints in development: Allow minor version flexibility (e.g., >=1.9.0, <1.10.0) for local development
  3. Commit .tool-versions: Include .tool-versions in version control so the team uses consistent versions
  4. Test upgrades in isolation: Install new versions and test before updating .tool-versions

See Also​