Moving Terraform to a Config-Driven Workflow

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 moved block instead of running terraform state mv
  • Remove a resource from state: add a removed block instead of terraform state rm
  • Import existing infrastructure: add an import block instead of running terraform 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