DEV Community

Spacelift team for Spacelift

Posted on • Originally published at spacelift.io

Terraform Init – Command Overview

In this post, we will explain what the terraform init command is used for, what it does, and when to run it. We will explore the options available and give an example of when to use it in automation using Azure DevOps.

What is Terraform init?

After writing your Terraform code or cloning your existing Terraform code from source control, the terraform init command is the first step you should take to prepare your working directory.

Terraform init is a CLI command that initializes the Terraform environment by installing the necessary provider plugins and modules and setting up the configuration according to your code. Terraform init enables you to run further commands like terraform plan and terraform apply.

How does Terraform init work?

In order to prepare the working directory for use with Terraform, the terraform init command performs the following steps:

  • Backend initialization

  • Child module installation

  • Plugin installation

We will look at each of these in more detail in this article.

Quick usage examples: Terraform init command flags

If you are looking for some quick examples on how to use the terraform init command, some of the more common usage flags are listed below. Later in the article, we will deep dive into some of these and provide examples.

Quick usage examples

terraform init - Initialize the working directory, install required provider plugins and modules, and set up the backend.

terraform init -lock=false - Initialize the working directory; don't hold a state lock during backend migration.

terraform init -input=false - Initialize the working directory and disable interactive prompts.

terraform init -migrate-state - Reconfigure a backend and attempt to migrate any existing Terraform state.

terraform init -upgrade - Ensure you're using the latest compatible versions of your providers

terraform init -reconfigure - Use the -reconfigure flag to force Terraform to forget any previous configuration and reinitialize.

terraform init -get=false - Disable downloading modules for this configuration

terraform init -plugin-dir=/path/to/custom/plugins - Point Terraform to custom or manually downloaded provider plugins

terraform init -input - Provide values for required input variables during initialization

How to initialize a Terraform file - example configuration files

The example files we will use in this article will create a specified number of groups in Azure AD.

1. Set main.tf file

These files are contained in a directory called az-ad-group. Within it, I have my Terraform configuration files, named main.tf, variables.tf and terraform.tfvars, as well as a .gitignorefile, which will specify which file extensions within the folder the git source control should ignore.

I have a subfolder (or module) within this which holds a main.tf and variables.tf file.

Lastly, the azure-pipeline.yml file specifies the pipeline settings to run terraform init and terraform plan.

AZ-AD-Group


provider "azurerm" {

  features {}

}

terraform {

  required_providers {

    azurerm = {

      source  = "hashicorp/azurerm"

      version = ">=2.95.0"

    }

    azuread = {

      source  = "hashicorp/azuread"

    }

  }

  backend "azurerm" {

    resource_group_name  = "tf-rg"

    storage_account_name = "jacktfstatesa"

    container_name       = "terraform"

    key                  = "adgroups.tfstate"

  }

}

module "ad_group" {

  source = "./ad_group"

  ad_group_names = var.ad_group_names

}

Enter fullscreen mode Exit fullscreen mode

Note that the main.tf file contains the required_providers and backend blocks. I also call a module called ad_group.

Since this article focuses on the terraform init command, and everything relevant to that command is shown in the above code, I will not publish the rest of the files here, but they can be found in the GitHub repository should you wish to try the example out yourself or delve deeper into the setup.

2. Initialize backend

My main.tf file is configured to use a storage account I have set up in Azure to store the Terraform state file. This is known as the 'backend'.

When terraform init runs, it will first attempt to initialize the backend.


backend "azurerm" {

    resource_group_name  = "tf-rg"

    storage_account_name = "jacktfstatesa"

    container_name       = "terraform"

    key                  = "adgroups.tfstate"

 }

Enter fullscreen mode Exit fullscreen mode

Before that can happen successfully I will need to login to Azure using the Azure CLI. If I run it without first authenticating, Terraform complains that the resource group containing the storage account cannot be found.

Backend

To login to Azure, use the following commands. The --tenant flag does not need to be specified if you have only one Azure AD tenant linked to your login. If you have multiple tenants linked to your login, you should specify this to avoid confusion.


az login --tenant <tenant ID>

az account set --subscription <subscription ID>

Enter fullscreen mode Exit fullscreen mode

Once authenticated successfully, run the terraform init command again:

Backend 2

This time we see the backend was initialized successfully.

