Skip to main content
New to Terragrunt? We recommend starting with the official Terragrunt quick start to learn the basics before diving into Fast Foundation’s implementation.
💡 Pro Tip: Always run terragrunt plan before apply to review changes. The before-hooks will ensure your parameters are synchronized and your configuration is valid.
Before running any Terragrunt commands in Fast Foundation, review the Gotchas section. It highlights common issues and pitfalls that are easier to avoid up front than to troubleshoot later.

Overview

Fast Foundation uses Terragrunt as the orchestration layer for Terraform. Terragrunt provides configuration management, dependency handling, and integrates our parameter management system that keeps infrastructure consistent across environments.

Why Terraform + Terragrunt?

Terraform defines what to create (resource definitions).
Terragrunt defines how to orchestrate it (configuration, dependencies, consistency checks).
  • DRY principles – Avoid repeating configuration across environments
  • Dependency management – Clear ordering of infrastructure components
  • Remote state – Centralized state using S3 (Simple Storage Service) and DynamoDB locks
  • Environment promotion – Move configurations cleanly between dev/staging/prod
  • Custom parameter sync – Our system keeps local and remote parameters in sync

Repository Structure

Infrastructure repository layout

For details about Organizational Units (OUs) and accounts, see the AWS Architecture section.
The repository mirrors your AWS Organization. At the top level you’ll see Organizational Units (OUs); accounts live one or two levels deeper. For example, to reach the Workload Development account, follow: Workloads (OU) → Development (OU) → Workload Development (account).
As a rule of thumb: OUs start with Capital letters, while accounts use lowercase.
Within an account, resources are divided by environment and region. Inside each region you’ll find:
  • shared config files, like _account.hcl, _environment.hcl,_region.hcl,_service.hcl, to set account, environment, region or service level configurations.
  • resource types or groups folders (e.g., ec2, clusters (eks/ecs), lambda, etc.)
  • each resource unit contains a Terragrunt file (the “unit”) and an inputs file (the parameters)
If the inputs.hcl file is not present yet, you may need to initialize the folder by running terragrunt init. Before-hooks automatically create the file for you or import any existing one from remote storage.
fast-foundation-infrastructure/
├─ root.hcl                            # Common Terragrunt config shared across all units
├─ _modules/                          
├─ Infrastructure/                     # OU
├─ Networking/                         # OU
├─ Organization Management/            # Account
├─ Security/                           # OU
└─ Workloads/                          # OU
   └─ Development/                     # OU
      └─ Workload Development/    # Account
         ├─ _account.hcl               # Account-level configuration
         └─ development/
            ├─ _environment.hcl        # Environment-level configuration
            ├─ global/                 # Global resources in this env/account
            └─ us-east-1/              # Regional resources
               ├─ _region.hcl          # Region-level configuration
               └─ ec2/
                  └─ _service.hcl       # Service-level configuration
                  └─ my_ec2/
                     ├─ inputs.hcl      # Input parameters for this unit (stored via parameter mgmt)
                     └─ terragrunt.hcl  # Terragrunt unit (invokes the Terraform module)

Shared Config Files

Fast Foundation relies on shared configuration files at different levels of the hierarchy. These files let you define variables once and automatically inherit them in all child units.
  • root.hcl – global project-wide configuration (backend, provider setup, hooks)
  • _account.hcl – account-specific settings like account IDs, tags, IAM roles
  • _environment.hcl – environment-wide values like environment name, prefixes, or SSO roles
  • _region.hcl – region-specific settings such as availability zones or region tags
  • _service.hcl – service-specific settings such as a common security group rule for all ec2 instances
You bring these into a unit’s terragrunt.hcl using include blocks, which makes shared variables directly available in your local unit without redefining them.
include "root" {
  path   = find_in_parent_folders("root.hcl")
  expose = true
}
The root.hcl already imports shared configuration variables. Including root is enough to access variables in _account.hcl, _environment.hcl,_region.hcl and _service.hcl files.You can easily access variables with include.root.account_vars, include.root.environment_vars, include.root.region_vars,include.root.service_vars, inside your unit configuration.

What lives where?

  • terragrunt.hcl – The “unit” file. It invokes a Terraform module, wires dependencies, configures remote state, and registers hooks.
  • inputs.hcl – The parameters for that unit (names, ARNs, sizes, tags, etc.). These inputs are managed by our parameter management system and are synchronized with S3, not committed to Git.
This separation lets you reuse modules safely while keeping sensitive or mutable inputs out of the repository and in sync across the team.

Parameter management – How inputs.hcl stays consistent

All the parameters used in the units are not stored locally or pushed to any repository. Instead, Fast Fundation stores all of them in S3 and provides an automated parameter management layer that synchronizes inputs.hcl between your local workspace and S3, so every team member work in sync. This is what the Parameter Management Automation does for you:
  • On init: Automatically creates the inputs.hcl file for you or import any existing one from remote storages
  • On plan: Warns on drift; you must resolve before applying
  • On apply: Blocks on drift to prevent inconsistent deployments
