DEV Community

Cover image for Terraform Modules: The Secret Sauce to Scalable Infrastructure

Terraform Modules: The Secret Sauce to Scalable Infrastructure

💡 Introduction

Welcome to the world of Infrastructure and Automation! 🚀

In today’s post, we’re going to explore one of the fundamental building blocks of Terraform — the Module.

If you’ve been using Terraform for a while, you already know how quickly your configuration files can grow messy as your infrastructure expands. That’s where modules come to the rescue. They help you organize, reuse, and standardize your Terraform code — making your infrastructure cleaner, more scalable, and much easier to maintain.

In this guide, we’ll break down:

  • What a Terraform module is

  • How it’s structured

  • Why using modules is beneficial

  • And finally, how to create your own module for a real-world use case.

To wrap things up, we’ll get hands-on and build a Terraform module for both VPC and EC2, deploy an instance, and serve a simple index.html page from it.

So, without further ado — let’s dive right in! 🌍


💡 Pre-Requisites

Before we dive into building our Terraform module, let’s make sure your workspace is all set up and ready to go. Here’s what you’ll need:

  • 🧑‍💻 An AWS Account with an IAM user that has Full Access to both VPC and EC2. The IAM user should have an Access Key generated and configured with the AWS CLI on your system.

    If you’re new to this step, I’ve covered it in detail here:

    👉 Set up AWS CLI and IAM User (Step 1 from my EKS blog)

  • ⚙️ Basic knowledge of Terraform — understanding what it is and how it works will make things much smoother.

Once you have these requirements in place, we’re all set to kick off our journey into Terraform Modules! 🎯


💡 What is a Terraform Module?

Terraform lets you define your infrastructure as code using HashiCorp Configuration Language (HCL). And like any programming language, it embraces one golden principle — DRY (Don’t Repeat Yourself).

Instead of repeatedly provisioning similar resources (like networking components or security groups) for every new environment, Terraform allows you to encapsulate those recurring configurations inside a module.

In simple terms, a Terraform module is a collection of Terraform configuration files (.tf or .tf.json) that live together in the same directory and work as a single logical unit.

Here are a few examples of what Terraform modules can represent:

  • 🧱 An AWS VPC module containing subnets, route tables, and internet gateways

  • 💾 A Microsoft SQL Always On cluster in Azure, including NSGs

  • ☁️ A GCP Project module that enables APIs and sets permissions


Why Modules Matter

Imagine you’re setting up an EC2 instance the traditional way — defining every component manually: the security group, IAM role, instance profile, and the instance itself. That’s a lot of repeated configuration!

Here’s what that would look like 👇

resource "aws_security_group" "my_app_sg" {
  name        = "my-app-sg"
  vpc_id      = var.vpc_id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_iam_role" "my_app_role" {
  name = "my_app_role"
  assume_role_policy = <<EOF
{ ... }
EOF
}

resource "aws_iam_instance_profile" "my_app_profile" {
  name = "my_app_profile"
  role = aws_iam_role.my_app_role.name
}

