Deploying a Flask Web App on an AWS EC2 Instance using Docker and Terraform

nadaahmed

Nada Ahmed

Posted on November 9, 2024

Deploying a Flask Web App on an AWS EC2 Instance using Docker and Terraform

This project combines Terraform for infrastructure automation and Docker for containerizing a simple Flask web application. You’ll create a basic Flask application, containerize it using Docker, and then use Terraform to provision an AWS EC2 instance, deploy the Docker container, and expose the Flask application over the web.


Project Overview

  • Flask Application: A simple Flask web app.
  • Docker: Containerize the app so it runs consistently on any machine.
  • Terraform: Provision AWS resources to host the Dockerized application on an EC2 instance.

Steps to Set Up the Project

1. Create a Simple Flask Application with Docker

  1. Create a New Directory: This will be your project folder.

  2. Create the Flask Application (app.py):
    In your project folder, create a file called app.py with the following content:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return "Hello, world from Flask in Docker on AWS!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
Enter fullscreen mode Exit fullscreen mode

This Flask application simply returns a "Hello World" message when you access the root URL.

  1. Create a Dockerfile: In the same directory, create a Dockerfile to containerize the Flask application.
# Use the official Python image
FROM python:3.9-slim

# Set the working directory
WORKDIR /app

# Copy the application files
COPY app.py /app

# Install Flask
RUN pip install Flask

# Run the application
CMD ["python", "app.py"]
Enter fullscreen mode Exit fullscreen mode

The Dockerfile defines the environment for your Flask app and installs Flask inside the container.


2. Build and Test the Docker Image Locally

To ensure the app works locally, build and run the Docker image:

docker build -t flask-app .
docker run -p 5000:5000 flask-app
Enter fullscreen mode Exit fullscreen mode

Image description

Now, open a browser and visit http://localhost:5000 to check if the Flask app is running locally.

Image description


3. Terraform configuration

Terraform configuration, including resource.tf, ec2.tf, main.tf, backend.tf, output.tf, and variables.tf, would look for deploying a Dockerized Flask app on an EC2 instance:

main.tf (Provider Configuration)

provider "aws" {
  region     = "us-west-2"
  access_key = "xxxxxxxxxxxxxxxxxxxxxx"
  secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
Enter fullscreen mode Exit fullscreen mode

resource.tf (Network, VPC, Security Group, and Internet Gateway)

# VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "Project terraform"
  }
}

# Internet Gateway
resource "aws_internet_gateway" "gw" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "MainInternetGateway"
  }
}

# Route Table for Public Subnet
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.gw.id
  }

  tags = {
    Name = "PublicRouteTable"
  }
}

# Subnet
resource "aws_subnet" "demosubnet" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.subnet_cidr
  availability_zone       = "us-west-2a"
  map_public_ip_on_launch = true

  tags = {
    Name = "Public Subnet"
  }
}

# Associate the Route Table with the Public Subnet
resource "aws_route_table_association" "public_subnet" {
  subnet_id      = aws_subnet.demosubnet.id
  route_table_id = aws_route_table.public.id
}

# Security Group
resource "aws_security_group" "allow_http" {
  name        = "allow_http_${random_id.unique_id.hex}"  # Use a random ID to make the name unique
  description = "Allow HTTP traffic"
  vpc_id      = aws_vpc.main.id

  # Ingress rule to allow HTTP traffic
  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"]
  }

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

  # Egress rule to allow all outbound traffic
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "AllowHTTP"
  }
}

resource "random_id" "unique_id" {
  byte_length = 4
}
Enter fullscreen mode Exit fullscreen mode

ec2.tf (EC2 Configuration to Run Flask App)

# EC2 Instance
resource "aws_instance" "flask_ec2" {
  ami           = var.ami_id
  instance_type = var.instance_type
  vpc_security_group_ids = [aws_security_group.allow_http.id]  # Dynamically use created security group ID
  subnet_id     = var.public_subnet_id
  associate_public_ip_address = true

  user_data = <<-EOF
              #!/bin/bash
              # Update the system
              yum update -y

              # Install Docker manually (alternative to amazon-linux-extras)
              yum install -y docker

              # Start Docker service
              service docker start

              # Enable Docker service on boot
              service docker enable

              # Add ec2-user to Docker group
              usermod -a -G docker ec2-user

              # Run the Flask app container
              docker run -d -p 5000:5000 nadasaad/flask-app:latest
            EOF

  tags = {
    Name = "FlaskDockerEC2"
  }

  depends_on = [aws_security_group.allow_http]  # Ensure security group is created first
}
Enter fullscreen mode Exit fullscreen mode

