← Back to list

Terragrunt Directory Structure

Background

Shlomi Menahem · 2026-03-14 10:11 · 0 claps · 3.9 min read
#devops #terragrunt #terraform #wrapper #hcl
Open on Medium ↗
Wiki topics: RAG · RAG & Retrieval ☁️ · DevOps & Cloud 🎬 · Film & Television 🎵 · Music & Audio

Terragrunt Directory Structure

Background

A common question is how Terragrunt integrates with Terraform.

Terragrunt is a wrapper around Terraform that passes inputs to Terraform modules to create resources.

In this article, I will present an example of how to build a Terragrunt directory structure with the required Terragrunt configuration files, enabling the use of terragrunt init, terragrunt plan, terragrunt apply, and terragrunt destroy while connecting to Terraform modules and infrastructure resources.

Generic Terragrunt Repository Structure

terragrunt/
│
├── root.hcl                          ⭐ Global configuration
│
├── _global/                          ⭐ Global module definitions
│   ├── gcs.hcl
│   ├── kms.hcl
│   ├── vpc.hcl
│   ├── gke.hcl
│   ├── peering.hcl
│   └── cloud-sql.hcl
│
├── organization/
│   └── gcp-project-name/
│
│       ├── project.hcl               ⭐ Project configuration
│       │
│       └── environment/
│           │
│           ├── env.hcl               ⭐ Environment variables
│           │
│           ├── terragrunt.hcl        ⭐ Remote state configuration
│           │
│           └── region/
│               │
│               ├── region.hcl        ⭐ Region configuration
│               │
│               ├── vpc/
│               │   └── terragrunt.hcl
│               │
│               ├── gke/
│               │   └── terragrunt.hcl
│               │
│               ├── gcs/
│               │   └── terragrunt.hcl
│               │
│               ├── peering/
│               │   └── terragrunt.hcl
│               │
│               └── cloud-sql/
│                   └── terragrunt.hcl

When a Terragrunt command (for example terragrunt apply or terragrunt plan) is executed, it typically starts inside a specific resource directory.

From that directory, Terragrunt begins walking up the directory tree, searching for configuration files referenced by the include and read_terragrunt_config functions.

At each level, Terragrunt loads the configuration from the parent directories until it eventually reaches the top-level configuration file (root.hcl).

This hierarchical lookup allows Terragrunt to inherit and combine configuration from multiple layers, such as:

  • global settings (root.hcl)
  • project configuration (project.hcl)
  • environment configuration (env.hcl)
  • regional configuration (region.hcl)
  • resource-specific configuration (terragrunt.hcl)

In practice, the process looks like this (what actually gets applied):

root.hcl
   ↓
project.hcl
   ↓
env.hcl
   ↓
region.hcl
   ↓
resource/terragrunt.hcl

Terragrunt walks up the directory tree to load shared configuration, allowing resources to reuse common settings and keeping the infrastructure code DRY and modular.

Example for .hcl file content

root.hcl (Global configuration)

locals {
  labels = {
    lob        = "applications"
    created_by = "terraform"
  }
}

inputs = {
  suffix = "example_org"
}

Purpose:

  • Global labels
  • Organization-level defaults

project.hcl (Project configuration)

locals {
  labels = {}
}

inputs = {
  project_id  = "example-project-name"
  project_num = "123456789012"
}

Purpose:

  • Defines the GCP project
  • Adds project labels

env.hcl (Environment configuration)

locals {
  labels = {
    env = "prod"
  }
}

inputs = {
  env       = "prod"
  env_short = "prod"
  network_name = "prod-vpc"
}

Purpose:

  • Defines the environment
  • Adds environment labels

region.hcl (Region configuration)

locals {
  labels = {}
}

inputs = {
  location = "europe"
  region   = "europe-west3"
}

Purpose:

  • Defines region variables used by modules.

Environment terragrunt.hcl (Remote state)


locals {
  env_config    = read_terragrunt_config(find_in_parent_folders("env.hcl"))
  region_config = read_terragrunt_config(find_in_parent_folders("region.hcl"))

  env    = local.env_config.inputs.env
  region = local.region_config.inputs.region
}

remote_state {
  backend = "gcs"

  config = {
    bucket   = "terraform-state-bucket"
    prefix   = "${local.env}/${local.region}/${basename(get_terragrunt_dir())}"
    project  = "example-project-name"
    location = "europe-west3"
  }

  generate = {
    path      = "backend.tf"
    if_exists = "overwrite"
  }
}

