When working with any type of code, stability and consistency are important, and Infrastructure as Code is no exception. The IaC you write today should work tomorrow in the same way it does now. But what if something outside your code changes? How do you minimize the risk of external updates and improve the reliability of your IaC? One possible mitigation is using Terraform version constraints.

Why Do You Need Terraform Versioning?

Each new release of Terraform brings with it new features and functionality, but also potential breaking changes. It can be tempting to jump on the latest version of Terraform to use a new feature or upgrade to the latest provider versions to leverage new resources and data sources.

However, new versions may change existing functionality or replace certain arguments, resulting in a configuration that won't generate a successful terraform plan. Controlling what versions are used for the Terraform Core executable, provider plugins, and modules is key to having a stable and consistent experience.

Understanding Terraform Versions

To best understand where and how to control versioning, we first need to talk about semantic versioning and how it applies to Terraform. Semantic versioning is used in software releases and is composed of three numbered identifiers separated by dots: Major.Minor.Patch - e.g. v1.5.12.

The generally accepted practice for updating an identifier on a release is:

  • Patch release - Includes bug fixes only. No new features or breaking changes.
  • Minor release - Includes new features, enhancements, and deprecation announcement. No breaking changes.
  • Major release - Includes significant changes, deprecation of features, and breaking changes.

As you can imagine, updating to a new patch version, say going from version 1.5.1 to 1.5.2, is unlikely to impact your configurations. Upgrading from version 1.14.0 to 2.0.0 could introduce significant changes that will prevent your configuration from running properly.

Note: Versions of Terraform before the 1.0 major release often contained breaking changes between minor versions. The switch from version 0.11 to 0.12 was particularly painful as it also switched to HashiCorp Configuration Language (HCL) v2.0 in the process. HashiCorp has pledged that configurations written for Terraform version 1.0 or newer will be compatible with all future releases of Terraform, while on major version 1.0.

Terraform Core releases always come with notes that include what has changed since the previous version. It's always a good idea to read the release notes for any major or minor version releases.

What is My Terraform Version?

The easiest way to determine your current Terraform version is by using the terminal:

> terraform version
Terraform v1.5.5
on windows_amd64

You can also use terraform -v to get the current version of your Terraform executable. The command will also check in with the Terraform website and let you know if you're not running the latest version.

What Are The Latest Versions Of Terraform And Major Providers?

As of writing, the latest version of the Terraform executable and the major Terraform providers are as follows:

PulumiTerraform
Terraform Core terraform1.6.2
Amazon Web Servicesaws5.22.0
Microsoft Azureazurerm3.77.0
Google Cloudgoogle5.3.0
Kuberneteskubernetes2.23.0
Alibaba Cloudalicloud1.211.2
Oracle Cloud Infrastructureoci5.17.0

Minor version updates of the most popular providers, like the Azure or AWS providers, tend to be released on a weekly cadence. These releases include bug fixes, new resources, and new resource attributes for existing resources. You can find a listing of providers and their latest version by going to the Terraform public registry.

Terraform Version Constraints

You can control the version of Terraform, a provider, or a module by using version constraints. We'll cover the exact arguments for Terraform modules and providers later in the post. First, let's take a look at how we can constrain the version of Terraform that is compatible with a given configuration.

The general constraint syntax consists of an operator and a semantic version:

= "1.1.1" # Equals the exact version 1.1.1
= "> 2.0" # Greater than version 2.0
= "<= 2.9" # Less than or equal to version 2.9

You can combine constraints together, separating them with commas:

= ">= 2.0, < 3.0" # Greater than or equal to 2.0 and less than 3.0

There is also a special expression that only allows the rightmost identifier to increment.

= "~> 3.0" # Any version that is part of major version 3, e.g. 3.1.1, 3.5.57

The [.code]~>[.code] expression is especially useful if you want to specify a particular major version including all minor and patch releases.

You can define the version of Terraform that is compatible with a configuration using the [.code]required_version[.code] argument inside the Terraform block:

terraform {
  required_version = ">= 1.2, < 2.0"
}

