AWS with Terraform (Day 22)

Building a 2-Tier AWS Architecture with Terraform (EC2 + RDS)

Learning, Building, and Moving Forward as a DevOps Engineer

Day 22 of my DevOps journey is complete.

Despite being in an active job search phase and managing real-life responsibilities, I’m continuing to focus on hands-on, production-style cloud architecture. Today’s work was about designing and implementing a secure, modular two-tier AWS architecture using Terraform — the kind of setup that forms the backbone of many real-world applications.

This wasn’t a demo-only exercise. I pulled the code, extended it, and implemented a NAT Gateway with proper routing to enable secure outbound access for the database tier — exactly how it should be done in real environments.


Architecture Overview

The design follows a classic two-tier application model, implemented with security and scalability in mind:

Web Tier (Public)

  • EC2 instance running a Flask application

  • Deployed in a public subnet

  • Internet access via Internet Gateway

  • Listens on port 80

Data Tier (Private)

  • RDS MySQL instance

  • Deployed in private subnets across multiple AZs

  • No direct inbound internet access

  • Outbound connectivity via NAT Gateway (for patching, backups, etc.)

Secrets Management

  • AWS Secrets Manager stores database credentials

  • Terraform generates a strong random password

  • Secrets stored as JSON (username, password, engine, host)

Network & Security

  • Custom VPC with public and private subnets

  • NAT Gateway for private subnet outbound traffic

  • Tight security group rules enforcing least privilege


Terraform Project Structure

To keep the code clean, reusable, and production-ready, the project is broken into custom Terraform modules. Each module owns a single responsibility.

Modules Used

  • secret
    Generates a secure database password and stores credentials in AWS Secrets Manager

  • vpc
    Provisions the VPC, public & private subnets, Internet Gateway, route tables, and NAT Gateway

  • security_group
    Creates separate security groups for the web and database tiers

  • rds
    Deploys a private RDS MySQL instance using credentials from Secrets Manager

  • ec2
    Provisions the web server and deploys the Flask app via user data

The root module wires everything together using outputs and input variables.


Key Implementation Details

Secure Secrets Handling

Instead of hardcoding credentials:

  • Terraform generates a 16-character random password

  • Credentials are stored securely in AWS Secrets Manager

  • Secret payload is structured JSON:

{
  "username": "app_user",
  "password": "generated-secret",
  "engine": "mysql",
  "host": "rds-endpoint"
}

Only required outputs are passed to dependent modules, reducing blast radius.


VPC, Subnets, and NAT Gateway

  • Public subnets host the EC2 instance

  • Private subnets host the RDS instance

  • NAT Gateway enables outbound access from private subnets

  • Route tables are explicitly configured so MySQL traffic routes through NAT

This is a commonly missed step — without NAT and routing, private RDS instances often fail during patching or updates.


Security Groups – Least Privilege by Design

  • Web Security Group

    • Allow HTTP (80) from anywhere

    • SSH restricted to a specific IP only

  • Database Security Group

    • Allow MySQL (3306) only from the web security group

    • No public exposure

Security groups reference each other instead of CIDR blocks — a cleaner and safer approach.


RDS in Private Subnets

  • Deployed across multiple private subnets

  • Not publicly accessible

  • Ingress limited strictly to web tier

  • Designed for availability and isolation


EC2 Application Deployment

The EC2 instance uses a user data template to:

  • Install system dependencies

  • Deploy a Flask application

  • Inject database connection details

The app exposes:

  • / – homepage

  • /health – database connectivity check

  • /db/info – database metadata

It performs basic insert and read operations to validate end-to-end connectivity.


Terraform Workflow Used

terraform init
terraform plan
terraform apply
terraform destroy

RDS provisioning takes time (often 10–15 minutes), so patience is part of the process.


Validation Steps

After deployment:

  • Verified Flask app via EC2 public DNS

  • Tested /health and /db/info endpoints

  • Confirmed RDS had no public access

  • Checked Secrets Manager values

  • Validated private subnet routing via NAT Gateway


Best Practices Reinforced

  • Use Terraform modules for clarity and reuse

  • Never expose databases publicly

  • Restrict SSH access aggressively

  • Always configure NAT for private subnet outbound access

  • Destroy non-production environments to manage cost

  • Review terraform plan before applying


Diagram




What’s Next

Future enhancements to this architecture:

  • Auto Scaling Group + Application Load Balancer

  • Runtime secret retrieval using IAM roles

  • Secrets Manager automatic rotation

  • Environment isolation using Terraform workspaces


Personal Note

I’m actively looking for a DevOps / Cloud Engineer role.

Even during a challenging phase, I’m staying consistent with:

  • Real AWS architecture work

  • Terraform best practices

  • Security-first design

  • Public documentation of learning

If your team values hands-on DevOps engineers who build, break, fix, and document, I’d love to connect.


Day 22 completed. Still building. Still learning. Still moving forward.

Here is the session link:



Comments

Popular posts from this blog

AWS with Terraform (Day 01)

AWS with Terraform (Day 27)

AWS with Terraform (Day 02)