Terragrunt import in a brownfield, multi-account AWS environment demands structure, guardrails, and repeatability. You need to bring existing resources under Terragrung and Terraform fast without risking production, while keeping state hardened and changes auditable. Terragrunt provides the DRY folder model, dependency graph, hooks, and shared backend config you need to do this safely. This guide shows a practical, copy‑paste path to a safe, auditable terragrunt import that hardens remote state, reduces drift, and keeps teams aligned.
At a Glance: Terragrunt Import Across AWS Accounts
- Standardize live repo layout and inheritance
- Harden S3 state with DynamoDB locks and KMS
- Use assume_role for least-privilege imports
- Sequence imports with hooks and dependencies
- Verify parity, then remove temporary lifecycles
Why A Terragrunt-First Approach For Brownfield Imports
Brownfield imports are risky: you must map real AWS resources into Terragrung and Terraform without creates or destroys in prod. In multi-account setups, the risk multiplies if each team improvises state and provider configs.
Terragrunt centralizes patterns – DRY hierarchy, shared backends, hooks, and dependency ordering – so teams import predictably.
The primary goal: a safe, auditable terragrunt import process that hardens remote state, avoids drift, and enforces consistent guardrails across accounts and regions.
Repo Layout And Environment Folders For Multi-Account Terragrunt Import
Use a live repo shaped by account, region, and environment to reduce blast radius:
– live/<account>/<region>/<env>/<service>/terragrunt.hcl
– live/terragrunt.hcl
for global inputs and reusable generate blocks
Define the provider and backend blocks at the highest directory level possible, while still meeting your isolation and security requirements. Then, inherit them into lower-level modules using the include block. This ensures consistency and reduces duplication.
State Partitioning Strategy
Organize Terraform state files by account, region, and service.
- Split modules when they have independent lifecycles, allowing for targeted updates and isolated changes.
- Keep modules together (monolithic) when resources are tightly coupled and need to be managed as a unit.
Why This Matters (Real-World Example)
We once had a case where a teammate accidentally imported a shared VPC from the “tools” account into the production state file — and overwrote critical state.
If we had used account/region-level folders
with inherited backends, this would have been impossible, because the isolation boundaries would’ve been clearly enforced.
Important Caveat
Make sure to define region-specific provider configurations in each region’s folder. This prevents mistakes like applying infrastructure to the wrong AWS region (e.g., deploying us-west-2 resources in us-east-1).
Secure Remote State: S3 Backend, DynamoDB Locking, And KMS For Terragrunt Import
Remote state must be boringly secure and reliable. Configure S3 with versioning, SSE‑KMS, access logs, and lifecycle. Use a DynamoDB table for state locking; favor on‑demand capacity during high‑throughput imports, and tune backoff/timeout to reduce lock contention. Set KMS key policies for your CI and import-only roles; avoid wildcard decrypt.
Canonical backend snippet (refer to this later):
# terragrunt.hcl (at account/region level)
generate "backend" {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
contents = <<-EOF
terraform {
backend "s3" {
bucket = "tf-state-${local.account_id}-${local.region}"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "${local.region}"
dynamodb_table = "tf-locks-${local.account_id}-${local.region}"
kms_key_id = "arn:aws:kms:${local.region}:${local.account_id}:key/${local.kms_key_id}"
encrypt = true
}
}
EOF
}
Reference: Backend Type: s3 (HashiCorp Docs)
Cross-Account Provider Auth With Assume_Role Patterns
Create per-account import roles that trust your CI/user principal. Scope permissions to read-only APIs plus targeted writes for state access (e.g., s3:GetObject on a narrow prefix, dynamodb:PutItem on the lock table). In Terragrunt, generate the AWS provider with assume_role
and default_tags
. Maintain separate profiles for “import” vs “update,” and rotate to update roles only after parity is proven.
Caveat: align session durations with long plans to prevent mid-run credential expiry.
# terragrunt.hcl (account-level)
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<-EOF
provider "aws" {
region = "${local.region}"
assume_role {
role_arn = "arn:aws:iam::${local.account_id}:role/terraform-import"
session_name = "tg-import"
}
default_tags {
tags = {
ManagedBy = "Terraform"
Env = "${local.env}"
}
}
}
EOF
}
Reference: AWS provider best practices for Terraform (AWS Prescriptive Guidance)
Import Runbook With Terragrunt Hooks And Dependency Ordering
Sequence imports to mirror dependencies: VPC → subnets → gateways and route tables → security groups → instances → IAM. In Terragrunt, wires these with dependencies for safe ordering. Use before_hook
to run terraform import with known IDs; use after_hook
to run terraform plan -detailed-exitcode to validate no creates/destroys.
# live/prod/us-east-1/network/vpc/terragrunt.hcl
terraform {
extra_arguments "common" {
commands = ["plan", "apply", "import"]
arguments = ["-input=false", "-lock-timeout=5m"]
}
}
dependency "vpc" { config_path = "../vpc" } # example pattern
before_hook "import_vpc" {
commands = ["init", "plan"]
execute = ["bash", "-lc", "terraform import aws_vpc.main vpc-0123456789abcdef0"]
}
after_hook "validate" {
commands = ["plan"]
execute = ["bash", "-lc", "terraform plan -detailed-exitcode || test $? -eq 2"]
}
Start with minimal HCL defining just the skeleton (names, IDs). Import, run plan, then fill unknown attributes. Caveat: provider version drift can change computed defaults; pin versions during import. Reference: Terraform Import documentation (HashiCorp Docs)
Handling Unknown Attributes, Lifecycle, And Drift
Gate changes with plan-only runs until parity is proven. Use -refresh-only to verify remote reality without proposing changes. Temporarily add lifecycle ignore_changes to noisy fields (timestamps, default SG rules, some IAM policy orderings); remove after parity. Maintain a tag parity checklist per account; enforce required tags and policies gradually. Use the backend snippet above to keep state consistent across folders.
Where ControlMonkey Reduces Terragrunt Import Toil
ControlMonkey discovers AWS resources across accounts and generates HCL plus import mappings that mirror your Terragrunt layout. It orchestrates import sequencing with guardrails, validates plans for zero-downtime risk, and continuously detects drift post-import. It also integrates with PR workflows so every import is reviewable and auditable. Want more background? See our deep-dive guide to Terraform import
Move Faster And Safer With Terragrunt Import
A structured repo, hardened S3+DynamoDB state, least-privilege roles, and hook-driven sequencing make terragrunt import safe and repeatable. You’ll cut risk, keep prod stable, and build a clean audit trail.
ControlMonkey can automate discovery, code generation, and drift prevention on top. Request a demo to see it in action.