In the storage account in Azure, in my terraform container, I can see that the adgroups.tfstate file has been created successfully.

adgroups.tfstate

Any changes to the backend configuration will be detected by Terraform. When terraform init is run, Terraform will throw an error alerting you to the change.

init error

In this scenario, there are two options:

  • terraform init -migrate-state -- To reconfigure the backend and attempt to migrate any existing state.

  • terraform init -reconfigure -- To reconfigure a backend while ignoring any saved configuration.

💡 You might also like:

3. Initialize the Terraform child module

A Terraform module is a container for multiple resources that are used together.

In my example code, my main.tf calls my child module ad_group.


module "ad_group" {

  source = "./ad_group"

  ad_group_names = var.ad_group_names

}

Enter fullscreen mode Exit fullscreen mode

When terraform init is run we can see it being installed:

Initializing modules

If you have any module blocks defined in your configuration files, these will be installed when executing the init command. The location of the source code for the referenced modules is defined in the source argument of the module block. All modules require a source argument.

The source of a module block can point to a local module, or a remote source such as a module held in a central repository, GitHub, Bitbucket, or a module in the public terraform registry. If the code is remote, it will be downloaded from the source specified to a local directory so it can be used by other Terraform commands.

After adding, removing, or modifying module blocks, terraform init must be run again in order for Terraform to make the necessary adjustments that have been defined in the configuration.

Re-running the terraform init command will not amend any modules that have already been installed if the configuration of those modules has not been changed. It will, however, install any modules added since the last time terraform init was executed. To force all modules to be re-installed, the -upgrade flag can be specified.

Child module installation can also be skipped by using the -get=false flag, this should only be used if the child modules have already been installed and no additions, deletions, or changes to the modules have been made. Note that Terraform builds an internal 'module tree' when terraform init is run, and if this is not complete, then other steps performed by terraform init will not complete successfully. It is, for this reason, the -get=false flag should be used with caution.

4. Initialize the Terraform plugin

Most Terraform providers are published separately from Terraform as plugins. There are hundreds of available providers which allow Terraform to be used with different services. Common providers include azurerm for Microsoft Azure, azuread for Azure Active Directory, and aws for Amazon Web Services. A full list of available providers can be browsed using the Terraform registry.

During terraform init the registry is searched for those providers that are specified directly in the configuration files but also searches for providers that are not specified directly (indirect). They will be automatically found, downloaded, and installed if they are present in the registry.

As well as searching the public Terraform registry for providers, it is also possible to use a third-party registry, by specifying the -plugin-dir=PATH flag.

If you do not wish to use these options, a provider_installation block can be configured in your code to override Terraforms default provider installation behavior, allowing the use of a local mirror for some or all of your required providers.

When I run terraform init with my example code, I see the following output:

Init provider plugins

Terraform searches the registry for the required azuread and azurerm providers and installs the appropriate versions as specified in my configuration files.

If I do not specify the version for a particular provider, Terraform will look for the latest version. It is recommended to always *pin *the provider version as upgrades may cause unexpected behavior over time. For example, the azuread v2.18.0 provider will have introduced many changes compared to v2.17.0.

Refer to the main.tf file at the beginning of the article for an example of this at line 9, which pins the version of the azurerm provider.

5. Create dependency lock file - terraform.lock.hcl

Once providers have been successfully installed, Terraform creates a dependency lock file and writes information about the providers to it. This file will be named .terraform.lock.hcl and should ideally be committed to your repository in your version control system to ensure that the same provider versions are used when terraform init is run again in the future.

Using my example configuration code, once terraform init has been run, Terraform will advise that the .terraform.lock.hcl file has been created.

.terraform.lock.hcl

The contents of the .terraform.lock.hcl file will look like this:


# This file is maintained automatically by "terraform init".

# Manual edits may be lost in future updates.