Before-hooks ensure your parameters are always consistent before any terragrunt init, terragrunt plan, or terragrunt apply command runs.

Troubleshooting drift

# Use the version from S3 (update local)
TG_SECRETS=update terragrunt plan

# Use your local version (save to S3)
TG_SECRETS=save terragrunt plan
Think of update as “pull from S3” and save as “push to S3”. Always resolve drift before running terragrunt apply. Blocking applies on drift is intentional and ensures your environments stay consistent.

Before-Hooks

In Fast Foundation, before-hooks are already part of the standard Terragrunt setup. You only need to add them if you’re creating a brand-new unit from scratch.
Terragrunt before-hooks are commands or scripts that run before specific Terragrunt commands (e.g., init, plan, apply) to enforce prerequisites and prevent mistakes.

Why they matter

  • ✅ Ensure AWS SSO sessions are valid before you run anything
  • ✅ Prevent configuration drift by syncing inputs.hcl with the authoritative S3 copy
  • ✅ Run validations (formatting, required files present, etc.) automatically
These hooks are the mechanism that powers the parameter management system.

Adding the Before Hook scripts to your Unit

If you are creating a new unit, you may need to add this hook block to its terragrunt.hcl file.
The parameter-management scripts are already included in your repository.
You need to add the before_hook section and the locals parameters described below.
terraform {
  source = "..."

  before_hook "secrets_management" {
    commands = ["init", "plan", "apply"]
    execute  = [
      "${local.parameter_script}",
      "${local.local_file_path}",
      "${include.root.locals.project_name}-terragrunt-states",
      "${local.s3_key}",
      "${local.aws_profile_infrastructure}"
    ]
    run_on_error = false
  }
}

locals {
  # Parameter management
  s3_key                     = "${path_relative_to_include("root")}/inputs.hcl"
  local_file_path            = "${get_terragrunt_dir()}/inputs.hcl"
  aws_profile_infrastructure = "${include.root.locals.project_name}-infrastructure"
  project_name               = include.root.locals.project_name

  # Cross-platform script selection
  script_base_path = "${dirname(find_in_parent_folders("root.hcl"))}/_scripts/parameter-management"

  # Platform detection through environment variables or simple fallback
  script_preference = get_env("TG_SCRIPT_TYPE", "auto")

  # Select script based on preference or auto-detection
  parameter_script = (
    local.script_preference == "powershell" ? "${local.script_base_path}.ps1" :
    local.script_preference == "python"     ? "${local.script_base_path}.py" :
    local.script_preference == "bash"       ? "${local.script_base_path}.sh" :
    "${local.script_base_path}.sh"  # Default fallback to bash
  )
}

Behavior

If the hook exits with a non-zero code, Terragrunt stops the main command.
This forces you to resolve issues like drift, expired SSO sessions, or missing inputs.hcl before any infrastructure changes are applied.
This “fail fast” design is intentional: it protects consistency across your team and environments.

Best Practices

Use unit folder names consistently

If possible, use the folder name as the resource name. This keeps the repository intuitive: the folder you are working in directly reflects the deployed resource.
  • ec2/my_app_server/ → contains inputs/terragrunt for my_app_server EC2 instance
  • eks/my_cluster/ → contains inputs/terragrunt for your my_cluster EKS cluster
This ensures resources are discoverable, reusable, and follow consistent naming conventions.

Parameter Management

Avoid these practices — they can compromise your infrastructure:
  • Hardcoding values in Terraform files
  • Skipping parameter validation
  • Deploying with parameter drift
  • Reusing production parameters in development

Gotchas

Mock outputs

Mock outputs let terragrunt init and terragrunt plan run even when the dependency hasn’t been created yet. They’re placeholders — not the real values. For additional information, visit the official Terragrunt documentation
In Terragrunt, units can depend on outputs from other units. For example: an EC2 unit may require the VPC ID exported from a networking unit. But what happens if the dependency hasn’t been applied yet? That’s where mock outputs come in. This helps during initial setup (so you don’t get a wall of errors before anything exists). However, it’s important to understand the trade-off:
  • Plans succeed early because placeholders exist
  • ⚠️ Applies may fail later if the real resource doesn’t exist yet

Key things to remember

  • If something works on plan but fails on apply with errors like “resource doesn’t exist”, it probably used mock outputs during planning
  • Always check whether dependencies (like VPCs, subnets, IAM roles) are actually applied before trusting a green terragrunt plan

State lock issues

Terragrunt and Terraform use DynamoDB locks to prevent multiple people from applying changes at the same time on the same unit. If you see a state lock error, it usually means someone else is already running a deployment. What to do:
  • ⏳ Wait for the other deployment to finish.
  • ✅ Verify you’re not overwriting someone else’s changes.
  • ✅ Refresh your parameter. Save your current changes locally (they may be overwritten).
  • 🚀 Apply your changes once you’re sure everything is okay.