Deploying a two-tier app on AWS

Daksh SawhneyDaksh Sawhney
6 min read

In this blog, I’ll walk you through how I deployed a real-world two-tier application (frontend and backend) on AWS EC2, using foundational DevOps tools and AWS services like VPC, Security Groups, EC2 instances, and IAM.

This project gave me hands-on experience with how cloud infrastructure is designed and deployed in a scalable, secure, and modular way—just like in production environments.

Whether you’re a student like me or a beginner DevOps enthusiast, this guide will help you understand:

✅ Designing a two-tier architecture for the cloud
✅ Setting up secure networking using VPC and subnets
✅ Deploying and connecting frontend & backend services
✅ Configuring security groups, SSH access, and traffic flow
✅ Using key AWS services like EC2 and IAM effectively

Let’s dive in and bring this app to life on the cloud. ☁️🛠️


Creating Dockerfile for Python App

FROM python:3.10 AS builder

WORKDIR /app

COPY requirements.txt .
RUN pip install mysqlclient
RUN pip install -r requirements.txt

COPY . .

# Stage 2
FROM python:3.10-slim

WORKDIR /app

COPY --from=builder /app .

RUN apt-get update && apt-get install -y \
    build-essential \
    default-libmysqlclient-dev \
    python3-dev \
    && rm -rf /var/lib/apt/lists/*

EXPOSE 5000

CMD ["python", "app.py"]

Running MySql container as without it, python won’t run

docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD="root" mysql:latest


Now, this Python app and MySQL are unable to connect with each other.

So we need to connect them by keeping them in the same network.

Creating a network

docker network create two-tier

Kill both containers and provide both with network


If you see in app.py

So we need to include it in the command of building container of flask app, as it requires this

Creating flask container

docker run -d -p 5000:5000 --network=two-tier -e MYSQL_HOST=mysql -e MYSQL_USER=admin -e MYSQL_PASSWORD=admin -e MYSQL_DB=myDb two-tier-app:latest

Creating MySql container

docker run -d -p 3306:3306 --network=two-tier -e MYSQL_DATABASE=myDb -e MYSQL_USER=admin -e MYSQL_PASSWORD=admin -e MYSQL_ROOT_PASSWORD=admin mysql:latest

Inspect network - which apps are there in it

docker network inspect two-tier

Customizing Names

kill both container again

docker run -d -p 5000:5000 --network=two-tier -e MYSQL_HOST=mysql -e MYSQL_USER=admin -e MYSQL_PASSWORD=admin -e MYSQL_DB=myDb --name two-tier-app two-tier-app:latest
docker run -d -p 3306:3306 --network=two-tier -e MYSQL_DATABASE=myDb -e MYSQL_USER=admin -e MYSQL_PASSWORD=admin -e MYSQL_ROOT_PASSWORD=admin --name my-sql mysql:latest


Now running this command inside mySQL for creation of table

Now inside this, write this command

CREATE TABLE messages (
    id INT AUTO_INCREMENT PRIMARY KEY,
    message TEXT
);


AND WOHOOO 🎉🎉🎉

App is available to use on port 5000 and data is going into the database


Pushing Docker image to Docker Hub

Do Docker login and enter docker hub credentials

Then tag the image

docker tag two-tier-app:latest dakshsawhneyy/two-tier-app:latest

And then push the image to docker hub

docker push dakshsawhneyy/two-tier-app:latest

Making Docker Compose for both the containers

version: '3.9'

services:
  flask:
    container_name: two-tier-app
    image: dakshsawhneyy/two-tier-app
    ports:
      - "5000:5000"
    # Add env variables from app.py
    environment:
      - MYSQL_HOST: "mysql"
      - MYSQL_USER: "root"
      - MYSQL_PASSWORD: "admin"
      - MYSQL_DB: "myDb"
    depends_on:
      - mysql

  mysql:
    container_name: mysql
    image: mysql:latest
    ports:
      - "3306:3306"
    environment:
      - MYSQL_DATABASE: "myDb"
      - MYSQL_USER: "admin"
      - MYSQL_PASSWORD: "admin"
      - MYSQL_ROOT_PASSWORD: "admin"

As we were facing problem of tables, so we need to create volume here

Add this in mysql

volumes:
      - ./message.sql:/docker-entrypoint-initdb.d/message.sql # table copy 
      - mysql-data:/var/lib/mysql    # Data doesnt gets lost from the container

Final Code

version: '3.9'

services:
  flask:
    container_name: two-tier-app
    image: dakshsawhneyy/two-tier-app
    ports:
      - "5000:5000"
    # Add env variables from app.py
    environment:
      - MYSQL_HOST=mysql
      - MYSQL_USER=root
      - MYSQL_PASSWORD=admin
      - MYSQL_DB=myDb
    depends_on:
      - mysql

  mysql:
    container_name: mysql
    image: mysql:latest
    ports:
      - "3306:3306"
    environment:
      - MYSQL_DATABASE=myDb
      - MYSQL_USER=admin
      - MYSQL_PASSWORD=admin
      - MYSQL_ROOT_PASSWORD=admin
    volumes:
      - ./message.sql:/docker-entrypoint-initdb.d/message.sql
      - mysql-data:/var/lib/mysql

volumes:
  mysql-data:

And both containers are running using Docker-Compose 🎉🎉🎉


Kubernetes Architecture and Cluster Setup (Kubeadm)

Create two instances—t2.medium, i.e., one for master and one for worker node

And Install by using commands


Creating Manifest Files

Making Deployment for Flask App

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-deployment
  labels:
    app: flask
spec:
  replicas: 4
  selector:
    matchLabels:
      app: flask
  template:
    metadata:
      labels:
        app: flask
    spec:
      containers:
      - name: flask-app
        image: dakshsawhneyy/two-tier-app
        ports:
        - containerPort: 5000
        env:
          - name: MYSQL_HOST
            value: "mysql"    # put mysql service ip address inside it
          - name: MYSQL_USER
            value: "root"
          - name: MYSQL_PASSWORD
            value: "admin"
          - name: MYSQL_DB
            value: "myDb"

If you want to scale deployment, use code

kubectl scale deployment flask-deployment --replicas=2

MySQL Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql-cnt
        image: mysql:latest
        ports:
        - containerPort: 3306
        env:
          - name: MYSQL_ROOT_PASSWORD
            value: "admin"
          - name: MYSQL_USER
            value: "root"
          - name: MYSQL_PASSWORD
            value: "admin"
          - name: MYSQL_DATABASE
            value: "myDb"

Pod is crashing. We need to create pv and pvc

Create pv and, and app will run fine


Deploying two-tier-app using HELM

# Install HELM
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm

Creating Nginx Pod using Helm

helm create nginx-chart

You can change anything in values. yml and charts gets updated by themselves


Let’s start deploying two-tier-app using HELM

Now modifying values

Now pasting environment vars of mysql into templates/deployment.yml

Inside template. yml, change these so we don’t need to hardcode the value

Then package Helm Chart

Now install chart


Error Solving

We havent provided 3306 inside security group inbound rules so we need to push 3306 inside inbound rules of master instance

Now use

helm list # verify which helm charts are present and delete the error one
helm uninstall mysql-chart

Also by mistake wrote image instead of env

Change username to admin from root

root is already built user and we say make a user root; it cannot make user of another name
so use username as “admin.”

Then Again install it

helm install mysql-chart ./mysql-chart

At least it’s running 😂. Now lets fix why it’s not working

And its's running🎉🎉🎉
Just needed to comment out the liveness probe inside values. yml


Creating flask-app

helm create flask-app-chart

Just like MySQL, copy env into values. yml and then extract them into templates. yml

Then go inside flask-app-chart/templates/service.yaml

And now package helm and install helm


Now going inside mysql to create a database

kubectl exec -it mysql-chart-7644667ccb-5vcd4 -- /bin/bash

And create database—naming it “myDb“ in it

And also inside mysql and database — for table

CREATE TABLE messages (
    id INT AUTO_INCREMENT PRIMARY KEY,
    message TEXT
);

And paste worker url on browser with port 300001

And our site is running 🎉🎉🎉


And to uninstall all in one command

# just run this command
helm uninstall flask-app-chart mysql-chart

And all will be deleted


Deploying on EKS-Cluster

Make an EC2 “t2.micro” instance

0
Subscribe to my newsletter

Read articles from Daksh Sawhney directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Daksh Sawhney
Daksh Sawhney

Aspiring DevOps & DevSecOps Engineer | Automating Infra with Terraform & Ansible | Kubernetes Enthusiast | Building Scalable Pipelines 🤷‍♂️