The above constraint would allow Terraform version 1.2 and any major version 1.X release to work with the configuration while blocking major version 2.X or later.

If you attempt to use a version of Terraform Core that doesn't meet the constraint, you will receive an error message:

>terraform validate 
╷
│ Error: Unsupported Terraform Core version
│
│   on terraform.tf line 2, in terraform:
│    2:   required_version = ">= 1.2, < 2.0"
This configuration does not support Terraform version 1.1.2. To proceed, 
either choose another supported Terraform version or update this version
constraint. Version constraints are normally set for good reason, so 
updating the constraint may lead to other errors or unexpected behavior.

Note: One potential reason for configuring the [.code]required_version[.code], is to stay on Terraform version 1.5.7 or earlier. The release of Terraform v1.6 includes the new Business Source License (BSL).  

As a result, using Terraform v1.6 will preclude you from using some additional open source tools that will not support Terraform releases under the BSL license (for example, Terragrunt and Terratest creators Gruntwork will only support up to Terraform v1.5.x).

If you have concerns over how the BSL may impact your organization, you can stay on the previous Mozilla Public License v2 by including [.code]<=1.5.7[.code] in your required version argument.

How to Upgrade Terraform Versions

Upgrading your version of Terraform Core is as simple as downloading the desired version from the available releases and replacing your current local executable. You can also update using one of many supported package managers, depending on your operating system.

If you would like to maintain multiple local versions of the Terraform Core executable, you can leverage the tfenv or tfswitch tools to install and select the desired version of Terraform to use with a given configuration.

If you are leveraging a workflow automation platform to perform your Terraform runs, then the version of Terraform is usually set when the automation is created. You should be able to change the version of Terraform used by going into the properties of the automation. For instance, on env0 you can update the Terraform version for an environment by changing it in the VCS Details.

Which Terraform Version to Use?

When you are writing a new configuration, you should choose the latest stable version of Terraform Core, the provider plugins, and modules. However, your choice of version may be limited by the current organization policies or supported options on your automation platform of choice.

For existing configurations, it's usually best practice to remain on the minor version used when the configuration was originally deployed. Patch version upgrades will assist with potential bugs and security flaws, but it's probably not worth the risk of updating to a new minor or major version unless there is some compelling feature driving your decision to upgrade.

Licensing Concerns

As mentioned earlier, HashiCorp has changed the licensing for Terraform from Mozilla Public License v2 (MPLv2) to Business Source License (BSL). Versions 1.6.0 and later will use this new license. So, if you have concerns about how it may impact your organization, you should continue using version 1.5.X or older.

Even if your organization is not directly impacted by the licensing change, as most organizations are not building a product competitive to HashiCorp, you may still be impacted depending on the additional tools you use to extend Terraform. Several open source tool developers, such as Gruntworks, have chosen to stop supporting Terraform after version 1.5.5.

You may also want to check out the OpenTofu project, an open-source alternative to Terraform, which is meant to act as a drop-in replacement. The source for OpenTofu was forked from version 1.5.6 of Terraform Core, and the project is hosted with the Linux Foundation, ensuring that it will remain open source and community driven. 

OpenTofu is following the same version numbering as Terraform to help with migration and feature tracking.

env0 is a proud member of the OpenTofu project and supports running OpenTofu on the platform as a first-class integration. You can read more about it from env0's CTO & Co-founder, and one of the project’s steering committee members, Omry Hay.

Terraform Provider Versions

In addition to the Terraform Core executable, provider plugins are also versioned. Providers follow the same general semantic versioning conventions as Terraform Core. Bug fixes will be part of a patch release, new resource types or attributes will be part of a minor release, and deprecated resources or arguments will be part of a major release. You can expect at least one breaking change in a major version release of a provider or sometimes several, so it's especially important to read the release notes.

For instance, here is a section of the release notes from the latest major version of the AWS provider:

There are over 100 breaking changes in all. I hope you didn't accidentally upgrade to version 5.0.0 without checking first!

By default, Terraform will download the latest version of a provider plugin during initialization, unless you specify otherwise with a version constraint. Provider requirements and versions are defined in the [.code]required_providers[.code] block inside of the [.code]terraform[.code] block:

