Terragrunt - Directory Structure

Published: August 06, 2023 Last Updated: Author: Saad Ali

Terragrunt has created a features page where they have linked their documentation detailing how to maximize DRYness across various scenarios. I am going to create a directory structure that allows me to achieve as much DRYness as possible based on the recommendations from the official documentation.

it is not necessary that you follow the same structure. You can come up with your own.

The Terragrunt Directory Structure

My GitHub repository for Terragrunt has the following bare structure:

|-- modules
|-- single-account
    |-- environments
        |-- common.yaml
        `-- terragrunt.hcl

The modules directory will house any modules we develop. For now, we'll focus on basic scenarios involving multiple VPCs within a single AWS account, hence the single-account directory. Within this, there's an environments directory, where each sub-directory represents a specific environment we'll establish. Each of these environment-named directories will feature an environment.yaml file, holding environment-specific configurations for Terragrunt to relay to Terraform.

Additionally, within the environments directory, we find a common.yaml and a terragrunt.hcl file.

The common.yaml contains common configuration, as follows, what will be used across all environments:

  region: us-east-1
    bucket_name: nixknight-terragrunt-demo-state
    file_name: state.json
  Managed-By: Terragrunt

The terragrunt.hcl file holds the top-level Terragrunt configuration, which will be applied across all our environments. It reads both the existing common.yaml. As we progress, this configuration will also accommodate any future environment.yaml files we introduce.

locals {
  vars     = yamldecode(file("common.yaml"))
  env_vars = yamldecode(file("${path_relative_to_include()}/../environment.yaml"))

  environment_tags = local.env_vars.tags

generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite"
  contents  = <<EOF
variable "aws_provider_default_tags" {
  type = map

provider "aws" {
  region = "${local.vars.aws.region}"
   default_tags {
    tags = var.aws_provider_default_tags

inputs = {
  aws_provider_default_tags = merge(local.vars.tags, local.environment_tags)

locals are a way to define a named value that can be used throughout the Terragrunt configuration. It's similar in concept to Terraform's own locals, where you can define reusable expressions so that you don't have to repeatedly write out the same values.

When Terragrunt runs a Terraform command, it first evaluates the generate blocks and generates the specified files. In this case we are using it to generate a Terraform provider configuration.

According to Terragrunt documentation as of this writing, any inputs provided are set by Terragrunt as environment variables (TF_VAR_*) for Terraform. In our setup, we have specified a value for the aws_provider_default_tags variable, aligning with what's declared in our provider configuration. Notably, Terragrunt will honor any pre-existing TF_VAR_* environment variables set prior to its execution.

This directory structure is quite basic. We'll expand upon it as we progress.


Tagged as: Linux Terraform Terragrunt IaC AWS DRY