Going to AWS re:Invent 2024?

➡️ Book a meeting with Spacelift

Terraform

Managing Terraform State – Best Practices & Examples

How to manage Terraform state

In this article, we look at how to manage Terraform State. First, we cover what Terraform state is and why it is required before looking at some best practices for storing, organizing, and isolating your state files. We then move on to look at referencing the remote state utilizing a data source, and, finally, how to use the terraform state command to manipulate the contents of the state file.

We will cover:

  1. What is Terraform state?
  2. Terraform state video tutorial
  3. Storing state files
  4. Isolating and organizing state files
  5. Terraform remote state
  6. Manipulating the state file
  7. Terraform state best practices
  8. Managing Terraform state with Spacelift

What is Terraform state?

Terraform logs information about the resources it has created in a state file. This enables Terraform to know which resources are under its control and when to update and destroy them. The terraform state file, by default, is named terraform.tfstate and is held in the same directory where Terraform is run. It is created after running terraform apply.

The actual content of this file is a JSON formatted mapping of the resources defined in the configuration and those that exist in your infrastructure. When Terraform is run, it can then use this mapping to compare infrastructure to the code and make any adjustments as necessary.

Read more about the elements of Terraform architecture.

Note: New versions of Terraform are 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 expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.

Terraform state video tutorial

terraform state video tutorial

Storing state files

State files, by default, are stored in the local directory where Terraform is run. If you are using Terraform to test or for a personal project, this is fine (as long as your state file is secure and backed up!). However, when working on Terraform projects in a team, this becomes a problem because multiple people will need to access the state file.

Also, when using automation and CI/CD pipelines to run Terraform, the state file needs to be accessible, and permission must be given to the service principal running the pipeline to access the storage account container that holds the state file. This makes shared storage the perfect candidate to hold the state file. Permissions can be granted as needed. Azure Storage accounts or Amazon S3 buckets are an ideal choice. You can also use a tool such as Spacelift to manage your state for you.

terraform {
  backend "azurerm" {
    resource_group_name  = "terraform-rg"
    storage_account_name = "terraformsa"
    container_name       = "terraformstate"
    key                  = "terraform.tfstate"
  }
}

Isolating and organizing state files

State files should be isolated to reduce the “blast radius”. Usually, projects are structured in a single folder and use a single state file for all resources. This immediately introduces risk, as a mistake in the configuration could change the state file and cause unwanted consequences to all your resources.

Isolating through multiple state files

A better way is to use multiple state files for parts of your infrastructure. Logically separating resources from each other and giving them their own state file in the backend means that changes to one resource will not affect the other. Different state files for different environments are also a good idea.

Check our guide on How to Manage Multiple Terraform Environments Efficiently.

terraformstate
--development
  --webapp.tfstate
  --sqldb.tfstate
  --vnet.tfstate
--UAT
  --webapp.tfstate
  --sqldb.tfstate
  --vnet.tfstate
--production
  --webapp.tfstate
  --sqldb.tfstate
  --vnet.tfstate

The backend configuration for the Azure Web App in the development environment:

terraform {
  backend "azurerm" {
    resource_group_name  = "terraform-rg"
    storage_account_name = "terraformsa"
    container_name       = "terraformstate"
    key                  = "development\webapp.tfstate"
  }
}

Isolation through Terraform workspaces

$ terraform workspace new development
Created and switched to workspace "development"! You're now on a new, empty workspace. Workspaces isolate their state, so if you run "terraform plan" Terraform will not see any existing state for this configuration.

Now, whenever you create a new workspace and you want to run your terraform code, this creates a terraform.tfstate.d directory which will hold the state for your workspace. Let’s create two other workspaces, one for stage and the other one for production:

$ terraform workspace new stage
Created and switched to workspace "stage"! You're now on a new, empty workspace. Workspaces isolate their state, so if you run "terraform plan" Terraform will not see any existing state for this configuration.


$ terraform workspace new prod
Created and switched to workspace "prod"! You're now on a new, empty workspace. Workspaces isolate their state, so if you run "terraform plan" Terraform will not see any existing state for this configuration.

Now, let’s create a local variable called cidr_block that will take different values depending on the selected workspace:

locals {
 cidr_block = terraform.workspace == "dev" ? "10.0.0.0/16" : terraform.workspace == "stage" ? "11.0.0.0/16" : "12.0.0.0/16"
}

output "cidr_block" {
 value = local.cidr_block
}

Because the last workspace we were on is prod, the value should be 12.0.0.0/16, let’s run a terraform apply and see it in action:

terraform apply             

Changes to Outputs:
  + cidr_block = "12.0.0.0/16"

You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.

Do you want to perform these actions in workspace "prod"?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

cidr_block = "12.0.0.0/16"

Now, let’s switch to dev and run a terraform apply:

terraform workspace select dev
Switched to workspace "dev".

terraform apply               

