Skip to main content

Command Palette

Search for a command to run...

Day 22: Two-Tier Flask Application with Terraform on AWS

Published
5 min read
Day 22: Two-Tier Flask Application with Terraform on AWS

When deploying modern web applications, infrastructure as code (IaC) has become the gold standard. In this post, I'll walk you through architecting and deploying a two-tier Flask application with MySQL on AWS using Terraform. This setup ensures scalability, security, and maintainability - all while keeping sensitive data protected.

Refer Complete Notes: Link for Notes
Refer Complete Code: Link for Code

The Architecture: Why Two-Tier?

A two-tier architecture separates concerns between the presentation layer (web server) and data layer (database). Our Flask application runs on EC2 instances while MySQL operates on Amazon RDS. This separation offers several advantages:

  • Scalability: Web and database tiers can scale independently

  • Security: Database resides in private subnets, isolated from public traffic

  • Maintainability: Updates and patches can be applied to each tier separately

  • High Availability: Multi-AZ deployment ensures database resilience

Securing Secrets with AWS Secrets Manager

One of the biggest challenges in infrastructure deployment is managing secrets securely. Hardcoding database passwords in Terraform files is a security anti-pattern. Here's our solution:

resource "random_password" "db_password" {
  length           = 16
  special          = true
  override_special = "!#$%&*()-_=+[]{}<>:?"
}

resource "aws_secretsmanager_secret_version" "db_password" {
  secret_id = aws_secretsmanager_secret.db_password.id
  secret_string = jsonencode({
    username = "admin"
    password = random_password.db_password.result
    engine   = "mysql"
    host     = aws_db_instance.main.endpoint
  })
}

This approach generates a strong, random password and stores it securely in AWS Secrets Manager. The application retrieves credentials at runtime, eliminating hardcoded secrets from your infrastructure code.

Network Isolation: The VPC Foundation

Proper network design is crucial for security. We create a VPC with clear separation between public and private subnets:

# Public subnet for web servers
resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "${var.aws_region}a"
  map_public_ip_on_launch = true
}

# Private subnets for RDS (different AZs for HA)
resource "aws_subnet" "private_1" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "${var.aws_region}a"
}

resource "aws_subnet" "private_2" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.3.0/24"
  availability_zone = "${var.aws_region}b"
}

The web servers live in public subnets with internet access, while the database resides in private subnets, inaccessible directly from the internet. This layered security approach significantly reduces the attack surface.

Security Groups: Granular Access Control

Security groups act as virtual firewalls. We create separate security groups for our web and database tiers:

# Web security group - allows HTTP/HTTPS and SSH
resource "aws_security_group" "web" {
  name        = "${var.project_name}-web-sg"
  description = "Allow web traffic"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # Restrict to your IP in production
  }
}

# Database security group - only allows web tier access
resource "aws_security_group" "database" {
  name        = "${var.project_name}-db-sg"
  description = "Allow database access from web tier"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.web.id]
  }
}

Notice how the database security group only allows traffic from the web security group on port 3306. This principle of least privilege is essential for securing your database.

Managed Database Service: RDS Configuration

Amazon RDS provides a managed MySQL service that handles patching, backups, and failover. Here's our Terraform configuration:

resource "aws_db_instance" "main" {
  identifier             = "${var.project_name}-db"
  allocated_storage      = 20
  storage_type           = "gp2"
  engine                 = "mysql"
  engine_version         = "8.0"
  instance_class         = "db.t3.micro"
  db_name                = "flaskapp"
  username               = var.db_username
  password               = random_password.db_password.result
  skip_final_snapshot    = true
  vpc_security_group_ids = [aws_security_group.database.id]
  db_subnet_group_name   = aws_db_subnet_group.main.name
  publicly_accessible    = false
}

Key security features:

  • publicly_accessible = false ensures no direct internet access

  • Database uses the private subnet group

  • Security group restricts access to web tier only

Application Deployment: EC2 with User Data

The final piece is our web server deployment. We use EC2 user data to bootstrap the Flask application:

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]

  user_data = templatefile("${path.module}/user_data.sh", {
    db_host     = aws_db_instance.main.endpoint
    db_username = var.db_username
    db_password = random_password.db_password.result
    db_name     = "flaskapp"
  })
}

The user_data.sh script installs Python, Flask, MySQL client, and configures the application to connect to our RDS instance.

Deployment Workflow

With all components defined, deployment is straightforward:

terraform init      # Initialize Terraform and providers
terraform plan     # Review changes
terraform apply    # Deploy infrastructure

Within minutes, you have a fully functional, secure two-tier application. The web server's public DNS output provides the URL to access your Flask application.

Best Practices Implemented

  1. Infrastructure as Code: The entire environment is version-controlled and reproducible

  2. Secret Management: No hardcoded credentials; AWS Secrets Manager handles sensitive data

  3. Network Security: Proper VPC design with public/private subnet segregation

  4. Least Privilege: Security groups restrict access to only necessary ports

  5. High Availability: Multi-AZ RDS deployment ensures database resilience

  6. Cost Optimization: Using t3.micro instances for both tiers in development

Video

Conclusion

This Terraform-based approach to deploying a two-tier Flask application demonstrates modern cloud infrastructure practices. By leveraging AWS services and Terraform's declarative syntax, we've created a secure, scalable foundation that can be extended for production use. The complete separation of concerns, secure credential management, and network isolation provide a robust platform for your Flask applications to thrive in the cloud.

Remember to always customize security settings (especially SSH access) for your specific needs and implement proper monitoring and logging before moving to production. Happy deploying!

More from this blog

Terraform with AWS

27 posts