Deploying a Flask Web App on an AWS EC2 Instance using Docker and Terraform
Nada Ahmed
Posted on November 9, 2024
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
Create a New Directory: This will be your project folder.
Create the Flask Application (
app.py
):
In your project folder, create a file calledapp.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)
This Flask application simply returns a "Hello World" message when you access the root URL.
-
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"]
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
Now, open a browser and visit http://localhost:5000
to check if the Flask app is running locally.
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"
}
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
}
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
}
backend.tf (Backend Configuration for Terraform State)
terraform {
backend "s3" {
bucket = "mybucket09a"
key = "terraform.tfstate"
region = "us-west-2"
dynamodb_table = "lockstate"
}
}
output.tf (Outputs for Public IP)
output "public_ip" {
value = aws_instance.flask_ec2.public_ip
}
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
}
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).
- Tag the Docker Image:
docker tag flask-app your_dockerhub_username/flask-app:latest
- Push the Image to Docker Hub:
docker push your_dockerhub_username/flask-app:latest
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:
- Initialize the Terraform Configuration:
terraform init
- Apply the Terraform Configuration:
terraform apply
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.
6. Access Your Flask Application
Once the Terraform apply is successful, it will output the public IP of the EC2 instance.
- Obtain the EC2 Public IP: You can find it in the Terraform output or AWS Management Console.
- Access the Flask App: Open a web browser and go to:
http://<EC2_PUBLIC_IP>:5000
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.
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.
Posted on November 9, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.