Hein Lin Zaw, home
All projects
Project 01 / 032026

Terraform Bootcamp

A multi-tier AWS environment for a dynamic e-commerce store — VPC, load balancer, auto-scaling EC2, Multi-AZ RDS, and a Packer-built AMI, all provisioned with Terraform.

The Velocity e-commerce storefront running on the deployed AWS infrastructure
Year
2026
Role
Infrastructure design & automation
Stack

The goal of this project was to deploy a dynamic e-commerce application the way a small production system would be deployed: not by clicking through the AWS console, but by describing the whole environment in Terraform so it can be recreated, reviewed, and torn down.

The application is Velocity, a Bagisto storefront. The infrastructure around it is the interesting part.

The architecture

  • A custom VPC with two public subnets for the load balancer and EC2 instances, and two private subnets for the database, spread across two availability zones.
  • Internet and NAT gateways, route tables, and security groups tying the network together.
  • An Application Load Balancer handling TLS termination through ACM, health checks, and traffic distribution. Route 53 points the domain at the ALB.
  • Auto-scaling EC2 instances running Nginx as a reverse proxy in front of the PHP application.
  • A Multi-AZ RDS MySQL instance in the private subnets, with automated backups and credentials stored in AWS Secrets Manager.
  • An IAM instance profile with least-privilege permissions for S3, CloudWatch Logs, and RDS access.
  • A custom AMI built with Packer that pre-installs PHP 8.3, Nginx, Composer, the AWS CLI, the CodeDeploy agent, and the Bagisto dependencies.
  • Terraform state stored in S3 with encryption enabled.

Keeping it modular

The root module calls five child modules: vpc, ec2, loadbalancer, rds, and iam. Each module owns its own variables and outputs, so the root configuration reads as a description of the system rather than a thousand lines of resources.

For server access, I used AWS Systems Manager Session Manager instead of opening SSH to the internet. That removed the need for a bastion host and kept the instances in a more defensible network position.

What was hard

The difficult parts were rarely the individual resources. They were the connections between them:

  • Getting subnet, security group, and load balancer dependencies right so that health checks passed on the first apply.
  • Debugging RDS connectivity from an instance in a different subnet.
  • Making the Packer build reproducible and verifying that the services it enabled actually started correctly.
  • Keeping the cost of NAT Gateway, ALB, and Multi-AZ RDS in mind while testing.

terraform plan became the main debugging tool. Reading the plan carefully before applying saved more time than fixing things in the console afterwards.

What I’d do next

  • Add remote state locking and run terraform validate, tflint, and a security scanner in CI.
  • Deploy through GitHub Actions using OIDC instead of long-lived AWS credentials.
  • Add CloudWatch alarms and a dashboard for the ALB, EC2 fleet, and RDS instance.
  • Tighten the security groups further and move the application to containers if it needed to scale beyond a learning environment.
  • Automate the Bagisto release step with CodeDeploy rather than baking everything into the AMI.

This project taught me that infrastructure work is mostly about explicit dependencies, least privilege, and being able to explain why each piece exists.