# Configure remote state backendremote_state{backend="s3"generate={path="backend.tf"if_exists="overwrite_terragrunt"}config={bucket="my-terraform-state-${get_aws_account_id()}"key="${path_relative_to_include()}/terraform.tfstate"region="us-east-1"encrypt=truedynamodb_table="terraform-locks"}}
# Define dependencies between modulesdependency"vpc"{config_path="../vpc" # Mock outputs for validation without dependencies appliedmock_outputs={vpc_id="vpc-temporary-id"}mock_outputs_allowed_terraform_commands=["validate", "plan"]}# Use dependency outputsinputs={vpc_id=dependency.vpc.outputs.vpc_idsubnet_ids=dependency.vpc.outputs.private_subnet_ids}
# Define local variableslocals{environment="production"region="us-east-1" # Parse environment from pathparsed_path=regex(".*/environments/(?P<env>.*?)/.*",get_terragrunt_dir())env_name=local.parsed_path.env # Common tagscommon_tags={Environment=local.environmentManagedBy="Terragrunt"Project="MyProject"}}# Use locals in inputsinputs=merge(local.common_tags,{region=local.region})
# Override module source to test local changescdenvironments/dev/vpc
terragruntplan--terragrunt-source../../../modules/vpc-local
# Apply with local moduleterragruntapply--terragrunt-source../../../modules/vpc-local
# Reset to remote sourceterragruntplan--terragrunt-source-update
# List all resources in stateterragruntstatelist
# Move resource to new addressterragruntstatemvaws_instance.oldaws_instance.new
# Import existing AWS resourceterragruntimportaws_instance.webi-1234567890abcdef0
# Remove resource from state (keeps actual resource)terragruntstatermaws_instance.temporary
# Pull remote state for inspectionterragruntstatepull>backup.tfstate
Use DRY principle: Keep backend configuration in root terragrunt.hcl and include it in child modules using include blocks to avoid duplication
Version your modules: Always pin module sources to specific versions or tags (e.g., ?ref=v1.0.0) to ensure reproducible deployments
Implement dependency management: Use dependency blocks to explicitly define relationships between modules rather than relying on manual execution order
Leverage locals and functions: Use Terragrunt's built-in functions like find_in_parent_folders(), get_aws_account_id(), and path_relative_to_include() for dynamic configuration
Use mock outputs: Define mock_outputs in dependency blocks to enable validation and planning without requiring all dependencies to be applied first
Organize by environment: Structure directories by environment (dev/staging/prod) with shared configuration at the root level for consistency
Enable state locking: Always configure DynamoDB tables for state locking when using S3 backend to prevent concurrent modification conflicts
Use run-all carefully: Test with run-all plan before run-all apply and consider using --terragrunt-parallelism to control concurrent executions
Generate provider configs: Use generate blocks to create provider configurations dynamically, ensuring consistency across modules
Implement hooks for automation: Use before_hook and after_hook to automate tasks like validation, notifications, or compliance checks
Ensure terraform.source is correctly specified in terragrunt.hcl and the source path exists. Run terragrunt init to download modules.
Backend initialization fails
Check AWS credentials and permissions. Verify S3 bucket and DynamoDB table exist or set skip_bucket_creation = false in remote_state config.
Dependency outputs not found
Ensure dependency module is applied first. Use mock_outputs for planning without dependencies. Check config_path points to correct directory.
"Working directory already exists" error
Clear Terragrunt cache: rm -rf .terragrunt-cache then run terragrunt init again. Or use --terragrunt-source-update flag.
Module source not updating
Force source update with terragrunt init --terragrunt-source-update or delete .terragrunt-cache directory to clear cached modules.
State lock acquisition timeout
Another process holds the lock. Wait for completion or manually release lock in DynamoDB table. Check for crashed processes.
"Module not found" with relative paths
Use find_in_parent_folders() to locate root config. Ensure relative paths account for Terragrunt's working directory structure.
Run-all commands fail partially
Check individual module errors with --terragrunt-log-level debug. Verify dependencies are correctly defined. Use --terragrunt-ignore-dependency-errors cautiously.
Permission denied errors on AWS
Verify IAM role/user has required permissions. Check if role_arn in provider config is correct. Ensure MFA token is valid if required.
Circular dependency detected
Review dependency blocks to identify circular references. Restructure modules to break the cycle. Use terragrunt graph-dependencies to visualize.
Variables not being passed correctly
Check inputs block syntax in terragrunt.hcl. Verify variable names match module definitions. Use terragrunt render-json to inspect final config.
Performance issues with many modules
Adjust --terragrunt-parallelism to optimize concurrent executions. Consider splitting into smaller module groups. Use --terragrunt-include-dir for selective runs.
Quick Reference: Terragrunt vs Terraform Commands¶
Terragrunt Command
Equivalent Terraform
Key Difference
terragrunt init
terraform init
Auto-configures backend from terragrunt.hcl
terragrunt plan
terraform plan
Includes Terragrunt-managed inputs and dependencies
terragrunt apply
terraform apply
Processes dependencies and generates configs
terragrunt run-all apply
N/A
Executes across multiple modules with dependency ordering