terraform {
  required_version = ">= 1.2, < 2.0"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
  }
}

Each provider entry will include a source and optionally a version argument. The source defines where the provider plugin can be found, in our case the azurerm provider is being sourced from the public registry. The [.code]version[.code] argument uses the same version constraint syntax as the [.code]required_version[.code] argument. In the example above, we are requiring the latest version of the [.code]azurerm[.code] provider that is on major version 3.X.

Identifying Providers in a Terraform Configuration

You can view the required providers and version constraints for a configuration using the [.code]terraform providers[.code] command:

> terraform providers

Providers required by configuration:
.
├── provider[registry.terraform.io/hashicorp/azurerm] ~> 3.0
└── provider[terraform.io/builtin/terraform]

The required list indicates providers used by both the root module and any child modules.

After you initialize the configuration – downloading the provider plugins in the process – you can view the currently installed providers and their version with the [.code]terraform version[.code] command:

> terraform version 
Terraform v1.5.4
on windows_amd64
+ provider registry.terraform.io/hashicorp/azurerm v3.77.0

How to Upgrade to a New Provider Version

When you initialize a Terraform configuration, in addition to downloading the provider plugins, Terraform also creates the .terraform.lock.hcl lock file. Contained in the file is a record of the current version constraint, the version of the plugin downloaded, and hashes of each required provider.

provider "registry.terraform.io/hashicorp/azurerm" {
  version     = "3.77.0"
  constraints = "~> 3.0"
  hashes = [
    "h1:7XzdPKIJoKazb3eMhQbjcVWRRu5RSsHeDNYUQW6zSAc=",
    "zh:071c82025cda506af90302b3e89f61e086ad9e3b97b8c55382d5aed6f207cf10",
    ...
  ]
}

The lock file is meant to be checked into source control, providing a consistent deployment experience across the team and automation platform. When a lock file is present, Terraform will honor the constraints and versions inside that file, downloading the exact versions of the providers listed.

Terraform will not use a newer version of the provider even if it meets the version constraint, unless you use the [.code]-upgrade[.code] flag with the [.code]terraform init[.code]command.

Likewise, if you change the version constraint within the configuration, Terraform will not update the provider version installed or the lock file contents unless you include the [.code]-upgrade[.code] flag.

In this [.code]required_providers[.code] block below, the version constraint has been changed to [.code]~>2.0[.code].

terraform {
  required_version = ">= 1.2, < 2.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>2.0" # CHANGED TO 2.0
    }
  }
}

Running a [.code]terraform init[.code] after the change will result in an error.

> terraform init
Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- terraform.io/builtin/terraform is built in to Terraform
╷
│ Error: Failed to query available provider packages
│
│ Could not retrieve the list of available versions for provider 
hashicorp/azurerm: locked provider registry.terraform.io/hashicorp/azurerm 
3.77.0 does not match configured version constraint ~> 2.0; must use 
terraform init -upgrade to allow selection of new versions

If you want to upgrade to a newer version of the provider or change the constraint, you'll need to include the [.code]-upgrade[.code] flag:

> terraform init -upgrade

Initializing provider plugins...
- terraform.io/builtin/terraform is built in to Terraform
- Finding hashicorp/azurerm versions matching "~> 2.0"...
- Installing hashicorp/azurerm v2.99.0...
- Installed hashicorp/azurerm v2.99.0 (signed by HashiCorp)

Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

The initialization process will download the latest version of the provider that meets the constraint and update the lock file.

Terraform Module Versioning

Modules in Terraform may also support versioning, depending on the source of the module. Modules that are sourced from a private or public registry support the [.code]version[.code] argument in the [.code]module[.code] block:

module "shared_vnet" {
  source   = "Azure/vnet/azurerm"
  version  = "4.1.0"
}

The version argument supports the same syntax as the [.code]required_version[.code] and [.code]required_providers[.code].

If your module is sourced from the source control repository, you can still specify a version but it will be included with the source URL as a ref query string field:

module "web_app" {
  source = "git::https://example.com/vpc.git?ref=v1.2.0"
}