Changes to Outputs:
  + cidr_block = "10.0.0.0/16"

You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.

Do you want to perform these actions in workspace "dev"?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

cidr_block = "10.0.0.0/16

Now, if we go inside the terraform.tfstate.d we will see the corresponding directories for the workspaces and two generated tfstate files for the dev and prod workspaces:

tree .         
.
├── dev
│   └── terraform.tfstate
├── prod
│   └── terraform.tfstate
└── stage

4 directories, 2 file

Terraform remote state

terraform_remote_state is a data source that can be used to fetch details from the remote state file directly. This is useful when you need to reference the outputs of configurations that are stored in different state files. When an output block is defined in your configuration, the contents are included in the state file. These details can then be referenced elsewhere in your project.

Read more about the Terraform remote state.

terraform_remote_state data source example

output "sqldb_id" {
  value       = azurerm_sql_database.example.id
  description = "Database ID"
}

We can now reference the resulting SQL database ID in the Web App configuration code by configuring a data block using terraform_remote_state:

data "terraform_remote_state" "dev_sqldb" {   
  backend = "azurerm"
   
  config = {     
    storage_account_name = "terraformsa"     
    container_name       = "terraformstate"     
    key                  = "development/sqldb.tfstate"   
  }
}

And then reference the name of the output where necessary in the configuration:

data.terraform_remote_state.dev_sqldb.outputs.sqldb_id

Manipulating the state file

It is sometimes necessary to directly interact with the state file, either to check its contents, remove items when they have been imported incorrectly or no longer exist in the real infrastructure, or import items that already exist to bring them under Terraform management.

To import existing items into the state file that have been created by other methods in the infrastructure to bring them under Terraform control, the terraform import command can be used. Each resource on the Terraform docs pages has an import section detailing how to use the command for a particular resource. For example, on the azurerm_sql_database docs page, it states that SQL Databases can be imported using the resource id and gives an example.

terraform import azurerm_sql_database.database1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myresourcegroup/providers/Microsoft.Sql/servers/myserver/databases/database1

The corresponding resource block azurerm_sql_database.database1 needs to be written in the configuration file prior to using this command, with the settings matching the real infrastructure. Importing resources can be very time-consuming, so avoiding any manual creation of resources from the get-go is normally a wise idea!

To learn more, see our step-by-step tutorial: Importing Existing Infrastructure into Terraform.

Terraform state best practices

When working with Terraform there are a couple of things you should consider about your infrastructure state, as this is critical for the successful and secure deployment of your IaC.

Here are a couple of best practices:

  1. Always use remote state – it goes without saying that the local state is planning to fail, especially when working inside a team. With remote state and locking mechanisms in place, you ensure that collaboration can be implemented and there won’t be any race conditions happening. See how to migrate your Terraform state.
  2. Implement state encryption – encryption should be enabled for state files at rest and in transit, and if your remote backend supports it, enable server-side encryption. 
  3. Review Terraform plans before doing applies – this will ensure you understand the impact your changes have on your infrastructure and your state.
  4. Version your configuration and use modules wherever possible – Your configuration should always be versioned because it will make rollbacks easier. Using modules and versioning them, will also ensure that you can easily roll back to the previous configuration.
  5. Use Terraform Automation and Collaboration Software such as Spacelift for state management – Custom remote state backends are good, but Spacelift takes state management to the next level without you needing to lift a finger.

Managing Terraform state with Spacelift

Spacelift can optionally manage the Terraform state for you, offering a backend synchronized with the rest of the platform to maximize convenience and security. You also get the ability to import your state during stack creation, which is very useful for engineers who are migrating their old configurations and states to Spacelift.

Behind the scenes, Spacelift uses Amazon S3 and is storing all the data in Ireland. It’s super simple to have Spacelift manage the state for you, as this behavior is achieved by default without you needing to do anything. You can read more about how it actually works here.

At the same time, it’s protected against accidental or malicious access as Spacelift is able to map state access and changes to legitimate Spacelift runs, automatically blocking all unauthorized traffic.

If you are interested in learning more about Spacelift, create a free account today or book a demo with one of our engineers.

Key points

Understanding aspects of Terraform state management and best practices is key to becoming proficient with Terraform. State files should be stored remotely as a general rule and isolated and organized so that separate state files exist for logical groups of resources and environments to reduce the “blast radius” if mistakes occur.

The terraform_remote_state data source can be used to reference outputs from state files. Finally the terraform state and terraform import commands can be used to manipulate the contents of the state file.

Cheers!

Manage Terraform Better and Faster

If you are struggling with Terraform automation and management, check out Spacelift. It helps you manage Terraform state, build more complex workflows, and adds several must-have capabilities for end-to-end infrastructure management.

Start free trial
Terraform State Cheatsheet

Grab our ultimate cheat sheet PDF and keep your IaC safe while managing State.

Share your data and download the cheatsheet