Have you ever wanted to deploy a production-grade application that can handle thousands of kids taking math tests simultaneously without breaking a sweat? Today, we’re diving into exactly how to do that using Terraform, AWS ECS Fargate, and Docker. Say hello to the math-test-app—a Node.js-based math testing platform built for scale and reliability.
The Challenge
Building infrastructure manually through the AWS console is tedious, error-prone, and not repeatable. As developers, we want infrastructure as code — where every resource is defined, version-controlled, and reproducible. We also need high availability, auto-scaling, and proper load balancing for an application that kids depend on during test time.
Enter Terraform and AWS ECS Fargate.
What We’re Building
The math-test-app project deploys a fully managed, containerized application with:
- High availability across multiple availability zones
- Auto-scaling that grows with demand and shrinks when traffic drops
- Load balancing to distribute traffic evenly
- Containerization using Docker (image:
muralidockertest/math-test-app:2.0) - Infrastructure as Code with Terraform for repeatability
All of this without worrying about managing EC2 instances directly. That’s the beauty of AWS Fargate.
Architecture Overview

1. VPC and Networking
The foundation begins with a Virtual Private Cloud (VPC) configured with CIDR block 10.0.0.0/16. Within this VPC, we create:
- 2 Public Subnets (10.0.1.0/24 and 10.0.2.0/24) — where the Application Load Balancer lives and can receive internet traffic
- 2 Private Subnets (10.0.10.0/24 and 10.0.11.0/24) — where ECS tasks run securely, isolated from direct internet access
This separation is crucial for security. Your containers never expose themselves directly to the internet.
2. Load Balancing
The Application Load Balancer (ALB) sits in the public subnets and receives all incoming HTTP traffic on port 80. It then intelligently forwards requests to healthy ECS tasks running on port 3000 in the private subnets.
The target group’s health check ensures that only healthy containers receive traffic. If a task fails to respond to HTTP requests, the ALB stops sending traffic to it.
health_check {
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 3
interval = 30
path = "/"
matcher = "200"
}
This means the ALB pings your app every 30 seconds, and if it gets 2 consecutive failures, that task is marked unhealthy and taken out of rotation.
3. Security Groups
Two security groups work together:
- ALB Security Group — allows inbound HTTP (port 80) and HTTPS (port 443) from the world
- ECS Tasks Security Group — allows inbound traffic only on port 3000 from the ALB, nothing else
This creates a fortress-like defense. Traffic can only reach your app through the ALB. Direct access is blocked.
4. NAT Gateways
Here’s a detail many overlook: your containers running in private subnets need to reach the internet for package updates, API calls, or pulling dependencies. That’s where NAT Gateways come in.
Each private subnet routes its outbound traffic through a NAT Gateway in the corresponding public subnet. Your containers get internet access without ever being directly exposed to the internet themselves.
5. ECS Fargate
This is where your app lives. Fargate handles the heavy lifting — you don’t manage EC2 instances, security patches, or cluster scaling. You just define your task, set CPU and memory, and Fargate handles the rest.
For math-test-app, we specify:
- CPU: 512 units
- Memory: 1024 MB
- Desired Count: 2 tasks (one per AZ for high availability)
- Image:
muralidockertest/math-test-app:2.0 - Port: 3000
6. Auto-Scaling
Here’s where it gets intelligent. The setup includes two auto-scaling policies:
CPU-based Scaling: If average CPU utilization hits 70%, Fargate automatically spins up more tasks (up to a max of 4). When traffic drops and CPU falls below 70%, it scales down.
Memory-based Scaling: Similarly, if memory utilization reaches 80%, new tasks are created.
This means:
- During peak test times, your app automatically scales to handle the load
- During off-peak hours, you pay for fewer resources
- No manual intervention needed
7. Logging
Every task writes logs to CloudWatch, making debugging and monitoring a breeze. The Terraform config sets up a log group with a 7-day retention policy, keeping costs reasonable while giving you enough history to investigate issues.
The Terraform Blueprint
The ecs.tf file defines all of this declaratively. Key variables you can customize:
variable "app_name" {
default = "math-test-app"
}
variable "container_port" {
default = 3000
}variable "container_cpu" {
default = 512 # Options: 256, 512, 1024, 2048, 4096
}variable "container_memory" {
default = 1024 # in MB
}variable "desired_count" {
default = 2
}
Want to test with smaller resources? Change container_cpu to 256 and container_memory to 512. Need more capacity? Bump desired_count to 3. It’s that simple.
Deploying in 3 Steps
- Initialize Terraform:
terraform init
2. Review the plan:
terraform plan
3. Apply the configuration:
terraform apply
Once deployed, Terraform outputs the ALB’s DNS name. Paste it in your browser, and boom — your math-test-app is live.
Repo URL : https://github.com/muralikrishna-sunkara/math-test-app
Why This Architecture?
- Reliability: Multi-AZ deployment means if one availability zone goes down, your app stays live
- Scalability: Auto-scaling handles traffic spikes automatically
- Security: Private subnets, security groups, and NAT gateways create multiple layers of protection
- Cost-efficiency: You only pay for resources you use; Fargate scales down during off-peak hours
- Simplicity: Fargate removes the operational burden of managing infrastructure
The Bottom Line
This Terraform configuration transforms deploying a web application from a complex, manual process into a simple, repeatable workflow. Whether complex applications, the infrastructure automatically adapts.
The beauty of Infrastructure as Code is that this entire setup is version-controlled, documented, and can be deployed identically across environments. Change the variables, run terraform apply, and you have a new environment ready to go.
For educational platforms like a kids’ math testing app, this level of reliability and scalability isn’t just nice to have — it’s essential. When exam time arrives, you want your infrastructure to just work.