The [.code]ref[.code] query string at the end of the URL can be set to any valid reference – like a branch, hash, or tag. Unlike the version argument, you cannot specify a range or versions. Instead, the [.code]ref[.code] will be set to an exact version.

If your module isn't sourced from a registry or source control, then versioning is not available to you. Of course, you should really keep your modules in source control!

Unlike providers, Terraform doesn't include modules in the lock file (.teraform.lock.hcl) generated running [.code]terraform init[.code]. To ensure a consistent experience for everyone using the configuration, it's best to pin the module to an exact version rather than a range.

Best Practices for Terraform Versioning

When planning your versioning strategy for Terraform, here are some practices we'd recommend:

  1. Set [.code]required_version[.code] for Terraform Core - Set a minimum to ensure feature compatibility, and a maximum to ensure future functionality.
  2. Set version constraints for all providers - For maximum stability pin an exact version, but at the least stay on the current major version.
  3. Set exact versions for modules - Module versions are not captured in the lock file, pin the exact version instead for consistency.
  4. Upgrade one thing at a time - Do not try to upgrade multiple components simultaneously, especially for major version upgrades.
  5. Read the release notes - Always read the release notes for new major and minor versions and be on the lookout for breaking changes.

The key takeaway is that you can maximize consistency and stability by using versioning for Terraform Core, the providers, and modules.

To Sum Things Up

This article walked through managing versions for Terraform, its modules, its providers, and its code. We reviewed the latest versions of multiple providers, how to use version constraints, and how to go about upgrades.

To ensure a consistent and predictable experience with Terraform, you should employ versioning in each of your configurations and modules. When it comes time to upgrade, you can do so deliberately, one component at a time.

The question of Terraform versioning is now a starker one considering Terraform’s move away from open source licensing with v1.6.0. There are alternatives though, particularly the newly created OpenTofu, a drop-in replacement that forked the last open source version of Terraform. Visit here to learn about env0’s OpenTofu integration.

To learn more about Terraform, check out this Terraform tutorial.

When working with any type of code, stability and consistency are important, and Infrastructure as Code is no exception. The IaC you write today should work tomorrow in the same way it does now. But what if something outside your code changes? How do you minimize the risk of external updates and improve the reliability of your IaC? One possible mitigation is using Terraform version constraints.

Why Do You Need Terraform Versioning?

Each new release of Terraform brings with it new features and functionality, but also potential breaking changes. It can be tempting to jump on the latest version of Terraform to use a new feature or upgrade to the latest provider versions to leverage new resources and data sources.

However, new versions may change existing functionality or replace certain arguments, resulting in a configuration that won't generate a successful terraform plan. Controlling what versions are used for the Terraform Core executable, provider plugins, and modules is key to having a stable and consistent experience.

Understanding Terraform Versions

To best understand where and how to control versioning, we first need to talk about semantic versioning and how it applies to Terraform. Semantic versioning is used in software releases and is composed of three numbered identifiers separated by dots: Major.Minor.Patch - e.g. v1.5.12.

The generally accepted practice for updating an identifier on a release is:

  • Patch release - Includes bug fixes only. No new features or breaking changes.
  • Minor release - Includes new features, enhancements, and deprecation announcement. No breaking changes.
  • Major release - Includes significant changes, deprecation of features, and breaking changes.

As you can imagine, updating to a new patch version, say going from version 1.5.1 to 1.5.2, is unlikely to impact your configurations. Upgrading from version 1.14.0 to 2.0.0 could introduce significant changes that will prevent your configuration from running properly.

Note: Versions of Terraform before the 1.0 major release often contained breaking changes between minor versions. The switch from version 0.11 to 0.12 was particularly painful as it also switched to HashiCorp Configuration Language (HCL) v2.0 in the process. HashiCorp has pledged that configurations written for Terraform version 1.0 or newer will be compatible with all future releases of Terraform, while on major version 1.0.

Terraform Core releases always come with notes that include what has changed since the previous version. It's always a good idea to read the release notes for any major or minor version releases.

What is My Terraform Version?

The easiest way to determine your current Terraform version is by using the terminal:

