Terraform State in Azure Storage Container

If multiple team members are working with Terraform against the same Azure resources, they will run into problems if all of them have their tfstate files in their local machines. I'll share how to store the Terraform state in an Azure Storage Container.


Introduction

Storing the Terraform state file in a secure and centralized location is crucial when a team is working with Terraform on the same infrastructure. In this article, I'll show you how to store the Terraform state file in an Azure Storage Container. The key to the Azure Storage Container will be stored in a Key Vault.

Prerequisites

Products

  • Azure CLI
  • Terraform
  • Azure Subscription

Permissions

Terraform Service Principal

  • Contributor on the Subscription
  • Key Vaults Secret User for the Subscription

Admin Account running the Azure CLI

  • Contributor on the Subscription
  • KeyVault Administrator on the Subscription

Method

We will use Azure CLI to create the Azure Storage Container and Key Vault. The reason for using Azure CLI is to keep the Storage Container and Key Vault out of Terraform State.

Setting up the Azure Storage Container

Let's create the Azure Storage Container. Run the following:

# Variables
RESOURCE_GROUP_NAME=tfstate_rg
STORAGE_ACCOUNT_NAME=tfstate$RANDOM
CONTAINER_NAME=tfstate
 
# Create resource group
az group create --name $RESOURCE_GROUP_NAME --location norwayeast
 
# Create storage account
az storage account create --resource-group $RESOURCE_GROUP_NAME --name $STORAGE_ACCOUNT_NAME --sku Standard_LRS --encryption-services blob
 
# Create blob container
az storage container create --name $CONTAINER_NAME --account-name $STORAGE_ACCOUNT_NAME

Setting up the Key Vault

If you wrote a script running the previous commands for the Azure Storage Container, you can add the commands in this chapter to the script. I split the commands into two chapters to make it more readable.

# Get the Account Key from the Storage Account.
ACCOUNT_KEY=$(az storage account keys list --resource-group $RESOURCE_GROUP_NAME --account-name $STORAGE_ACCOUNT_NAME --query '[0].value' -o tsv)
 
# Create a Key Vault (if it doesn't already exist)
az keyvault create \
  --name <your-keyvault-name> \
  --resource-group $RESOURCE_GROUP_NAME \
  --location <your-location>
 
# Store the Storage Account access key in Key Vault
az keyvault secret set \
  --vault-name <your-keyvault-name> \
  --name <your-secret-name> \
  --value "$ACCOUNT_KEY"

If you append randomized numbers to the Key Vault name for global uniqueness, you can use the following command to get the Key Vault name:

az keyvault list --resource-group $RESOURCE_GROUP_NAME --query '[].name' -o tsv

Alternatively, grab it from the JSON output of the previous commands.

Set the Environment Variable needed by Terraform to configure the backend

Use the following commands to set the $ARM_ACCESS_KEY needed for the Terraform backend configuration:

# Variables
vault_name = <your-keyvault-name>
secret_name = <your-secret-name>
 
# Set the ARM_ACCESS_KEY environment variable
export ARM_ACCESS_KEY=$(az keyvault secret show \
  --vault-name $vault_name \
  --name $secret_name \
  --query "value" -o tsv)
 

We need to run the commands above every time we open a new terminal session. There are ways around this, but I trust you to consider the security implications of those methods. Make sure to store your vault name and secret name in a secure location.

Configure Terraform

Configure Terraform to use the Azure Storage Account as the backend:

main.tf

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
  }
  backend "azurerm" {
      resource_group_name  = "tfstate"
      storage_account_name = "<storage_account_name>"
      container_name       = "tfstate"
      key                  = "terraform.tfstate"
  }
 
}
 
provider "azurerm" {
  features {}
}
 
resource "azurerm_resource_group" "state-demo-secure" {
  name     = "state-demo"
  location = "norwayeast"
}

Verify the Configuration

  1. Run the following command to initialize Terraform:
terraform init

If the output looks like this, you have successfully configured the backend:

Terraform2001
  1. Verify that the Terraform state is stored in the Azure Storage Container:
Terraform2002
  1. Deploy your own resources to verify that the Terraform state is stored in the Azure Storage Container.

Conclusion

In this article, I shared what I learned in improving Terraform state handling. Storing the Terraform state in an Azure Storage Container is way more secure and manageable than storing it locally. For visitors using AWS, something similar can be done with S3 and KMS.

Other things I discovered while working on this article:

  • Azure CLI has dependencies on older versions of Python. This can make make a mess, I like to keep my host computer clean. I dockerized it, come back later for a post on that. Working on it.
  • In a production environment, I would have done the permissions in a more granular way.
  • Setting the Environment Variable every time I open a new terminal session is a bit annoying. I'll put working on a script and adding it to my .bashrc on my to-do list. Should be a quick fix.

I hope you found it useful. If you have any questions or feedback, please let me know in the comments below.