Learn · State
The state file is the truth, and it is often wrong
The configuration says what you want and the cloud says what exists. The state file says what the tool believes — and every interesting failure is one of the three disagreeing.
Three things, not two
A plan is not a comparison between your configuration and the cloud. It is a comparison between your configuration and the state file, after the state file has been refreshed against the cloud.
That third thing is where the surprises live. A resource deleted in a console is still in state until a refresh notices; a resource created by hand is not in state at all, and the tool will happily create a second one beside it; a state file restored from the wrong backup describes infrastructure that no longer exists.
serial goes up on every write and is what stops two concurrent applies overwriting each other. lineage identifies the file — two state files with different lineages are not interchangeable even if they describe the same resources.The secrets are in there, in the clear
A plan prints (sensitive value) where a secret would go. The state file does not. Every password, every generated token and every private key the provider returned is in it as plain text, because the tool has to be able to diff it next time.
That is not a bug and there is no setting that fixes it. It is why the state file is treated as a secret in its own right, and why terraform.tfstate in a git repository is a genuine incident rather than a tidiness problem.
Before you run it
A database's password is marked sensitive by the provider. Where does the value end up?
moved and import: changing the file without changing the cloud
Renaming a resource in your configuration destroys the old one and creates a new one, because the address is the identity as far as state is concerned. A moved block says this used to be called that, and the plan comes out empty — the record is renamed and nothing is touched.
import goes the other way: an object exists in the cloud and not in state, and an import block brings it under management. The plan shows the resource with importing rather than create, and if your configuration disagrees with what is actually there, it shows the difference too.
Both are the same idea — the file and the cloud are separate things, and either can be edited without the other.
moved block whose plan is empty is a moved block that worked. It is the one construct in the language whose success looks like nothing happening.The lock, and the two-people-applying disaster
An apply takes a lock on the state before it starts and releases it at the end. Two people applying at once is what it exists to prevent, and what happens without it is not a merge conflict — it is two applies both reading serial 41, both writing serial 42, and one of them describing resources the other has just destroyed.
The lock is advisory and there is a flag to skip it. The flag exists for the case where a previous apply died and left the lock held, which is a real situation with a real fix — and it is reached for far more often than that.
The plan
The same service every claim above was made about. Nothing here is graded — load whatever you like, or change nothing and read what the configuration already produces.
Plan: 0 to add, 0 to change, 0 to destroy.
Nothing in this plan is destroyed.
The state file
- serial
- 15
- lineage
- world-orders
- resources
- 15
1 value is in this file in plain text, masked here and not in the file itself.
cloud_bucket.uploads
- arn
- "arn:cloud:s3:eu-west-1:000000000000:orders-uploads-eu-west-1"
- bucket
- "orders-uploads-eu-west-1"
- force_destroy
- false
- id
- "orders-uploads-eu-west-1"
- object_lock_enabled
- false
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders"}
cloud_db_instance.primary
- allocated_storage
- 100
- arn
- "arn:cloud:rds:eu-west-1:000000000000:db:orders-primary"
- availability_zone
- "eu-west-1a"
- backup_retention_period
- 7
- db_name
- "orders"
- db_subnet_group_name
- "orders-private"
- endpoint
- "orders-primary.83d691363f86.eu-west-1.cloud-rds.internal:5432"
- engine
- "postgres"
- engine_version
- "16.4"
- id
- "orders-primary"
- identifier
- "orders-primary"
- instance_class
- "db.r6g.large"
- multi_az
- false
- password
- ••••••••
- skip_final_snapshot
- false
- storage_encrypted
- true
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders"}
- username
- "app"
cloud_iam_policy.bucket_access
- arn
- "arn:cloud:iam:eu-west-1:000000000000:policy/orders-bucket-access"
- description
- "Read and write the orders bucket"
- id
- "arn:cloud:iam:eu-west-1:000000000000:policy/orders-bucket-access"
- name
- "orders-bucket-access"
- path
- "/"
- policy
- "{\"Version\":\"2012-10-17\",\"Statement\":[]}"
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders"}
cloud_iam_role.application
- arn
- "arn:cloud:iam:eu-west-1:000000000000:role/orders-application"
- assume_role_policy
- "{\"Version\":\"2012-10-17\",\"Statement\":[]}"
- description
- "The orders service"
- id
- "orders-application"
- max_session_duration
- 3600
- name
- "orders-application"
- path
- "/"
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders"}
cloud_vpc.main
- arn
- "arn:cloud:ec2:eu-west-1:000000000000:vpc/vpc-5ad440a3"
- cidr_block
- "10.0.0.0/16"
- enable_dns_hostnames
- true
- enable_dns_support
- true
- id
- "vpc-f2bc8340"
- instance_tenancy
- "default"
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders", name = "orders-main"}
cloud_security_group.database
- arn
- "arn:cloud:ec2:eu-west-1:000000000000:security-group/sg-2ff112a6"
- description
- "Managed by Terraform"
- egress
- []
- id
- "sg-9905d588"
- ingress
- []
- name
- "orders-database"
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders"}
- vpc_id
- "vpc-f2bc8340"
cloud_security_group.web
- arn
- "arn:cloud:ec2:eu-west-1:000000000000:security-group/sg-4eb879ab"
- description
- "Managed by Terraform"
- egress
- []
- id
- "sg-dc3c10ff"
- ingress
- []
- name
- "orders-web"
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders"}
- vpc_id
- "vpc-f2bc8340"
cloud_subnet.private["a"]
- arn
- "arn:cloud:ec2:eu-west-1:000000000000:subnet/subnet-464caea8"
- availability_zone
- "eu-west-1a"
- cidr_block
- "10.0.1.0/24"
- id
- "subnet-c8249f67"
- map_public_ip_on_launch
- false
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders", name = "orders-private-a"}
- vpc_id
- "vpc-f2bc8340"
cloud_subnet.private["b"]
- arn
- "arn:cloud:ec2:eu-west-1:000000000000:subnet/subnet-ef93cca1"
- availability_zone
- "eu-west-1b"
- cidr_block
- "10.0.2.0/24"
- id
- "subnet-c923d137"
- map_public_ip_on_launch
- false
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders", name = "orders-private-b"}
- vpc_id
- "vpc-f2bc8340"
cloud_subnet.private["c"]
- arn
- "arn:cloud:ec2:eu-west-1:000000000000:subnet/subnet-c206fc26"
- availability_zone
- "eu-west-1c"
- cidr_block
- "10.0.3.0/24"
- id
- "subnet-9486b397"
- map_public_ip_on_launch
- false
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders", name = "orders-private-c"}
- vpc_id
- "vpc-f2bc8340"
cloud_instance.worker[0]
- ami
- "ami-0a1b2c3d4e5f60718"
- arn
- "arn:cloud:ec2:eu-west-1:000000000000:instance/i-edc72b4be6c41f445"
- availability_zone
- "eu-west-1a"
- id
- "i-740986cf9622122c3"
- instance_type
- "t3.medium"
- key_name
- null
- monitoring
- false
- private_ip
- "10.0.1.226"
- public_ip
- "52.195.252.190"
- subnet_id
- "subnet-c8249f67"
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders", name = "orders-worker"}
- user_data
- null
- vpc_security_group_ids
- ["sg-dc3c10ff"]
cloud_instance.worker[1]
- ami
- "ami-0a1b2c3d4e5f60718"
- arn
- "arn:cloud:ec2:eu-west-1:000000000000:instance/i-767b78828f7099885"
- availability_zone
- "eu-west-1a"
- id
- "i-6f731e0b1836039e6"
- instance_type
- "t3.medium"
- key_name
- null
- monitoring
- false
- private_ip
- "10.0.1.16"
- public_ip
- "52.189.23.140"
- subnet_id
- "subnet-c8249f67"
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders", name = "orders-worker"}
- user_data
- null
- vpc_security_group_ids
- ["sg-dc3c10ff"]
cloud_instance.worker[2]
- ami
- "ami-0a1b2c3d4e5f60718"
- arn
- "arn:cloud:ec2:eu-west-1:000000000000:instance/i-24fd4c9fa06d5994c"
- availability_zone
- "eu-west-1a"
- id
- "i-0ef9be0db515c57c0"
- instance_type
- "t3.medium"
- key_name
- null
- monitoring
- false
- private_ip
- "10.0.1.41"
- public_ip
- "52.223.134.146"
- subnet_id
- "subnet-c8249f67"
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders", name = "orders-worker"}
- user_data
- null
- vpc_security_group_ids
- ["sg-dc3c10ff"]
cloud_lb.public
- arn
- "arn:cloud:elasticloadbalancing:eu-west-1:000000000000:loadbalancer/app/orders-public/dd83d73d5d75ca7d"
- dns_name
- "orders-public-133879307.eu-west-1.cloud-elb.internal"
- enable_deletion_protection
- true
- id
- "orders-public"
- internal
- false
- load_balancer_type
- "application"
- name
- "orders-public"
- security_groups
- ["sg-dc3c10ff"]
- subnets
- ["subnet-c8249f67", "subnet-c923d137", "subnet-9486b397"]
- tags
- {environment = "prod", managed_by = "opentofu", team = "orders"}
- zone_id
- "Z421B1B24846BA"
cloud_dns_record.api
- fqdn
- "api.orders.example.com"
- id
- "api.orders.example.com_CNAME"
- name
- "api.orders.example.com"
- records
- ["orders-public-133879307.eu-west-1.cloud-elb.internal"]
- ttl
- 300
- type
- "CNAME"
- zone_id
- "Z0SAMPLEZONEID01"