Migrating a Terraform Monolith to Terragrunt: State Slicing Without Downtime
How I decomposed a monolithic Terraform state into isolated Terragrunt modules for a SaaS client — without touching live infrastructure.
Migrating a Terraform Monolith to Terragrunt: State Slicing Without Downtime
How I decomposed a monolithic Terraform state into isolated Terragrunt modules for a SaaS client — without touching live infrastructure.
What I Built
I decomposed a monolithic AWS infrastructure state into a Terragrunt monorepo with 13 isolated state files. This system provides per-component state locking and independent execution cycles for VPC, EKS, RDS, and 10 other logical infrastructure layers without modifying live resources.

System Architecture
Monolith State — A single S3-backed remote state containing all 19 infrastructure components.
Terragrunt Modules — 13 independent directories, each with its own state file and DynamoDB lock.
Dependency Graph — Explicit wiring between modules using Terragrunt dependency blocks with versioned, cacheable outputs.
Core Technical Behavior
The core of this system is the transformation of resource addresses during the migration. In the monolith, every resource lived under a top-level parent prefix, such as module.client_stage.module.database.
The execution flow for the migration required pulling the monolith state to a local file to serve as an immutable source. A script then enumerated every resource address and dynamically discovered child modules.
Address rewriting stripped the parent module prefix while preserving resource indices. For example, module.client_stage.module.database.module.rds.aws_db_instance.this[0] was moved to module.rds.aws_db_instance.this[0] within the new isolated RDS module state.
Capture a local snapshot of the monolith state
terraform state pull > monolith.tfstate
Enumerate all resources to validate the state list is not empty
STATE_LIST=$(terraform state list -state="$MONOLITH_STATE")
Extract child module names to discover logical boundaries
DIRECT_MODULES=$(echo "$STATE_LIST" | grep "^${MODULE_PREFIX}\.module\." | \
sed "s|^${MODULE_PREFIX}\.module\.||" | \
sed 's/^\([^.[]*\).*/\1/' | sort -u)
Execute the move from the immutable source to target module files
terraform state mv \
-state="$MONOLITH_STATE" \
-state-out="$TARGET_STATE" \
"$resource" "$new_address"
The runtime behavior of the new system relies on Terragrunt dependency blocks. These blocks read output values from the completed state files of upstream modules, such as passing a VPC ID to a Kubernetes module.
Key Engineering Decisions
Script-driven slicing over manual commands ensures the operation is reproducible and reviewable in version control, preventing typos across hundreds of resource moves.
Immutable source state management using separate -state and -state-out files prevents the script from modifying the original monolith state, allowing for a clean restart if the process is interrupted.
Dynamic module discovery derives module names directly from the state list, providing a built-in completeness check that warns if a module exists in state but lacks a directory mapping.
Python-based address rewriting handles complex regex patterns for nested module prefixes and bracketed indices that are not safely expressible in bash.
Local backend initialization prior to S3 migration allows for verification of the sliced state against live infrastructure via a zero-diff plan before any state is pushed to the remote backend.
Materializing outputs via a no-op apply is required for Terragrunt’s DAG to resolve dependencies, as downstream modules cannot plan until the upstream module state contains valid outputs.
Trade-offs
Optimized for: blast radius reduction, per-module state locking, and independent execution cadence.
Sacrificed: operational simplicity during the migration window, requiring a change freeze while state existed in both monolithic and sliced forms.
Results / Cost Impact
The system now operates with 13 independent state files in S3, each with a dedicated DynamoDB lock.
Parallel workstreams are no longer blocked by a single global state lock.
Plan performance is restricted to the specific component being modified rather than the entire 19-component stack.
Conclusion
This migration turned a monolithic state into a modular system where management boundaries are explicitly defined. By treating state decomposition as a surgery problem rather than a re-creation problem, the infrastructure remained stable while the blast radius of changes was reduced.
Zero-diff plans after state slicing provide the final correctness guarantee that infrastructure remains unchanged.
Further Reading
For the full implementation details, see the complete article at jakops.cloud.
Need Help?
If you’re working on a similar state decomposition or evaluating Terragrunt adoption for a growing SaaS platform, feel free to reach out at hello@jakops.cloud.
메타데이터
- post_id
- ca2bcdde8de7
- slug
- migrating-a-terraform-monolith-to-terragrunt-state-slicing-without-downtime-ca2bcdde8de7
- url
- https://medium.com/@jakops/migrating-a-terraform-monolith-to-terragrunt-state-slicing-without-downtime-ca2bcdde8de7
- canonical_url
- https://medium.com/@jakops/migrating-a-terraform-monolith-to-terragrunt-state-slicing-without-downtime-ca2bcdde8de7
- author_url
- https://medium.com/@jakops
- status
- ok
- fetched_at
- 2026-07-26 08:49:52