Using Terragrunt and Anchor Tags for Infrastructure Management

Terragrunt is a powerful tool that extends Terraform to help manage infrastructure with ease, particularly in large-scale, modular environments. It enables the use of DRY (Don’t Repeat Yourself) principles by reusing configurations across multiple environments. Incorporating the concept of anchor tags—or logical links and references—into a Terragrunt workflow can make managing cloud infrastructure even more efficient. These tags allow for better navigation, organization, and reusability of modules and resources, improving the clarity of complex infrastructure.

Here’s how anchor tags can be applied effectively in Terragrunt setups.


1. What Are Anchor Tags in Terragrunt?

In Terragrunt, anchor tags function conceptually as references or logical labels that link components together across multiple modules or hierarchical configurations. They:

  • Simplify navigation between different layers (e.g., environments, modules, and resources).
  • Enable better parameterization and modularization.
  • Improve clarity in configurations by creating reusable “pointers” to shared settings.

2. Structure of Terragrunt with Anchor Tags

A typical Terragrunt structure includes:

  1. Root Directory: Contains global configurations shared across environments (e.g., remote state storage, providers).
  2. Environment Directories: Separate directories for prod, staging, and dev environments.
  3. Module Directories: Shared Terraform modules for resources like networking, compute, or storage.

Anchor tags in this context can be applied through:

  • HCL variables and outputs to create reusable links.
  • Terragrunt configurations (e.g., terragrunt.hcl) to define dependencies between modules.

3. Using Anchor Tags for Efficient Resource Linking

Example 1: Shared Variables for Cross-Module References

Define anchor-like variables at the root level that can be referenced in child modules.

terragrunt.hcl in the root directory:

inputs = {
  common_tags = {
    environment = "prod"
    owner       = "team-infra"
  }
  vpc_id = "vpc-123456" # Anchor tag for VPC
}

Child modules or environment-specific configurations can reference these tags to avoid redundancy.

terragrunt.hcl in prod/network/:

include {
  path = find_in_parent_folders()
}

inputs = {
  tags      = merge(local.common_tags, { module = "network" })
  vpc_id    = local.vpc_id # Reuse anchor tag
}

This ensures consistency across modules and simplifies updates.


Example 2: Dependency Injection

Use Terragrunt’s dependency blocks as logical anchor tags to link modules together. For example, link a vpc module with a subnet module:

terragrunt.hcl in prod/vpc/:

inputs = {
  vpc_name = "prod-vpc"
  cidr     = "10.0.0.0/16"
}

terragrunt.hcl in prod/subnet/:

dependency "vpc" {
  config_path = "../vpc" # Anchor tag pointing to VPC configuration
}

inputs = {
  vpc_id   = dependency.vpc.outputs.vpc_id
  subnet_cidr_blocks = ["10.0.1.0/24", "10.0.2.0/24"]
}

This creates a clear dependency chain, linking the subnet module to the VPC module using anchor tags.


4. Benefits of Anchor Tags in Terragrunt

a. Consistent Environment Management

  • Anchor tags simplify the process of creating multiple environments (e.g., dev, staging, prod) by referencing shared configurations.
  • Using tags like common_tags ensures that all resources are consistently labeled for tracking and governance.

b. Reduced Duplication

  • Tags prevent duplication by allowing configurations to reference reusable variables, outputs, or dependencies.
  • This aligns with Terragrunt’s goal of enforcing DRY principles.

c. Enhanced Collaboration

  • Logical tags and dependencies provide clear, actionable links for teams, making configurations easier to understand.
  • New team members can quickly grasp relationships between modules.

d. Easier Maintenance

  • Updating a single tag or variable cascades changes throughout all dependent configurations, reducing maintenance overhead.
  • For example, changing a VPC ID in the root configuration automatically updates all dependent modules.

5. Example: Complete Terragrunt Structure with Anchor Tags

Directory Structure:

├── live
│   ├── prod
│   │   ├── vpc
│   │   │   └── terragrunt.hcl
│   │   ├── subnet
│   │   │   └── terragrunt.hcl
│   ├── staging
│   │   ├── vpc
│   │   │   └── terragrunt.hcl
│   │   ├── subnet
│   │   │   └── terragrunt

Here’s how each component is structured and uses anchor tags for efficient management:


6. Terragrunt Configuration Examples

Root-Level Configuration

The root directory (live/terragrunt.hcl) contains shared configurations, such as remote state storage and common inputs.

terragrunt.hcl at the root level:

remote_state {
  backend = "s3"
  config = {
    bucket         = "my-terraform-state"
    key            = "${path_relative_to_include()}/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-lock-table"
  }
}

inputs = {
  common_tags = {
    team        = "team-infra"
    environment = "shared"
  }
}

This setup acts as an anchor for shared state and common inputs, propagating them to all child configurations.


Environment-Level Configuration

Each environment (prod, staging, etc.) has its own terragrunt.hcl file. Anchor tags reference root-level configurations and define environment-specific inputs.

terragrunt.hcl in prod/:

include {
  path = find_in_parent_folders()
}

inputs = {
  environment_name = "prod"
  additional_tags = {
    project = "critical-app"
  }
}

This configuration inherits the shared remote state and common_tags from the root, adding environment-specific tags.


Module-Level Configuration

Each module (e.g., vpc, subnet) defines specific resources and uses dependencies to link modules together.

VPC Module: prod/vpc/terragrunt.hcl

include {
  path = find_in_parent_folders()
}

inputs = {
  vpc_name = "prod-vpc"
  cidr     = "10.0.0.0/16"
  tags     = merge(local.common_tags, { module = "vpc" })
}

Subnet Module: prod/subnet/terragrunt.hcl

include {
  path = find_in_parent_folders()
}

dependency "vpc" {
  config_path = "../vpc"
}

inputs = {
  vpc_id            = dependency.vpc.outputs.vpc_id
  subnet_cidr_blocks = ["10.0.1.0/24", "10.0.2.0/24"]
  tags              = merge(local.common_tags, { module = "subnet" })
}

Here, the dependency block acts as an anchor, linking the subnet module to the vpc module. Any changes to the vpc outputs (e.g., vpc_id) automatically update the dependent configurations.


7. Advantages of Anchor Tags in This Setup

a. Centralized Management

By defining shared inputs and remote state in the root-level configuration, teams can manage changes centrally, with updates cascading throughout the hierarchy.

b. Logical Dependencies

Dependencies between modules are clearly defined using Terragrunt’s dependency block, making relationships between resources explicit and traceable.

c. Modular and Reusable Design

Anchor tags (e.g., vpc_id and common_tags) create reusable building blocks, enabling rapid deployment of additional environments or resources with minimal duplication.

d. Simplified Troubleshooting

When issues arise, anchor tags and dependencies provide a clear path for tracing and resolving problems across modules.


8. Conclusion

By integrating the concept of anchor tags into a Terragrunt-based infrastructure setup, teams can achieve a modular, maintainable, and scalable architecture. Logical linking of resources, environments, and modules streamlines cloud infrastructure management, reduces duplication, and enhances collaboration.

As cloud environments grow in complexity, leveraging tools like Terragrunt alongside a clear system of anchor tags ensures infrastructure remains organized, efficient, and easy to manage. Whether you’re deploying a single application or managing a multi-cloud strategy, this approach provides a robust foundation for modern Infrastructure as Code.