> terraform version
Terraform v1.5.5
on windows_amd64

You can also use terraform -v to get the current version of your Terraform executable. The command will also check in with the Terraform website and let you know if you're not running the latest version.

What Are The Latest Versions Of Terraform And Major Providers?

As of writing, the latest version of the Terraform executable and the major Terraform providers are as follows:

PulumiTerraform
Terraform Core terraform1.6.2
Amazon Web Servicesaws5.22.0
Microsoft Azureazurerm3.77.0
Google Cloudgoogle5.3.0
Kuberneteskubernetes2.23.0
Alibaba Cloudalicloud1.211.2
Oracle Cloud Infrastructureoci5.17.0

Minor version updates of the most popular providers, like the Azure or AWS providers, tend to be released on a weekly cadence. These releases include bug fixes, new resources, and new resource attributes for existing resources. You can find a listing of providers and their latest version by going to the Terraform public registry.

Terraform Version Constraints

You can control the version of Terraform, a provider, or a module by using version constraints. We'll cover the exact arguments for Terraform modules and providers later in the post. First, let's take a look at how we can constrain the version of Terraform that is compatible with a given configuration.

The general constraint syntax consists of an operator and a semantic version:

= "1.1.1" # Equals the exact version 1.1.1
= "> 2.0" # Greater than version 2.0
= "<= 2.9" # Less than or equal to version 2.9

You can combine constraints together, separating them with commas:

= ">= 2.0, < 3.0" # Greater than or equal to 2.0 and less than 3.0

There is also a special expression that only allows the rightmost identifier to increment.

= "~> 3.0" # Any version that is part of major version 3, e.g. 3.1.1, 3.5.57

The [.code]~>[.code] expression is especially useful if you want to specify a particular major version including all minor and patch releases.

You can define the version of Terraform that is compatible with a configuration using the [.code]required_version[.code] argument inside the Terraform block:

terraform {
  required_version = ">= 1.2, < 2.0"
}

The above constraint would allow Terraform version 1.2 and any major version 1.X release to work with the configuration while blocking major version 2.X or later.

If you attempt to use a version of Terraform Core that doesn't meet the constraint, you will receive an error message:

>terraform validate 
╷
│ Error: Unsupported Terraform Core version
│
│   on terraform.tf line 2, in terraform:
│    2:   required_version = ">= 1.2, < 2.0"
This configuration does not support Terraform version 1.1.2. To proceed, 
either choose another supported Terraform version or update this version
constraint. Version constraints are normally set for good reason, so 
updating the constraint may lead to other errors or unexpected behavior.

Note: One potential reason for configuring the [.code]required_version[.code], is to stay on Terraform version 1.5.7 or earlier. The release of Terraform v1.6 includes the new Business Source License (BSL).  

As a result, using Terraform v1.6 will preclude you from using some additional open source tools that will not support Terraform releases under the BSL license (for example, Terragrunt and Terratest creators Gruntwork will only support up to Terraform v1.5.x).

If you have concerns over how the BSL may impact your organization, you can stay on the previous Mozilla Public License v2 by including [.code]<=1.5.7[.code] in your required version argument.

How to Upgrade Terraform Versions

Upgrading your version of Terraform Core is as simple as downloading the desired version from the available releases and replacing your current local executable. You can also update using one of many supported package managers, depending on your operating system.

If you would like to maintain multiple local versions of the Terraform Core executable, you can leverage the tfenv or tfswitch tools to install and select the desired version of Terraform to use with a given configuration.

If you are leveraging a workflow automation platform to perform your Terraform runs, then the version of Terraform is usually set when the automation is created. You should be able to change the version of Terraform used by going into the properties of the automation. For instance, on env0 you can update the Terraform version for an environment by changing it in the VCS Details.

Which Terraform Version to Use?

When you are writing a new configuration, you should choose the latest stable version of Terraform Core, the provider plugins, and modules. However, your choice of version may be limited by the current organization policies or supported options on your automation platform of choice.

For existing configurations, it's usually best practice to remain on the minor version used when the configuration was originally deployed. Patch version upgrades will assist with potential bugs and security flaws, but it's probably not worth the risk of updating to a new minor or major version unless there is some compelling feature driving your decision to upgrade.

