Terraform State
What is Terraform State?
Section titled “What is Terraform State?”Terraform stores information about your infrastructure within a state file.
Terraform uses state to determine which changes to make to your infrastructure, based on the HCL configuration it finds in your code.
Changes in state will lead to the resources being modified, added or removed.
Before any changes are applied to your infrastructure, Terraform performs a refresh to sync the current state, in order to check for any resources’ that may have encountered configuration drifts.
State Commands
Section titled “State Commands”You can view and manipulate Terraform state using the terraform state command.
| Command | Description |
|---|---|
| list | List resources in the state |
| mv | Move an item in the state |
| pull | Pull current state and output to stdout |
| push | Update remote state from a local state file |
| replace-provider | Replace provider in the state |
| rm | Remove instances from the state |
| show | Show a resource in the state |
Examples
Section titled “Examples”Listing all resources in the state
Section titled “Listing all resources in the state”Example terraform state list output
data.aws_ami.latest_amazon_linuxdata.aws_vpc_endpoint_service.s3aws_instance.web_serveraws_internet_gateway.igwaws_route.public_routeaws_route_table.privateaws_route_table.publicaws_route_table_association.private["eu-west-2a"]aws_route_table_association.private["eu-west-2b"]aws_route_table_association.private["eu-west-2c"]aws_route_table_association.public["eu-west-2a"]aws_route_table_association.public["eu-west-2b"]aws_route_table_association.public["eu-west-2c"]aws_security_group.web_serveraws_subnet.private["eu-west-2a"]aws_subnet.private["eu-west-2b"]aws_subnet.private["eu-west-2c"]aws_subnet.public["eu-west-2a"]aws_subnet.public["eu-west-2b"]aws_subnet.public["eu-west-2c"]aws_vpc.vpcaws_vpc_endpoint.s3aws_vpc_endpoint_route_table_association.s3_az_associationGetting details of a specific resource
Section titled “Getting details of a specific resource”Example terraform state show aws_vpc.vpc output
resource "aws_vpc" "vpc" { arn = "arn:aws:ec2:eu-west-2:<id>:vpc/vpc-05f087bb5414aab4c" assign_generated_ipv6_cidr_block = false cidr_block = "192.168.0.0/16" default_network_acl_id = "acl-017e916dbee0b5d63" default_route_table_id = "rtb-06b2809db725c55ef" default_security_group_id = "sg-0b5a4febc36710404" dhcp_options_id = "dopt-6c222d04" enable_dns_hostnames = true enable_dns_support = true enable_network_address_usage_metrics = false id = "vpc-05f087bb5414aab4c" instance_tenancy = "default" ipv6_netmask_length = 0 main_route_table_id = "rtb-06b2809db725c55ef" owner_id = "<id>" tags = { "App" = "webapp" "Name" = "webapp-vpc" "Prefix" = "webapp" "Region" = "eu-west-2" } tags_all = { "App" = "webapp" "Name" = "webapp-vpc" "Prefix" = "webapp" "Region" = "eu-west-2" }}State File Security
Section titled “State File Security”Terraform state files contain a full snapshot of your infrastructure, including any values marked as sensitive in your configuration. These values are stored in plain text inside the state file. This means database passwords, API keys, and other secrets are readable by anyone with access to the file.
Sensitive Data in Plain Text
Section titled “Sensitive Data in Plain Text”When Terraform creates or reads a resource that includes sensitive attributes, those values end up in the state file as-is. For example, an aws_db_instance resource stores the master password in state, and an aws_secretsmanager_secret_version stores the secret value. The sensitive flag on variables and outputs only masks values in CLI output — it does not encrypt them in state.
Encryption at Rest
Section titled “Encryption at Rest”Remote backends like S3, Azure Blob Storage, and Google Cloud Storage support encryption at rest. When using S3 as a backend, enable server-side encryption on the bucket so that the state file is encrypted on disk:
terraform { backend "s3" { bucket = "my-terraform-state" key = "prod/terraform.tfstate" region = "eu-west-2" encrypt = true dynamodb_table = "terraform-locks" }}Setting encrypt = true tells Terraform to request server-side encryption when writing the state file to S3. This protects the data at rest but does not prevent authorised users from reading the decrypted contents.
Access Controls
Section titled “Access Controls”Treat your state file like a secret. Restrict who and what can read or write it:
- Use IAM policies to limit access to the S3 bucket (or equivalent storage) to only the roles and users that run Terraform.
- Apply a bucket policy that denies unencrypted uploads and enforces HTTPS.
- If you use DynamoDB for state locking, restrict access to the lock table as well.
- In team environments, prefer remote backends with access controls over passing state files around manually.
Never Commit State to Version Control
Section titled “Never Commit State to Version Control”State files should never be committed to Git or any other version control system. They contain sensitive data and change with every apply, making them a poor fit for source control.
Add the following to your .gitignore:
*.tfstate*.tfstate.backup.terraform/If you are using a remote backend, Terraform does not write a local state file by default, which removes the risk of accidental commits. For local state workflows, always verify that your .gitignore excludes state files before pushing changes.
Importing Existing Resources with import Blocks
Section titled “Importing Existing Resources with import Blocks”When you have infrastructure that was created outside of Terraform — manually in the console, by another tool, or before your team adopted Terraform — you need a way to bring those resources under Terraform management. The import block (Terraform 1.5+) lets you do this declaratively in your configuration files, rather than running one-off CLI commands.
import Blocks vs terraform import CLI
Section titled “import Blocks vs terraform import CLI”Before Terraform 1.5, the only way to import a resource was the terraform import CLI command:
terraform import aws_s3_bucket.assets my-existing-bucketThis works, but it has drawbacks. The command only updates the state file — it does not generate any configuration. You still need to write the matching resource block by hand, and if the configuration does not match the real resource, the next plan will show unexpected changes. The command also cannot be reviewed in a pull request or shared with your team, since it runs interactively.
The import block solves these problems by making imports part of your configuration:
- Import operations are visible in
terraform planoutput before anything changes. - They can be reviewed in version control alongside the resource configuration.
- They work in CI/CD pipelines without interactive input.
Basic Syntax
Section titled “Basic Syntax”An import block takes two arguments: to (the Terraform resource address) and id (the cloud provider’s identifier for the existing resource):
import { to = aws_s3_bucket.assets id = "my-existing-bucket"}
resource "aws_s3_bucket" "assets" { bucket = "my-existing-bucket"}When you run terraform plan, Terraform reads the real resource using the provider, compares it against your resource block, and reports the import. Running terraform apply writes the resource into state. After a successful apply, you can remove the import block — it only needs to be present for one apply cycle, similar to moved blocks.
Generating Configuration with -generate-config-out
Section titled “Generating Configuration with -generate-config-out”Writing the matching resource block by hand can be tedious, especially for resources with many attributes. The -generate-config-out flag on terraform plan generates a configuration file from the imported resource’s actual state:
terraform plan -generate-config-out=generated.tfThis reads the real resource and writes a resource block to generated.tf with all of its attributes filled in. You can then review the generated file, remove any read-only or computed attributes, and move the configuration into your main files.
A typical workflow looks like this:
- Add the
importblock to your configuration with the resource address and cloud ID. - Run
terraform plan -generate-config-out=generated.tfto create the matching resource configuration. - Review
generated.tf, clean up unnecessary attributes, and move the resource block into the appropriate file. - Run
terraform planagain to confirm the import shows no unexpected changes. - Run
terraform applyto write the resource into state. - Remove the
importblock from your configuration.
Things to Keep in Mind
Section titled “Things to Keep in Mind”- The
idformat depends on the provider. For AWS resources, it is usually the resource’s ARN or name. Check the provider documentation for the correct format. importblocks supportfor_each(Terraform 1.7+), so you can import multiple resources of the same type in a single block.- If the generated configuration does not match the real resource exactly,
terraform planwill show a diff. Adjust your configuration until the plan is clean before applying. - Like
movedblocks,importblocks are one-time operations. Remove them after the import is complete to keep your configuration tidy.