resource "aws_instance" "my_app_server" {
  ami           = "ami-0c55b159fbd7718e9"
  instance_type = "t3.micro"
  vpc_security_group_ids = [aws_security_group.my_app_sg.id]
  iam_instance_profile   = aws_iam_instance_profile.my_app_profile.name
  subnet_id              = var.subnet_id
  user_data              = file("setup.sh")
  tags = {
    Name = "my-app-server"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, let’s see how modules make this effortless and elegant:

module "ec2_instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "5.6.0"

  name          = "my-app-server"
  ami           = "ami-0c55b159fbd7718e9"
  instance_type = "t3.micro"
  subnet_id     = var.subnet_id

  tags = {
    Name = "my-app-server"
  }

  # Configuration for managed resources
  create_iam_instance_profile = true
  iam_role_name               = "my_app_role"
  iam_role_policy_json        = "{ ... }"

  security_group_ingress = [
    {
      description = "HTTP access from anywhere"
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

See the difference?

Instead of defining every resource from scratch, you just call a module and pass the required inputs. Terraform takes care of the rest!

We’ll create our own module in the practical demonstration later — but this example perfectly shows why modules are so powerful.


Why Use Terraform Modules?

Here’s how modules simplify your infrastructure management:

  • 📦 Package related resources together into a reusable configuration

  • 👨‍👩‍👧‍👦 Share standardized configurations across your team or projects

  • 💡 Embrace DRY principles, reducing repetitive code

  • Minimize human error when referencing complex resources manually


Quick Question: How Are Terraform Resources Different from Modules?

That’s a great distinction to understand early on 👇

  • A resource in Terraform describes a single piece of infrastructure — like a VPC, subnet, EC2 instance, or IAM role.

  • A module, on the other hand, is a collection of resources grouped together to form a reusable and logical unit — for example, a complete VPC setup or an EC2 deployment stack.


💡 Benefits of Using Terraform Modules

Now that we understand what modules are and how they work, let’s explore why they’re such an essential part of Terraform best practices.

Using modules isn’t just about cleaner code — it’s about making your infrastructure reusable, scalable, and collaborative. Let’s look at the key benefits 👇

🧩 1. Reusability

Just like every programming language has functions, classes, or libraries, Terraform has modules.

They allow you to abstract a set of resources into a single reusable component that can be used across multiple projects or environments.

For example, once you’ve built a VPC module, you can reuse it for your dev, staging, and production environments — without rewriting a single line of networking code.

In short: build once, use anywhere.

🚀 2. Scalability

Modules make scaling your infrastructure seamless.

Let’s say you have a security group defined for your development environment that allows traffic on port 27017 for MongoDB. Instead of manually updating that configuration across multiple environments, you can simply update the module and propagate the change everywhere it’s used.

This ensures consistency and saves tons of time — especially when managing large-scale infrastructure across multiple environments.

🤝 3. Team Collaboration

As your Terraform usage grows across teams, modules help maintain standardization and best practices.

Platform teams can create a catalog of pre-approved modules (for example, VPC, EC2, or EKS modules) that application teams can directly use. This ensures that all deployed infrastructure meets security, compliance, and organizational standards — while allowing developers to focus on their applications.

In short, modules enable smooth collaboration, reduce misconfigurations, and promote a shared IaC culture within your organization.


💡 A Practical Demonstration

Alright, now that we’ve covered the theory — it’s time to get our hands dirty! 🧑‍💻

Earlier, we looked at an example using the official AWS EC2 module, but in this section, we’ll create our own Terraform modules from scratch.

Our goal? To build a modular Terraform setup that provisions a VPC, Security Group, and EC2 instance, which in turn hosts a static portfolio page.

🧱 What We’ll Be Building

We’ll use some of the most common AWS infrastructure components:

  • VPC (Virtual Private Cloud)

  • Subnets

  • Internet Gateway (IGW)

  • Route Table (RT)

  • Security Group (SG)

  • EC2 Instance

These resources form the foundation of most web applications, whether it’s an e-commerce site, a personal portfolio, or a marketing page.

In production environments, infrastructure code is usually broken down into reusable modules to:

  • Avoid duplication

  • Improve maintainability

  • Enable better team collaboration

For example, a DevOps team might maintain a VPC module that’s reused across multiple environments — dev, staging, and production — ensuring consistent infrastructure everywhere.

Our demo follows the same principle. We’ll organize our project into separate modules for vpc, security-group, and ec2, each containing its own:

  • main.tf — core resource definitions

  • variables.tf — variable declarations

  • outputs.tf — exported outputs

We’ll keep variables flexible (no default values) so they can be customized per environment.

⚙️ Automation with user_data

Inside our EC2 module, we’ll use a user_data script to automatically install Apache and deploy a simple index.html page.

In real-world deployments, such automation scripts (or tools like Ansible or Chef) are used to configure servers automatically at startup — eliminating the need for manual setup.

This approach is standard for bootstrapping web servers, application backends, or API services.

📂 Project Structure

The complete code is available in my GitHub repository:

👉 https://github.com/Pravesh-Sudha/terra-projects

Navigate to the terra-modules directory, and you’ll find the following structure:

terra-modules/
|
│-- main.tf                 # Calls our custom modules
├── variables.tf                 
│-- outputs.tf
|
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   │
│   ├── security-group/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   │
│   └── ec2/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
Enter fullscreen mode Exit fullscreen mode

Here, each submodule (vpc, security-group, ec2) defines its own configuration logic, and the root main.tf file simply calls these modules and passes the required variables.

Once everything is set, our static website server configuration will be ready to deploy.

🚀 Running the Terraform Project

Now let’s see it in action!

  • Initialize Terraform

    terraform init
    

This initializes the project and fetches all three modules.
Enter fullscreen mode Exit fullscreen mode
  • Review the execution plan

    terraform plan
    

You’ll see that Terraform plans to create **7 resources** in total.
Enter fullscreen mode Exit fullscreen mode
  • Deploy the infrastructure

    terraform apply --auto-approve
    

Wait for a minute or two — once the deployment completes, Terraform will output the **public IP address** of your EC2 instance.
Enter fullscreen mode Exit fullscreen mode
  • View your static website Copy the public IP into your browser, and you’ll see your web server running — serving the index.html page! 🎉

🧹 Cleanup

When you’re done experimenting, make sure to tear down the infrastructure to avoid unnecessary AWS costs:

terraform destroy --auto-approve
Enter fullscreen mode Exit fullscreen mode

With that, you’ve successfully created your own set of Terraform modules — a foundational skill for structuring production-grade Infrastructure as Code (IaC).

In case you are wondering how to reuse the components, here is an example for your file to create another VPC using our custom module:

module "vpc" "another-vpc" {
    source = "./modules/vpc"
    vpc_cidr = "<Provide the CIDR>"
    subnet_cidr = "<Provide the CIDR>"
    app_name = var.app_name
}

variable "app_name" {
    default = "Another-App"
    type = string
    description = "Name of the Application"
}
Enter fullscreen mode Exit fullscreen mode

💡 Conclusion

And that’s a wrap! 🎉

In this guide, we explored the core concept of Terraform modules — what they are, why they’re beneficial, and how they make your infrastructure reusable, scalable, and team-friendly.

We then put theory into practice by creating our own Terraform modules for VPC, Security Group, and EC2, and deployed a working static website on AWS — all using clean, modular Terraform code.

By now, you should have a solid understanding of how to:

  • Structure Terraform projects into reusable modules

  • Simplify complex configurations

  • Collaborate efficiently within teams

  • Keep your infrastructure code DRY and maintainable

The next step? Try expanding your modules! Add resources like S3 buckets, RDS databases, or Load Balancers — and see how modular design keeps your Terraform journey organized and production-ready.

References

If you’d like to go deeper, here are the key references I used while crafting this guide:

Let’s Connect 🌐

If you enjoyed this blog and want to explore more about DevOps, Terraform, and Cloud automation, feel free to connect with me here:

If you found this post helpful, share it with your DevOps peers and drop your thoughts in the comments — I’d love to hear how you use Terraform modules in your projects!

👋 Adios Amigos!

Top comments (0)