provider "registry.terraform.io/hashicorp/azuread" {

  version = "2.18.0"

  hashes = [

    "h1:cpppwljjeyqTnwNlQGHK+o1Jb5Tf4UAnJiInNkN6P38=",

    "zh:04b2b5e09dedd45316cd47d21d2ed7e3cd7a4e5f3c8b6e8fba0a10e7eb2a1ca9",

    "zh:232440381b60d918e0da0ea8e8a2e8a78a4fe1ae785b3f829f2f965464ab79a2",

    "zh:688111a9cb8d9ffec2ccabacb27456d275bf1d404dd5f85e681715abbdd64654",

    "zh:7f37b7be7859170f077c58e74be42b5571e364c52aac0a2df3a6a14fbe48d3c5",

    "zh:a385743bfae40f6a01bf6662a3c7a71035113c33965e0dbf421997a015734d08",

    "zh:a97b7430647d7b449441e5515f11a4d123f6d6a383a8fbca5c0a4086be407358",

    "zh:be6d40d1431e8e71a96cce2099a259ef5a8dfb0e849817875d9ee4bb8cf59d40",

    "zh:db3b541d90881d620111fdae0efe90d1e0972fc80a2b4346d4af8d96e1fc1195",

    "zh:e6d9e0481f2bdc16ee69aa00001d9713282daccfbc621e0143c53d9f6dbdb537",

    "zh:ee5b724ca78301057059eff18062fa88d8b24ac7b39f52eb17b8609634427ce0",

    "zh:fdb169f89551f97f6b0bf90d61d5fda166a25cce6867ec16f63c3bfb4d90a0a2",

  ]

}

provider "registry.terraform.io/hashicorp/azurerm" {

  version     = "2.98.0"

  constraints = ">= 2.95.0"

  hashes = [

    "h1:8Sg08lYcJC12Y8EH5oFfgBhIR9OhZFKF633NjOMjilY=",

    "zh:025f656a6d3ecc30f7cc2279bc41969789987b405e3fa8a7c1eb5f74e3ee1140",

    "zh:23c54b330678a16378156193d709bbddce3ba76ee827fd65fb751ce90790af9e",

    "zh:2d28d359ce6881918bd6c03701f6ec4fd90215abfce9b863cfd3172e28c1acb3",

    "zh:31df88584d39cf876fa45ff6de92e67e03814a0985d34c7671bd6989cda22af8",

    "zh:36019109790b9a905770355e5bbb57b291a9689a8b9beac5751dcbdb1282d035",

    "zh:5fb4a277331c459db9e1b150d79b7c7157a176ceca871195e81225e949141b72",

    "zh:7ec304afa1b60dc84257a54cea68e97f85df3feb405d25a9226a4f593ed00744",

    "zh:bac469f104b8ad2c8b5ddc88ddae3b0bc27ae5f9c2ccf03f14a001a5c3ed6ae1",

    "zh:d860b0ec60a978fe3f08d695326e9051a61cd3f60786fc618a61fbdb5d6a4f15",

    "zh:ebcb2911ee27587f63df7eff3836c9a206181a931357c6b9a380124be4241597",

    "zh:f37fae57bf7d05c30fda6e5414ad5a4aad1b34d41a5f2465a864736f92ded1ac",

  ]

}

Enter fullscreen mode Exit fullscreen mode

Notice that the versions are specified, as well as any version constraints. In my main.tf file I specified a constraint for the azurerm provider, but not my azuread provider.

If you want to force Terraform to look for newer versions than those contained in the lock file, you can specify the --upgrade option, an example of which can be found in the 'terraform init options' section of this article.

What is the difference between Terraform init and plan?

Both terraform init and terraform plan are among Terraform's foundational commands.

The init command prepares the Terraform working directory by installing all the necessary provider plugins, downloading modules, and setting up state storage. In contrast, the plan command generates an execution plan showing infrastructure changes without implementing them.

While init prepares the environment for Terraform, plan previews potential adjustments based on the defined configuration against the current state.

Do you need to run Terraform init before every Terraform plan?

terraform init is the first command you should run in the workflow, however, if you know that no changes have been made to the modules, backend, or provider installations, you can go ahead and run terraform plan without running terraform init first. In automation, it is always best practice to put in a terraform init stage first to make sure the modules, providers, and backend are always up-to-date as specified in your configuration files.

Is it safe to run Terraform init multiple times?

It is always safe to run terraform init. It will never modify the configuration or destroy any resources. If it is run multiple times, it will simply update the working directory with the changes in the configuration. This will be required if the configuration changes include the addition of a new provider block or a change to the backend storage location, for example.

Running Terraform init in automation