Licensing Concerns

As mentioned earlier, HashiCorp has changed the licensing for Terraform from Mozilla Public License v2 (MPLv2) to Business Source License (BSL). Versions 1.6.0 and later will use this new license. So, if you have concerns about how it may impact your organization, you should continue using version 1.5.X or older.

Even if your organization is not directly impacted by the licensing change, as most organizations are not building a product competitive to HashiCorp, you may still be impacted depending on the additional tools you use to extend Terraform. Several open source tool developers, such as Gruntworks, have chosen to stop supporting Terraform after version 1.5.5.

You may also want to check out the OpenTofu project, an open-source alternative to Terraform, which is meant to act as a drop-in replacement. The source for OpenTofu was forked from version 1.5.6 of Terraform Core, and the project is hosted with the Linux Foundation, ensuring that it will remain open source and community driven. 

OpenTofu is following the same version numbering as Terraform to help with migration and feature tracking.

env0 is a proud member of the OpenTofu project and supports running OpenTofu on the platform as a first-class integration. You can read more about it from env0's CTO & Co-founder, and one of the project’s steering committee members, Omry Hay.

Terraform Provider Versions

In addition to the Terraform Core executable, provider plugins are also versioned. Providers follow the same general semantic versioning conventions as Terraform Core. Bug fixes will be part of a patch release, new resource types or attributes will be part of a minor release, and deprecated resources or arguments will be part of a major release. You can expect at least one breaking change in a major version release of a provider or sometimes several, so it's especially important to read the release notes.

For instance, here is a section of the release notes from the latest major version of the AWS provider:

There are over 100 breaking changes in all. I hope you didn't accidentally upgrade to version 5.0.0 without checking first!

By default, Terraform will download the latest version of a provider plugin during initialization, unless you specify otherwise with a version constraint. Provider requirements and versions are defined in the [.code]required_providers[.code] block inside of the [.code]terraform[.code] block:

terraform {
  required_version = ">= 1.2, < 2.0"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
  }
}

Each provider entry will include a source and optionally a version argument. The source defines where the provider plugin can be found, in our case the azurerm provider is being sourced from the public registry. The [.code]version[.code] argument uses the same version constraint syntax as the [.code]required_version[.code] argument. In the example above, we are requiring the latest version of the [.code]azurerm[.code] provider that is on major version 3.X.

Identifying Providers in a Terraform Configuration

You can view the required providers and version constraints for a configuration using the [.code]terraform providers[.code] command:

> terraform providers

Providers required by configuration:
.
├── provider[registry.terraform.io/hashicorp/azurerm] ~> 3.0
└── provider[terraform.io/builtin/terraform]

The required list indicates providers used by both the root module and any child modules.

After you initialize the configuration – downloading the provider plugins in the process – you can view the currently installed providers and their version with the [.code]terraform version[.code] command:

> terraform version 
Terraform v1.5.4
on windows_amd64
+ provider registry.terraform.io/hashicorp/azurerm v3.77.0

How to Upgrade to a New Provider Version

When you initialize a Terraform configuration, in addition to downloading the provider plugins, Terraform also creates the .terraform.lock.hcl lock file. Contained in the file is a record of the current version constraint, the version of the plugin downloaded, and hashes of each required provider.

provider "registry.terraform.io/hashicorp/azurerm" {
  version     = "3.77.0"
  constraints = "~> 3.0"
  hashes = [
    "h1:7XzdPKIJoKazb3eMhQbjcVWRRu5RSsHeDNYUQW6zSAc=",
    "zh:071c82025cda506af90302b3e89f61e086ad9e3b97b8c55382d5aed6f207cf10",
    ...
  ]
}

The lock file is meant to be checked into source control, providing a consistent deployment experience across the team and automation platform. When a lock file is present, Terraform will honor the constraints and versions inside that file, downloading the exact versions of the providers listed.

Terraform will not use a newer version of the provider even if it meets the version constraint, unless you use the [.code]-upgrade[.code] flag with the [.code]terraform init[.code]command.