Purpose:

  • Defines Terraform backend
  • Shared by all resources in the environment.

Resource terragrunt.hcl

Example: ../gcs/terragrunt.hcl

include "backend_state" {
  path = find_in_parent_folders()
}

include "_res" {
  path   = find_in_parent_folders("_global/gcs.hcl")
  expose = true
}

inputs = merge(include._res.inputs, {
    encryption_key_id = dependency.kms.outputs.keys.gcs
    buckets = [
    {
      name       = "${include._res.locals.env.inputs.env}_gcs-sources_${include._res.locals.root.locals.suffix}",
      project_id = include._res.locals.proj.inputs.project_id,
      location   = include._res.locals.region.inputs.region,
      encryption = [{
        default_kms_key_name = dependency.kms.outputs.keys.gcs
      }]
      labels = merge(include._res.locals.all_labels, {
        name      = "gcs-sources"
        function  = "cloud-storage"
        tech      = "gcs"
        team      = "devops"
        component = "internal"
      })
      lifecycle_rule = [{
        condition = {
          age                        = 1
          days_since_custom_time     = 0
          days_since_noncurrent_time = 0
          num_newer_versions         = 0
          with_state                 = "ANY"
        }
        action = {
          type = "Delete"
        }
      }]
    }
  ]
})

Purpose:

  • Calls the Terraform module
  • Reads shared configuration from parent folders.

_global/gcs.hcl (Global GCS module definition)

terraform {
  source = "git@gitlab.com:mycompany/devops/terraform-modules.git//terraform-google-gcs"
}

locals {
  root       = read_terragrunt_config(find_in_parent_folders("root.hcl"))
  proj       = read_terragrunt_config(find_in_parent_folders("project.hcl"))
  env        = read_terragrunt_config(find_in_parent_folders("env.hcl"))
  region     = read_terragrunt_config(find_in_parent_folders("region.hcl"))
  all_labels = merge(local.root.locals.labels, local.proj.locals.labels, local.env.locals.labels, local.region.locals.labels)
}

dependency "kms" {
  config_path = "${get_terragrunt_dir()}/../kms"
}

inputs = merge(local.root.inputs, local.proj.inputs, local.env.inputs, local.region.inputs, {
  encryption_key_id = dependency.kms.outputs.keys["gcs"]
})

Terragrunt Commands Table

| Command                     | Description                                                         | Example                                   |
|-----------------------------|---------------------------------------------------------------------|-------------------------------------------|
| terragrunt init             | Initializes the Terraform working directory and downloads modules.  | terragrunt init                           |
| terragrunt plan             | Shows the execution plan without applying changes.                  | terragrunt plan                           |
| terragrunt apply            | Applies the Terraform changes to create or update infrastructure.   | terragrunt apply                          |
| terragrunt destroy          | Destroys the infrastructure managed by the module.                  | terragrunt destroy                        |
| terragrunt output           | Displays outputs defined in the Terraform module.                   | terragrunt output                         |
| terragrunt state list       | Lists all resources stored in the Terraform state.                  | terragrunt state list                     |
| terragrunt state show       | Shows detailed information about a resource in the state.           | terragrunt state show google_compute_vpc  |
| terragrunt state rm         | Removes a resource from the Terraform state without destroying it.  | terragrunt state rm google_compute_vpc    |
| terragrunt state mv         | Moves a resource to a different address in the state.  

Summary

This article explained how a Terragrunt repository is structured and how configuration inheritance works. Terragrunt organizes infrastructure into layers such as global, project, environment, region, and resource. When a Terragrunt command runs inside a resource directory, it walks up the directory tree and loads configuration from parent .hcl files.

Shared configuration is defined once at higher levels and reused by lower-level resources, keeping the infrastructure DRY, modular, and easier to manage. The _global directory defines reusable module configurations, while region directories deploy those modules for specific environments. Terraform state is stored centrally in a GCS bucket using a structured prefix based on the environment, region, and resource name.


메타데이터
post_id
ee156fbd75c7
slug
terragrunt-directory-structure-ee156fbd75c7
url
https://medium.com/@shlomi.menahem/terragrunt-directory-structure-ee156fbd75c7
canonical_url
https://medium.com/@shlomi.menahem/terragrunt-directory-structure-ee156fbd75c7
author_url
https://medium.com/@shlomi.menahem
status
ok
fetched_at
2026-06-14 11:28:49