terraform init will be the first step in configuring your Terraform workflow in an automation pipeline. To set this up using Azure DevOps pipelines, we will use the azure-pipelines.yml file contained in the example code repository.


stages:

- stage: Build

  displayName: Terraform-Plan

  jobs:

  - job: TerraformPlan

    displayName: Terraform-Plan

    pool:

      name: Private-Build-Agents

    steps:

    - checkout: self

    - script: ls $(System.DefaultWorkingDirectory)

    - task: TerraformInstaller@0

      displayName: 'Use Terraform latest'

    - task: TerraformCLI@0

      displayName: Terraform-Init

      inputs:

        command: 'init'

        workingDirectory: '$(System.DefaultWorkingDirectory)'

        backendType: 'azurerm'

        backendServiceArm: 'IAC Service Connection-Azure'

        backendAzureRmResourceGroupName: 'tf-rg'

        backendAzureRmStorageAccountName: 'jacktfstatesa'

        backendAzureRmContainerName: 'terraform'

        backendAzureRmKey: 'adgroups.tfstate'

    - task: TerraformCLI@0

      displayName: Terraform-Plan

      inputs:

        command: 'plan'

        workingDirectory: '$(System.DefaultWorkingDirectory)'

        environmentServiceName: 'RG Service Connection'

Enter fullscreen mode Exit fullscreen mode

This file specifies the pipeline in YAML format. As the pipeline is defined as code rather than set up manually through the Azure DevOps GUI, it can be considered 'pipeline-as-code', whereas terraform configuration files themselves would be considered 'infrastructure-as-code'.

The pipeline file specifies two stages, the terraform init stage and a terraform plan stage.

The terraform init stage is specified starting at line 15. Note the backend details are specified here. They can also be specified in the main.tf configuration file, but if they were committed from the file, the pipeline would still run using the values specified in the azure-pipelines.yml file.

During the pipeline run, Azure DevOps will run the Terraform CLI task with the Azure DevOps plugin which it will set up during the 'initialize job' phase.

Downloading task

Terraform-Init

In some cases where you have lots of providers specified and want to avoid them being installed repeatedly on each pipeline run by terraform init, you may wish to make the providers available locally. This is possible and is covered in this advanced 'Terraform in automation' tutorial.

Terraform init options

There are multiple options that can be used with the terraform init command that can be viewed using the --help flag. Below are some examples of the more useful and commonly used options.

1. terraform init -backend=false

Terraform init backend=false lets you disable backend or Terraform Cloud initialization for this configuration and use what what was previously initialized instead. aliases: -cloud=false.

As you might infer, this option does not create a local backend

2. terraform init -backend-config=path

When you use terraform init -backend-config=path, your configuration will be merged with that in the configuration file's 'backend' block. This option allows you to dynamically configure the backend by passing in a file path or key/value pairs during the initialization process, separating potentially sensitive values from your main Terraform code.

3. terraform init -force-copy

terraform init -force-copy is used to suppress prompts about copying state data when initializing a new state backend. It allows you to initialize a new backend and migrate the state data from the previous backend automatically, without any interactive prompts for confirmation. This enables smoother backend switching in automated workflows. However, it can potentially cause data loss if the state is being copied incorrectly.

4. terraform init -from-module=SOURCE

With terraform -init from-module=SOURCE you can initialize the current working directory with the contents of the specified module source.

5. terraform init -get=false

terraform init -get=false initialize a Terraform working directory by installing providers but skipping automatically downloading any declared modules. This can be useful in certain workflows where you want more control over when modules are downloaded or updated

If the working directory has been previously initialized without any further changes, this can be used to prevent child modules from being re-downloaded.

6. terraform init -input=false

terraform init -input=false command is used to initialize the Terraform working directory without prompting for any input.

This is useful in automation pipelines where the run may simply hang and time out waiting for input. For example, if your code had an error that prompted for a value for a missing variable, the pipeline would prompt for this rather than erroring and finishing the run.

7. terraform init -lock=false

terraform init -lock=false disables the locking of the state file during the initialization step. When Terraform runs, it locks the state file so it cannot be modified by other processes.

If a Terraform run ends unexpectedly (crashes or is canceled mid-run), or multiple pipelines have been running simultaneously using the same state file, then the state file remains locked. On the next run, you may see any error as pictured below, showing that the 'state blob is already locked'.