Likewise, if you change the version constraint within the configuration, Terraform will not update the provider version installed or the lock file contents unless you include the [.code]-upgrade[.code] flag.

In this [.code]required_providers[.code] block below, the version constraint has been changed to [.code]~>2.0[.code].

terraform {
  required_version = ">= 1.2, < 2.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>2.0" # CHANGED TO 2.0
    }
  }
}

Running a [.code]terraform init[.code] after the change will result in an error.

> terraform init
Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- terraform.io/builtin/terraform is built in to Terraform
╷
│ Error: Failed to query available provider packages
│
│ Could not retrieve the list of available versions for provider 
hashicorp/azurerm: locked provider registry.terraform.io/hashicorp/azurerm 
3.77.0 does not match configured version constraint ~> 2.0; must use 
terraform init -upgrade to allow selection of new versions

If you want to upgrade to a newer version of the provider or change the constraint, you'll need to include the [.code]-upgrade[.code] flag:

> terraform init -upgrade

Initializing provider plugins...
- terraform.io/builtin/terraform is built in to Terraform
- Finding hashicorp/azurerm versions matching "~> 2.0"...
- Installing hashicorp/azurerm v2.99.0...
- Installed hashicorp/azurerm v2.99.0 (signed by HashiCorp)

Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

The initialization process will download the latest version of the provider that meets the constraint and update the lock file.

Terraform Module Versioning

Modules in Terraform may also support versioning, depending on the source of the module. Modules that are sourced from a private or public registry support the [.code]version[.code] argument in the [.code]module[.code] block:

module "shared_vnet" {
  source   = "Azure/vnet/azurerm"
  version  = "4.1.0"
}

The version argument supports the same syntax as the [.code]required_version[.code] and [.code]required_providers[.code].

If your module is sourced from the source control repository, you can still specify a version but it will be included with the source URL as a ref query string field:

module "web_app" {
  source = "git::https://example.com/vpc.git?ref=v1.2.0"
}

The [.code]ref[.code] query string at the end of the URL can be set to any valid reference – like a branch, hash, or tag. Unlike the version argument, you cannot specify a range or versions. Instead, the [.code]ref[.code] will be set to an exact version.

If your module isn't sourced from a registry or source control, then versioning is not available to you. Of course, you should really keep your modules in source control!

Unlike providers, Terraform doesn't include modules in the lock file (.teraform.lock.hcl) generated running [.code]terraform init[.code]. To ensure a consistent experience for everyone using the configuration, it's best to pin the module to an exact version rather than a range.

Best Practices for Terraform Versioning

When planning your versioning strategy for Terraform, here are some practices we'd recommend:

  1. Set [.code]required_version[.code] for Terraform Core - Set a minimum to ensure feature compatibility, and a maximum to ensure future functionality.
  2. Set version constraints for all providers - For maximum stability pin an exact version, but at the least stay on the current major version.
  3. Set exact versions for modules - Module versions are not captured in the lock file, pin the exact version instead for consistency.
  4. Upgrade one thing at a time - Do not try to upgrade multiple components simultaneously, especially for major version upgrades.
  5. Read the release notes - Always read the release notes for new major and minor versions and be on the lookout for breaking changes.

The key takeaway is that you can maximize consistency and stability by using versioning for Terraform Core, the providers, and modules.

To Sum Things Up

This article walked through managing versions for Terraform, its modules, its providers, and its code. We reviewed the latest versions of multiple providers, how to use version constraints, and how to go about upgrades.

To ensure a consistent and predictable experience with Terraform, you should employ versioning in each of your configurations and modules. When it comes time to upgrade, you can do so deliberately, one component at a time.

The question of Terraform versioning is now a starker one considering Terraform’s move away from open source licensing with v1.6.0. There are alternatives though, particularly the newly created OpenTofu, a drop-in replacement that forked the last open source version of Terraform. Visit here to learn about env0’s OpenTofu integration.

To learn more about Terraform, check out this Terraform tutorial.

Logo Podcast
With special guest
Andrew Brown

Schedule a technical demo. See env0 in action.

CTA Illustration