When a Terraform plan fails mysteriously, or a teammate accidentally destroys a production database, the root cause often traces back to state mismanagement. State is the map Terraform uses to know what real-world resources exist—and when that map gets corrupted, you're flying blind. Over the years, we've seen teams struggle with the same three mistakes again and again. Here's what they are and how to fix them.
1. The Allure of the Manual Edit: Why Direct State Surgery Backfires
It starts innocently enough: a resource was created outside Terraform, or a configuration change didn't register, and someone runs terraform state rm or terraform import without fully understanding the consequences. Manual state manipulation is the number one cause of state drift and team confusion.
What Happens When You Edit by Hand
Terraform's state file is a JSON document that maps resource addresses to real infrastructure IDs. When you manually remove or rename a resource in state, you break that link. The next time someone runs terraform apply, Terraform may try to recreate the resource (if it's still in config) or leave an orphaned resource in your cloud account. We've seen teams lose entire VPCs because a manual state edit desynchronized the mapping.
The real danger isn't just technical—it's social. Once one person edits state directly, others lose trust in the state's accuracy. They start running terraform plan with suspicion, and soon everyone resorts to manual fixes. This erodes the entire value proposition of Infrastructure as Code.
How to Avoid Manual Edits
First, enforce a strict no-manual-edit policy through your CI/CD pipeline. Use terraform plan and terraform apply as the only way to modify state. For imports, always use terraform import followed by a full plan and apply cycle. For removals, use terraform state rm only as part of a deliberate refactor with peer review. Consider enabling state locking in your backend (e.g., DynamoDB for S3) so that two people can't edit simultaneously.
If you must inspect state, use terraform show or terraform state list—read-only commands that don't change anything. And never edit the state file directly in an editor; that's a recipe for JSON syntax errors that can break the entire state.
2. The Monolithic State Trap: When One File Rules Them All
Many teams start with a single Terraform configuration and a single state file. It works fine for small projects, but as infrastructure grows, the monolith becomes a bottleneck. Anyone who has waited 15 minutes for terraform plan on a 20,000-resource state knows the pain.
Why Large State Files Hurt
A monolithic state file slows down every operation because Terraform must refresh every resource. More importantly, it increases the blast radius: a mistake in one module can corrupt the entire state, affecting all environments. We've seen teams accidentally delete a staging database while applying changes meant for a different module—because everything was in one state.
Large state files also make collaboration difficult. Only one person can hold the state lock at a time, so teams end up queuing their changes. This kills the agility that IaC promises.
Strategies for Breaking Up State
The standard approach is to split state by environment (dev, staging, prod) and by logical component (networking, compute, data). Use terraform_remote_state data sources to pass outputs between states. For example, the networking state can output VPC IDs, which the compute state reads.
A more advanced pattern is to use Terragrunt or Terraspace to manage multiple states with DRY configurations. These tools generate backend configurations automatically and handle dependencies between states. The key is to find a granularity that balances isolation (small blast radius) with coordination (not too many states to manage).
We recommend starting with three to five state files per environment and adjusting as you learn. Avoid going to extremes—hundreds of tiny state files create their own management overhead.
3. Neglecting State Security: The Open Secret in Your S3 Bucket
State files contain sensitive information: resource IDs, IP addresses, and sometimes plaintext secrets (if you're not using a secrets manager). Leaving them unprotected in a public S3 bucket or unencrypted at rest is a compliance disaster waiting to happen.
The Risk of Exposed State
Imagine an attacker gaining read access to your state file. They can see every resource you have, its configuration, and its dependencies. With write access, they could modify state to point resources to their own accounts. This is why state security is non-negotiable.
Even with encryption, if you store state in a bucket with public list permissions, anyone can enumerate your infrastructure. We've audited teams who thought their state was safe because they used server-side encryption, but the bucket policy allowed public read—effectively giving away the keys.
How to Lock Down State
Always use a remote backend that supports encryption and access control. For AWS, that means S3 with server-side encryption (SSE-S3 or SSE-KMS) and a bucket policy that restricts access to specific IAM roles. Enable versioning on the bucket so you can recover from accidental deletions or corruptions.
For state locking, use DynamoDB with a table that has a primary key named LockID. This prevents concurrent modifications that could corrupt state. Also, enable logging and monitoring on the bucket—set up CloudTrail or S3 access logs to detect unauthorized access attempts.
Finally, never store secrets in state. Use a secrets manager like AWS Secrets Manager or HashiCorp Vault, and pass secret values as variables. Terraform state will still reference the secret ARN, but not the secret value itself.
4. The Drift Dilemma: When State and Reality Diverge
State drift happens when resources are changed outside of Terraform—via the cloud console, CLI, or another automation tool. Terraform's next plan will try to revert those changes, causing confusion and potential downtime.
Common Causes of Drift
Developers often tweak resources manually for quick tests, forgetting to update the Terraform config. Or a CI/CD pipeline from another team modifies a resource without coordinating. Over time, the state becomes a fiction, and applying a plan can break things unexpectedly.
Drift is especially dangerous in shared environments like staging, where multiple teams deploy changes. Without a single source of truth, no one knows what the infrastructure actually looks like.
Detecting and Resolving Drift
Run terraform plan regularly—ideally in a scheduled CI job—and compare the output with your expected configuration. Use terraform refresh (or the -refresh-only flag in newer versions) to update state without applying changes. But be careful: refresh can mask drift by accepting the current state as correct.
A better approach is to use a drift detection tool like driftctl or Terraform Cloud's drift detection feature. These tools alert you when resources differ from the config, without modifying state. Then you can decide whether to update the config (to match reality) or apply Terraform (to revert the manual change).
We recommend a policy of "config is king": any manual change should be immediately followed by a pull request that updates the Terraform code. If that's not possible, document the drift and schedule a remediation.
5. Remote Backend Misconfiguration: More Than Just an S3 Bucket
Choosing a remote backend is straightforward, but misconfiguring it can cause data loss, lockouts, or performance issues. The most common pitfalls are using the wrong region, forgetting state locking, or using a backend that doesn't support encryption.
Backend Feature Comparison
Not all backends are equal. The S3 backend is popular but requires DynamoDB for locking. The Terraform Cloud backend provides locking, versioning, and run history but costs money for large teams. The local backend is fine for solo projects but dangerous for collaboration.
We've seen teams use the AzureRM backend without enabling blob versioning, losing the ability to roll back a corrupt state. Others used the Consul backend without ACLs, exposing state to anyone with network access.
How to Choose and Configure
Start by listing your requirements: encryption at rest and in transit, state locking, versioning, access control, and audit logging. For most teams, S3 + DynamoDB is a solid choice. Configure the backend with encrypt = true, bucket, key, region, dynamodb_table, and workspace_key_prefix for workspaces.
Test your backend recovery process: delete a state file from the bucket and restore it from a version. If you can't, you're not ready for production. Also, ensure that your CI/CD roles have the minimum permissions needed to read and write state—no more.
6. When Not to Use Terraform State (And What to Do Instead)
Terraform state is essential for managing resources, but there are cases where its overhead isn't justified. For ephemeral environments that are created and destroyed frequently, state management can become a bottleneck.
Ephemeral Environments and Stateless Approaches
If you're spinning up test environments for each pull request and tearing them down after merge, maintaining a persistent state file for each environment adds complexity. Instead, consider using a tool like Terratest or Pulumi, which can manage state more dynamically. Or use Terraform with a -auto-approve flag and a fresh workspace per branch, but be prepared for state file proliferation.
Another case is when you're managing infrastructure that changes rapidly, like Kubernetes clusters with frequent pod scaling. Terraform isn't designed for real-time reconciliation—it's a declarative provisioning tool. For ongoing management, use Kubernetes-native tools like Helm or Kustomize, and use Terraform only for the cluster itself.
Alternatives for Specific Use Cases
For simple deployments (e.g., a single VM), you might not need Terraform at all. A shell script or cloud-init can suffice. For large-scale multi-cloud environments, consider Crossplane or Pulumi, which offer more flexible state models. The key is to evaluate whether Terraform's state model adds value or just complexity for your specific workflow.
We recommend doing a cost-benefit analysis: if you spend more time managing state files than deploying infrastructure, it's time to reconsider your toolchain.
7. Frequently Asked Questions About Terraform State
Can I recover from a corrupted state file? Yes, if you have versioning enabled on your backend. Restore a previous version from S3 or your backend's history. If you don't have versioning, you can try terraform state pull from another copy, but you may lose recent changes. Always enable versioning.
How often should I run terraform refresh? Avoid running refresh as a routine operation. It masks drift and can slow down your workflow. Instead, rely on terraform plan to detect drift, and only run refresh when you intentionally want to accept external changes.
Should I commit state files to Git? No. State files can contain secrets and are binary (in some backends). They also change frequently, bloating your repository. Use a remote backend and store state outside of version control. If you need a record of changes, use Terraform Cloud's run history or a changelog in your CI pipeline.
What's the best practice for sharing state between teams? Use terraform_remote_state data sources to read outputs from other teams' state files. Ensure that the remote state bucket has read-only access for consuming teams. Avoid writing to another team's state directly.
How do I handle state for multiple environments? Use workspaces or separate state files per environment. Workspaces are simpler but share the same backend configuration. Separate state files offer stronger isolation. We prefer separate state files for production and non-production environments.
8. Summary and Next Steps: Keeping Your State Firm
Terraform state is the bedrock of your IaC practice. The three mistakes we've covered—manual edits, monolithic state, and weak security—are the most common ways teams undermine that foundation. By avoiding these pitfalls, you can trust your state and focus on building infrastructure.
Here are your next moves:
- Audit your current state files: check for manual edits, large state sizes, and security settings. Use
terraform state listandterraform showto inspect. - Implement remote state with locking and encryption if you haven't already. Start with S3 + DynamoDB for AWS users.
- Break up monolithic state files by environment or component. Use
terraform_remote_stateto share outputs. - Set up drift detection: schedule a daily
terraform planin CI and review the output. Use a tool like driftctl for automated alerts. - Establish a team policy: no manual state edits, always use pull requests for config changes, and review state changes as part of code review.
By following these steps, you'll transform state from a source of anxiety into a reliable asset. Your team will deploy faster, break fewer things, and sleep better at night.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!