state blob is already locked

Error message: State blob is already locked

The easiest fix for this issue is to navigate to the storage account and then to the container in the Azure portal that holds the state file. The blob will show as 'Leased' under the leased state column. Select the state file and hit the 'break lease' button.

Break lease

Lease state on the state file blob should then show as 'broken', and you can kick off the next Terraform run.

You can also use Azure CLI to do this, e.g.


az storage blob lease break -b terraform.tfstate -c myAzureStorageAccountContainerName --account-name "myAzureStorageAccountName" --account-key "myAzureStorageAccountAccessKey"

Enter fullscreen mode Exit fullscreen mode

Or, using Terraform you can force the unlock, (get the LockID from the error) e.g.


terraform force-unlock <LockId>

Enter fullscreen mode Exit fullscreen mode

You can also specify the lock=false option which as mentioned might be dangerous if others might be concurrently running against the same state file, so it is better to fix the issue using the steps above.

8. terraform init -lock-timeout=<duration>

With terraform init -lock-timeout=<duration> you can specify the maximum time Terraform should wait to acquire a lock on the state file during initialization.

9. terraform init -no-color

terraform init -no-color initializes the Terraform working directory as usual, but with all color codes removed from the command output.

For example, when using Terraform in an Azure DevOps release pipeline, you may notice that the encoding on the console output has annoying characters displayed. This is caused by Terraform attempting to output colors to the console to signify additions, deleting, changes, etc. Azure DevOps can't handle this. Examples are as follows:

color

no-color

10. terraform init -plugin-dir

You can use terraform init with the -plugin-dir option to specify an alternate directory for Terraform to install and look for provider plugins*.*

You can use pre-installed plugins by either putting them in the same directory as the Terraform binary or by setting the -plugin-dir flag. This might be desirable when you have connectivity issues or restrictions to https://releases.hashicorp.com.

11. terraform init -upgrade

terraform init -upgrade is useful when any pinned provider versions have been changed and you want to force Terraform to search for the latest version.

For example, if I pin my provider version for azuread to version 2.17.0, on running terraform init for the first time, this is the version that will be installed.


azuread = {

   source  = "hashicorp/azuread"

   version = "=2.17.0"

}

Enter fullscreen mode Exit fullscreen mode

If I then proceed to remove the version constraint from the azuread provider:


azuread = {

   source  = "hashicorp/azuread"

}

Enter fullscreen mode Exit fullscreen mode

Then re-run terraform init you will notice that Terraform reports that it will use the previously installed version (still version 2.17.0) rather than searching again for the latest version.

init upgrade

When re-runnig with the upgrade flag, you will notice that Terraform searches for the latest version of the azuread provider (v2.18.0 at the time of writing). Notice that any modules in the configuration have also been upgraded.

init upgrade 2

12. terraform init -migrate-state

terraform init -migrate-state lets you migrate your Terraform state data from one backend to another when changing backend configurations

13. terraform init -reconfigure

terraform init -reconfigure option is used to reconfigure the backend for an existing Terraform working directory while ignoring any previously saved configuration.

14. terraform init -ignore-remote-version

terraform init -ignore-remote-version command is a rarely used option that lets you override checking that the local and remote Terraform versions agree when using the remote backend, allowing an operation to proceed even when there is a version mismatch.

Key points

The terraform init command is the first command you should use to prepare the working directory. terraform init specifically performs the following actions:

  • Backend Initialization

  • Child Module Installation

  • Plugin Installation

It is always safe to run terraform init. It can be used in automation and comes with a myriad of options that can be used to further control its behavior.

Note: New versions of Terraform will be placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that will expand on Terraform's existing concepts and offerings. It is a viable alternative to HashiCorp's Terraform, being forked from Terraform version 1.5.6.

Don't forget to take a look at how Spacelift helps you manage the complexities and compliance challenges of using Terraform. It brings with it a GitOps flow, so your infrastructure repository is synced with your Terraform Stacks, and pull requests show you a preview of what they're planning to change. It also has an extensive selection of policies, which lets you automate compliance checks and build complex multi-stack workflows. You may also check how initialization policies work with Spacelift.

Written by Jack Roper and Flavius Dinu.

Top comments (0)