Skip to main content
Welcome to Lab Three! In this session, you’ll learn how to structure Terragrunt configurations for a multi-module AWS deployment. Follow these steps to provision a VPC, EC2 instances, and manage remote state with Terraform and Terragrunt. If you ever need to retrieve your AWS credentials during the lesson, run:
show creds
You can also open the VS Code IDE in a new browser tab to copy and paste commands.
The image shows an AWS IAM user sign-in page with fields for account ID, username, and password. There's also an advertisement for AWS Skill Builder, offering access to free digital courses.

Terraform Modules Overview

ModuleSourcePurpose
VPCterraform-aws-modules/vpc/aws//?version=5.8.1Create a scalable VPC with public & private subnets
EC2 Webterraform-aws-modules/ec2-instance/aws//?version=2.0.0Launch a web server in the public subnet
EC2 Database(Your custom module)Deploy a database instance in a private subnet

1. Define the VPC Module

Create a Terragrunt configuration under Terraform stack/vpc/terragrunt.hcl:
The image shows a split-screen view of a coding environment, with instructions for defining a VPC module on the left and a Visual Studio Code editor with a README file and terminal information on the right.
terraform {
  source = "terraform-aws-modules/vpc/aws//?version=5.8.1"
}

inputs = {
  name               = "vpc"
  cidr               = "10.0.0.0/16"
  azs                = ["us-east-1a", "us-east-1b", "us-east-1c"]
  public_subnets     = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
  private_subnets    = ["10.0.201.0/24", "10.0.202.0/24"]
  enable_nat_gateway = true
  single_nat_gateway = true
}
Save the file and validate the configuration:
terragrunt validate

2. Configure Remote State in the Root

What is path_relative_to_include()?

The image shows a split-screen view with a coding task on the left, asking about the path_relative_to_include() function, and a code editor on the right displaying a Terraform configuration file with AWS VPC settings.
The path_relative_to_include() function computes the relative path from the current terragrunt.hcl to the included parent. This ensures each module’s state key is unique in S3.
In your root Terraform stack/terragrunt.hcl, add:
remote_state {
  backend = "s3"
  config = {
    encrypt = true
    bucket  = "kk-tf-state-314"
    key     = "${path_relative_to_include()}/terraform.tfstate"
    region  = "us-east-1"
  }
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite_terragrunt"
  }
}
  1. List your S3 buckets to find the Terraform state bucket:
    aws s3 ls | grep "kk-tf-state-"
    
  2. Reinitialize Terragrunt and confirm updating the backend:
    terragrunt init
    # Type "yes" when prompted
    

3. Define Common Locals

To DRY out your configuration, define shared values at the root:
locals {
  project       = "kodeloud-labs"
  ami           = "ami-0f2a1bb3c24f7285"
  instance_type = "t2.micro"
}
Validate once more:
terragrunt validate

4. Generate Provider and Terraform Version Files

Leverage Terragrunt’s generate blocks to auto-create providers.tf and a Terraform version override:
generate "provider" {
  path      = "providers.tf"
  if_exists = "overwrite_terragrunt"
  contents  = <<EOF
provider "aws" {
  region = "us-east-1"
}
EOF
}

generate "provider_version" {
  path      = "provider_version_override.tf"
  if_exists = "overwrite_terragrunt"
  contents  = <<EOF
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
EOF
}
Re-run initialization:
terragrunt init

5. Include Root Configuration in the VPC Module

In Terraform stack/vpc/terragrunt.hcl, inherit root settings:
The image shows a split-screen view with a task description on the left about setting up an include block in Terragrunt, and a code editor on the right displaying a Terraform configuration file.
terraform {
  source = "terraform-aws-modules/vpc/aws//?version=5.8.1"
}

include "root" {
  path   = find_in_parent_folders()
  expose = true
}

inputs = {
  name               = "vpc"
  cidr               = "10.0.0.0/16"
  azs                = ["us-east-1a", "us-east-1b", "us-east-1c"]
  public_subnets     = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
  private_subnets    = ["10.0.201.0/24", "10.0.202.0/24"]
  enable_nat_gateway = true
  single_nat_gateway = true
}
Apply the VPC module:
cd "Terraform stack/vpc"
terragrunt init
terragrunt apply
# Type "yes" to confirm
Verify the new VPC in the AWS Console.

6. Deploy EC2 Web Module with VPC Dependency

Create Terraform stack/ec2-web/terragrunt.hcl and reference the VPC outputs:
The image shows a split-screen view of a coding environment. On the left, there's a task description about setting dependencies for an EC2 web module, and on the right, there's a code editor displaying Terraform configuration files.
terraform {
  source = "terraform-aws-modules/ec2-instance/aws//?version=2.0.0"
}

include "root" {
  path   = find_in_parent_folders()
  expose = true
}

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

inputs = {
  name          = include.root.locals.project
  ami           = include.root.locals.ami
  instance_type = include.root.locals.instance_type
  subnet_id     = dependency.vpc.outputs.public_subnets[0]
}
Initialize and apply:
cd "Terraform stack/ec2-web"
terragrunt init
terragrunt apply
# Type "yes" to confirm

7. Add Database Dependency to the Web Module

If you have a database module under Terraform stack/ec2-database, ensure it deploys before the web server:
dependencies {
  paths = ["../ec2-database"]
}
Re-run in ec2-web:
terragrunt init
terragrunt apply
Terragrunt will sequence: VPC → Database → Web.

8. Deploy All Modules Simultaneously

From the root of Terraform stack, run:
terragrunt run-all apply
This command orchestrates every module in dependency order.

9. Verify Remote State Files in S3

Check that each module has its own state file:
BUCKET=$(aws s3 ls | grep kk-tf-state | awk '{print $3}' | head -n1)
aws s3 ls "s3://$BUCKET" --recursive
Expected output:
2024-06-23 09:14:06   180 0/terraform.tfstate
2024-06-23 09:14:06  9575 0/database/terraform.tfstate
2024-06-23 09:14:09 39803 0/web/terraform.tfstate

10. Why Generate at the Root

By generating the backend, provider, and Terraform-version files at the project root, you:
  • Centralize configuration for consistency
  • Avoid duplication in child modules
  • Simplify maintenance as your infrastructure grows

Thank you for completing Lab Three! You now have a reusable Terragrunt setup for AWS VPC, EC2, and remote state management. See you in the next lab!