backend.tf (Backend Configuration for Terraform State)

terraform {
  backend "s3" {
    bucket         = "mybucket09a"
    key            = "terraform.tfstate"
    region         = "us-west-2"
    dynamodb_table = "lockstate"
  }
}
Enter fullscreen mode Exit fullscreen mode

output.tf (Outputs for Public IP)

output "public_ip" {
  value = aws_instance.flask_ec2.public_ip
}
Enter fullscreen mode Exit fullscreen mode

variables.tf (Variable Definitions)

variable "ami_id" {
  description = "The AMI ID to use for the EC2 instance"
  default     = "ami-066a7fbea5161f451"  # Replace with the correct AMI ID for your region
}

variable "instance_type" {
  description = "The type of instance to use for the EC2 instance"
  default     = "t2.micro"  # Modify as needed
}

variable "subnet_cidr" {
  description = "The CIDR block for the subnet"
  default     = "10.0.1.0/24"  # Modify as needed
}

variable "security_group_id" {
  description = "The security group ID to associate with EC2 instance"
  default     = "sg-06e61ad57b5d3d5d0"  # Modify with your security group ID
}

variable "public_subnet_id" {
  description = "The subnet ID where EC2 will be created"
  default     = "subnet-0f84f2821d4f682bb"  # Modify with your subnet ID
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Security Group: Allows inbound traffic on port 5000 for HTTP and port 22 for SSH.
  • EC2 Instance: Launches an EC2 instance with Docker installed using a user-data script that also runs the Flask app in a Docker container.

Replace:

  • your_vpc_id with your actual VPC ID.
  • your_key_pair_name with your EC2 key pair name.
  • your_dockerhub_username with your Docker Hub username.

4. Push the Docker Image to Docker Hub

Before Terraform can deploy the Flask app to the EC2 instance, you need to push the Docker image to a registry (such as Docker Hub).

  1. Tag the Docker Image:
   docker tag flask-app your_dockerhub_username/flask-app:latest
Enter fullscreen mode Exit fullscreen mode
  1. Push the Image to Docker Hub:
   docker push your_dockerhub_username/flask-app:latest
Enter fullscreen mode Exit fullscreen mode

Image description

Make sure your Docker Hub account is logged in using docker login.


5. Deploy the Project with Terraform

Now, let’s deploy the resources with Terraform:

  1. Initialize the Terraform Configuration:
   terraform init
Enter fullscreen mode Exit fullscreen mode
  1. Apply the Terraform Configuration:
   terraform apply
Enter fullscreen mode Exit fullscreen mode

Image description

Terraform will prompt you for confirmation before provisioning the infrastructure. Type yes to proceed. It will then create an EC2 instance, security group, and other necessary resources.

Image description

Image description

Image description


6. Access Your Flask Application

Once the Terraform apply is successful, it will output the public IP of the EC2 instance.

  1. Obtain the EC2 Public IP: You can find it in the Terraform output or AWS Management Console.
  2. Access the Flask App: Open a web browser and go to:
   http://<EC2_PUBLIC_IP>:5000
Enter fullscreen mode Exit fullscreen mode

Replace <EC2_PUBLIC_IP> with the actual public IP of your EC2 instance. You should see the message "Hello, world from Flask in Docker on AWS!" displayed in the browser.

Image description

Conclusion

In this project, we successfully:

  • Created a simple Flask web application.
  • Containerized the application with Docker.
  • Used Terraform to provision AWS infrastructure and deploy the Dockerized app to an EC2 instance.
  • Ensured the app was accessible via a public IP.

By combining Docker for containerization and Terraform for infrastructure automation, we can deploy applications consistently and efficiently in the cloud.
This project can be expanded further by adding more complex applications, integrating databases, and setting up monitoring solutions to make it a fully-featured production environment.

💖 💪 🙅 🚩
nadaahmed
Nada Ahmed

Posted on November 9, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related