> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fastfoundation.nimble.la/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy a Custom Module

> Create a custom EC2 wrapper module with supporting resources

<Warning>
  This workshop assumes you’re already familiar with the techniques and concepts in the  **Introduction & Key Concepts** section. If you haven’t reviewed it yet, start with [AWS Architecture](/introduction-key-concepts/1-aws-architecture) and work your way through the rest of the section to fully understand the mechanics and philosophy behind our setup.
</Warning>

## Overview

<Tip>
  Why a wrapper? It standardizes patterns (networking, security, tagging) across
  teams while keeping the underlying upstream module up-to-date. This example uses
  a remote module from Git, demonstrating how teams can share reusable infrastructure
  patterns across multiple projects.
</Tip>

This workshop walks you through deploying a pre-built wrapper module built on top of the official [terraform-aws-modules/ec2-instance](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance). The wrapper extends the base EC2 functionality with additional AWS components—such as an Application Load Balancer (ALB)—to create a complete, production-ready web application stack.

***

## Prerequisites

<Check>
  * AWS SSO signed in: `aws sso login` (see [Getting Started with AWS SSO](/aws-sso/1-getting-started-with-aws-single-sign-on-sso) if you need help setting this up)
  * Fast Foundation **infrastructure repository** cloned
  * You know the **target account / environment / region** you'll deploy to
  * You have a **VPC** and at least one **private subnet** and one **public subnet**. Both tagged with "Type: Private/Public" according the case.
</Check>

***

## Module Structure

<Tip>
  **Remote Module Benefits:** By referencing the module from Git, multiple teams can use
  the same standardized wrapper while the module maintainers can update it centrally.
  The `ref=main` ensures you're using a specific version. Check the [official documentation](https://terragrunt.gruntwork.io/docs/features/units/#remote-opentofuterraform-modules) for more details.
</Tip>

The wrapper module is already created and available in the fast-foundation-examples repository provided by your team. Instead of creating local files, you'll reference it directly from Git.

👉 Take a moment to explore the repository files to understand the structure and configuration you’ll be using them throughout this workshop.

## Target folder (where to place your unit)

<Warning>
  If your **ec2** folder does not already exist, create it and add an **\_service.hcl** file:

  ```
  locals {
    service = basename(get_terragrunt_dir())
  }
  ```
</Warning>

Now you’ll deploy this wrapper module into your infrastructure. To do this, you need to create two files in the appropriate folder:

* the deployment unit file → **terragrunt.hcl**
* the parameters file → `inputs.hcl`

For this workshop, we’ll work in the **development\_workload\_account** account.

You can copy the examples below — [terragrunt.hcl](#terragrunt-hcl) and [inputs.hcl](#inputs-hcl) — into your module folder, following the structure shown in this file system tree:

```
fast-foundation-infrastructure/
└── Workloads/
    └── Development/
        └── <development_workload_account>/
            └── development
                └── us-west-1/
                    └── ec2/
                        └── _service.hcl
                        └── web-server/
                            ├── terragrunt.hcl
                            └── inputs.hcl
```

***

## Step 1 — Create `terragrunt.hcl`

This references the **wrapper module** from the fast-foundation-examples repository.

<CodeGroup>
  ```hcl terragrunt.hcl theme={null}
  terraform {
    # Reference the wrapper module from the examples repository
    source = "git@github.com:<your-org>/fast-foundation-examples.git//terragrunt/modules/ec2-wrapper?ref=main"
  }

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

  # Optional: Use dependencies instead of data sources to fetch VPC information
  # dependency "vpc" {
  #   config_path = "../../networking/vpc"
  #   
  #   mock_outputs = {
  #     vpc_id = "vpc-mock"
  #     private_subnet_ids = ["subnet-mock1", "subnet-mock2"]
  #     public_subnet_ids = ["subnet-mock3", "subnet-mock4"]
  #   }
  #   mock_outputs_allowed_terraform_commands = ["validate", "plan"]
  # }

  locals {
    # Base variables from parameter store
    region_vars         = include.root.locals.region_vars.locals
    environment_vars    = include.root.locals.environment_vars.locals
    service_vars        = include.root.locals.service_vars.locals
    inputs = try(read_terragrunt_config("${get_terragrunt_dir()}/inputs.hcl").locals, {})

    # Use directory name for resource naming
    instance_name = basename(get_terragrunt_dir())
  }

  inputs = {
    # Resource naming based on directory name
    name = "${local.instance_name}-${local.environment_vars.environment}"

    # Environment fetch from repository structure
    environment = local.environment_vars.environment

    # Parameters from inputs file
    vpc_id = try(local.inputs.vpc_id, null)
    vpc_private_subnets = try(local.inputs.vpc_private_subnets, [])
    vpc_public_subnets = try(local.inputs.vpc_public_subnets, [])
    instance_type = try(local.inputs.instance_type, null)
    app_port = try(local.inputs.app_port, null)
    health_check_path = try(local.inputs.health_check_path, null)
    enable_deletion_protection = try(local.inputs.enable_deletion_protection, true)
    
    tags = {
      Name = "${local.instance_name}-${local.environment_vars.environment}"
      Service = local.instance_name
      DeployedBy = "terragrunt"
    }
  }
  ```
</CodeGroup>

***

## Step 2 — Define `inputs.hcl`

Parameter values are stored in Git. Update the VPC ID and subnet IDs with valid values from your environment.

<CodeGroup>
  ```hcl inputs.hcl theme={null}
  locals {
    # Environment-specific configuration stored in parameter store
    vpc_id = "vpc-0f7fff5a089d46f0a" # Change with a valid vpc id from your environment
    vpc_private_subnets = ["subnet-0eb97763906e0b202"] # Change with a valid private subnet for the web instance
    vpc_public_subnets = ["subnet-0a47ba8a3d3c2470e", "subnet-0a7f55f161ed7a956"] # Change with at least two valid public subnet for your ALB
   
    instance_type = "t3.small"
    
    # Application configuration
    app_port = 8080
    health_check_path = "/health"
    enable_deletion_protection = false
  }
  ```
</CodeGroup>

***

## Step 3 — Deploy the custom module

Run these commands from your new folder:

```bash theme={null}
terragrunt init
terragrunt plan
terragrunt apply
```

***

## Cleanup

<Warning>
  **Important**: Don't forget to clean up the resources you created during this workshop to avoid unnecessary AWS charges. This deployment creates multiple resources including EC2 instances, Load Balancers, and Security Groups.
</Warning>

To destroy all resources created by the custom module, run the following command from your deployment folder:

```bash theme={null}
terragrunt destroy
```

When prompted, type `yes` to confirm the destruction of resources.

***

## Notes & Options

### **Terragrunt Dependencies vs. Data Sources**

If you prefer stronger guarantees about resource ordering (and want to avoid relying on data-source lookups), you can switch to using **Terragrunt dependencies** instead:

1. Uncomment the dependency block in `terragrunt.hcl`
2. Update your module inputs to accept values like `vpc_id` and subnet IDs directly
3. Pass outputs such as `dependency.vpc.outputs.vpc_id` into the module
