You’ve automated Terraform deployments in CI/CD pipelines with GitHub Actions or Harness, but have you kept state management manual by manually running terraform state mv, terraform state rm, and terraform import commands locally? That creates an audit gap and defeats half the purpose of infrastructure-as-code.
The fix is straightforward: use Terraform’s config-driven blocks instead of CLI commands. Your state operations become part of your Terraform configuration, reviewed in pull requests, and executed in your pipeline alongside everything else.
The Three Commands You Should Stop Running
1. terraform state mv → moved block
You’ve renamed a resource in your code. Now you need to tell Terraform that the old state should map to the new name.
The old way:
terraform state mv aws_instance.web aws_instance.web_server
This command runs locally. Nobody reviews it. There’s no record in version control. If something breaks, you’re troubleshooting based on memory.
The new way:
moved {
from = aws_instance.web
to = aws_instance.web_server
}
Now the move is in your Terraform configuration. It gets reviewed in your pull request. It executes in CI/CD alongside your other changes. It’s documented forever in git history.
2. terraform state rm → removed block
You’re removing a resource from Terraform management, but want to keep the actual infrastructure.
The old way:
terraform state rm aws_s3_bucket.logs
Same problem: manual, local, no audit trail.
The new way:
removed {
from = aws_s3_bucket.logs
lifecycle {
destroy = false
}
}
The removed block tells Terraform to stop managing the resource without destroying it. The operation is documented and approved, as with any other infrastructure change.
3. terraform import → import block
You need to bring existing infrastructure under Terraform management.
The old way:
terraform import aws_instance.existing i-1234567890abcdef0
Manual command. No record of what was imported or when.
The new way:
import {
to = aws_instance.existing
id = "i-1234567890abcdef0"
}
resource "aws_instance" "existing" {
# configuration matches the existing instance
}
The import happens during terraform plan and terraform apply. It’s part of your normal workflow, not a separate operation.
Why This Matters for CI/CD Workflows
Most organizations run Terraform through some kind of automation: GitHub Actions, Harness, GitLab CI, Jenkins, or Terraform Cloud.
The whole point is to make infrastructure changes consistent, auditable, and controlled through code review.
But if you’re still running state commands manually, you’re bypassing that process for some of your most critical operations. Renaming resources, removing them from state, importing existing infrastructure – these all change what Terraform manages. They should go through the same approval process as creating new resources.
With config-driven blocks:
- State operations are code-reviewed in pull requests
- Your CI/CD pipeline executes them automatically
- Everything is version-controlled and auditable
- Team members can see exactly what changed and when
- Rollbacks work the same as any other infrastructure change
Without them:
- State operations happen locally on someone’s machine
- No review process for critical state changes
- Audit trail exists only in Terraform Cloud logs (if you’re lucky)
- Knowledge lives in whoever ran the command
- Troubleshooting requires digging through Slack to find out who did what
The Practical Benefits
Auditability: Every state operation is in git history. You can see who proposed it, who approved it, and exactly when it executed.
Consistency: State operations run in the same environment as your deployments. No more “works on my machine” problems with state files.
Knowledge sharing: New team members can see how resources were moved or imported by reading the configuration, not by asking around.
Safety: Pull request reviews catch mistakes before they affect production state. Someone renames a critical database resource? The moved block gets reviewed.
When to Still Use CLI Commands
There are legitimate cases for CLI state commands:
- Emergency fixes during an incident (but document them afterward)
- One-time migrations where you need immediate results and will clean up later
- Local development where you’re experimenting and not ready to commit
But for production infrastructure managed through CI/CD, config-driven blocks should be your default.
Implementation
Migrating is straightforward. Next time you need to:
- Move a resource: add a
movedblock instead of runningterraform state mv - Remove a resource from state: add a
removedblock instead ofterraform state rm - Import existing infrastructure: add an
importblock instead of runningterraform import
Your workflow doesn’t change. You still create a branch, make changes, open a pull request, and merge. The only difference is that state operations are now part of that workflow instead of separate manual steps.
Final Thoughts
If you’re running Terraform in CI/CD but still using CLI state commands, you’re operating with split workflows. Some changes go through code review and automation, others don’t.
Config-driven blocks close that gap. They make state management as auditable and controlled as everything else in your infrastructure.
The migration isn’t complex. Start using moved, removed, and import blocks for your next state operation and build from there.
Your future self – and your teammates – will appreciate having a complete record of what changed and why.
Explore my latest courses



