DEV Community

Stephanie Makori
Stephanie Makori

Posted on

Building a Scalable Web Application on AWS with EC2, ALB, and Auto Scaling using Terraform

On Day 26 of the Terraform Challenge, I moved from deploying static infrastructure to building a scalable web application architecture on AWS using Terraform. This project brought together EC2 Launch Templates, an Application Load Balancer, an Auto Scaling Group, and CloudWatch alarms into a modular infrastructure design that can automatically respond to changes in demand.

This was one of the most practical labs in the challenge because it demonstrated how multiple Terraform modules can work together to create a production-style environment where traffic is distributed across healthy instances and scaling decisions happen automatically.

Project Architecture

The infrastructure was split into three reusable Terraform modules:

  1. EC2 Module

    This module created the launch template and security group for the web application instances.

  2. ALB Module

    This module provisioned the Application Load Balancer, listener, target group, and the ALB security group.

  3. ASG Module

    This module created the Auto Scaling Group, attached instances to the ALB target group, and configured CPU-based scaling policies with CloudWatch alarms.

By separating the resources into modules, the deployment stayed organized and reusable. Each module focused on one responsibility, and outputs from one module were passed as inputs to another.

Why Modular Design Matters

Instead of placing all AWS resources in one Terraform file, I separated them into modules so that each part of the infrastructure could be reused independently.

For example:

  • The EC2 module outputs the launch template ID
  • The ALB module outputs the target group ARN
  • The ASG module consumes both outputs to launch instances and attach them behind the load balancer

This modular structure keeps the environment configuration clean and makes it easier to maintain or expand the infrastructure later.

The envs/dev configuration only needed to define environment-specific variables like:

  • AMI ID
  • desired capacity
  • subnet IDs
  • VPC ID

All the infrastructure logic remained inside the modules.

Deployment Workflow

After building the modules, I deployed the infrastructure using the normal Terraform workflow:

  1. terraform init
  2. terraform validate
  3. terraform plan
  4. terraform apply

Terraform created:

  • EC2 Launch Template
  • Application Load Balancer
  • Target Group
  • Auto Scaling Group
  • CPU scale-out and scale-in policies
  • CloudWatch alarms

After deployment, Terraform returned the ALB DNS endpoint, which served as the public URL for the application.

When I opened the ALB URL in the browser, the application returned:

Deployed with Terraform — environment: dev

This confirmed that:

  • the EC2 instances launched successfully
  • the load balancer was routing traffic correctly
  • the Auto Scaling Group registered healthy targets

How Auto Scaling Works

The Auto Scaling Group was configured with:

  • minimum capacity: 1 instance
  • desired capacity: 2 instances
  • maximum capacity: 4 instances

CloudWatch alarms monitored average CPU utilization:

  • If CPU reached 70%, Terraform triggered a scale-out policy to add one instance
  • If CPU dropped to 30%, Terraform triggered a scale-in policy to remove one instance

This creates elasticity in the infrastructure, allowing the application to handle increased load while reducing costs during low usage.

One important configuration in the Auto Scaling Group was:

health_check_type = "ELB"

This setting ensures that scaling decisions are based on the Application Load Balancer health checks rather than only EC2 instance status.

Without it, an EC2 instance could remain "healthy" from AWS's perspective even if the web server application had failed. Using ELB health checks ensures only working instances receive traffic.

Benefits of This Architecture

This infrastructure design provides several key advantages:

1. High Availability

The Application Load Balancer distributes requests across multiple instances, reducing the risk of downtime.

2. Elastic Scaling

The Auto Scaling Group increases or decreases capacity based on CPU demand automatically.

3. Modular Reusability

Each module can be reused in other environments such as staging or production.

4. Maintainability

Because the modules are separated by function, updates can be made without affecting unrelated components.

5. Cost Efficiency

Scaling policies ensure resources are only added when needed.

Cleanup

Once the deployment was verified, I ran:

terraform destroy

This removed the Auto Scaling Group, load balancer, target group, launch template, and CloudWatch alarms.

Cleaning up after testing is important because EC2 instances and ALBs continue incurring charges if left running.

Final Thoughts

This project was a major step forward in understanding how scalable infrastructure is built on AWS with Terraform.

It was not just about provisioning EC2 instances, but about connecting multiple services into a self-managing system:

  • Launch Templates define compute
  • ALB distributes traffic
  • ASG manages instance count
  • CloudWatch triggers scaling actions

The biggest lesson was understanding how Terraform modules can model real infrastructure relationships while keeping the code reusable and organized.

This project felt like the first truly production-style deployment in the challenge, combining modularity, scalability, automation, and resilience into one infrastructure workflow.

Day 26 showed how Infrastructure as Code moves beyond provisioning resources into designing systems that can adapt automatically to real demand.



Enter fullscreen mode Exit fullscreen mode

Top comments (0)