Day 22 - Deploying an RDS MySQL Web Application Stack with Terraform Modules

Introduction

For Day 22 of my 30 Days of AWS Terraform challenge, I built a small but complete web application stack using Terraform modules.

The goal of this mini project was to deploy a Flask web application on an EC2 instance and connect it securely to an RDS MySQL database running in private subnets.

This project helped me understand how a real application stack is usually separated into networking, compute, database, and security layers.

Architecture

The architecture includes:

VPC with public and private subnets
EC2 instance in the public subnet
RDS MySQL database in private subnets
Security groups controlling traffic between EC2 and RDS
Internet Gateway for public access to the web server

This diagram should show users accessing the EC2 Flask application through the internet, while the EC2 instance connects privately to RDS MySQL.

Terraform Module Structure

I used a modular Terraform structure instead of putting everything into one large main.tf file.

The project was split into these modules:

VPC module
Security Groups module
RDS module
EC2 module


This screenshot should show the root Terraform files and the modules directory.

The root module calls each child module and passes the required values between them. For example, the VPC module creates the subnet IDs, the security group module creates the EC2 and RDS security groups, and the RDS module uses the private subnet IDs.

This makes the code cleaner, easier to troubleshoot, and easier to reuse in future projects.

VPC and Subnet Design

The VPC uses the CIDR block 10.0.0.0/16.

I created one public subnet for the EC2 web server and two private subnets for RDS. RDS requires a DB subnet group, and AWS recommends placing database subnets across multiple Availability Zones for better availability.




This screenshot should prove that the public and private subnets were created correctly.

Security Group Design

The web server security group allows:

HTTP on port 80
SSH on port 22

The database security group allows:

MySQL on port 3306 only from the EC2 security group

This is the most important security part of the project. The RDS database is not open to the internet. Only the EC2 instance can connect to it.



This screenshot should show port 3306 allowed only from the EC2 security group, not from 0.0.0.0/0.

RDS MySQL Deployment

The RDS MySQL database was deployed inside private subnets. This means users cannot connect to the database directly from the internet.

The Flask application connects to the database using the RDS endpoint, database name, username, and password passed through the EC2 user data script.

This screenshot should show the RDS instance status as Available.

This screenshot should show that RDS is using private subnets.

EC2 and Flask Application

The EC2 instance runs Ubuntu and installs the required packages during bootstrapping.

The user data script installs Python, Flask, MySQL client libraries, creates the Flask app, and runs it as a systemd service.

The application exposes these endpoints:

/ for the home page
/health for database connectivity check
/db-info for database information

This screenshot should show the Terraform apply output and application URL.


This screenshot should show the web application loading in the browser.


This screenshot should prove that the Flask application can connect to the RDS database.

Validation Commands

After deployment, I used Terraform outputs to get the application URL.

terraform output application_url

I also validated the RDS endpoint:

terraform output rds_endpoint

To verify the infrastructure:

terraform state list

To confirm the application from the browser:

Why Modular Terraform Matters

This project showed why Terraform modules are useful.

Instead of writing one large Terraform file, each layer has its own responsibility.

The VPC module handles networking.
The security group module handles access control.
The RDS module handles the database.
The EC2 module handles the application server.

This makes the project easier to read and maintain.

In real environments, this same pattern can be extended for production workloads with ALB, Auto Scaling Groups, Secrets Manager, CloudWatch, and private EC2 access through SSM Session Manager.

Security Improvements for Production

For a demo, this setup works well. For production, I would improve it by:

Restricting SSH to my IP or removing SSH completely
Using AWS Systems Manager Session Manager instead of SSH
Storing database credentials in AWS Secrets Manager
Enabling RDS encryption
Enabling automated backups
Adding CloudWatch logs and alarms
Using an Application Load Balancer instead of direct EC2 access
Moving the EC2 instance to a private subnet and exposing only the ALB

Cleanup

After completing the validation, I destroyed the resources to avoid unnecessary AWS charges.

terraform destroy


Key Learnings

This project helped me understand how EC2 and RDS communicate securely inside a VPC. The biggest takeaway was that RDS should not be publicly exposed, and access should be controlled using security group references instead of open CIDR ranges. I also learned how Terraform modules make infrastructure easier to organize, reuse, and troubleshoot. This was a good step toward building production-style AWS application stacks.

Video Reference


Jay

Comments

Popular posts from this blog

ASM Integrity check failed with PRCT-1225 and PRCT-1011 errors while creating database using DBCA on Exadata 3 node RAC

Life is beautiful

Lock Tables in MariaDB