DEV Community

Cover image for A Practical Guide To Deploying A Complex, Production Level, Three-tier Architecture On AWS
Kelvin Onuchukwu
Kelvin Onuchukwu

Posted on • Updated on

A Practical Guide To Deploying A Complex, Production Level, Three-tier Architecture On AWS

Multi-tiered architectures have become the most popular ways of designing and building applications in the cloud. This is primarily because they offer high availability, replication, flexibility, security and many other numerous benefits. This is as opposed to single-tier architectures which typically involves packaging all the requisite components of a software application into a single server.

The most popular multi-tier design pattern is a three-tier architecture. The three-tier architecture consists of the following layers:

  • Presentation Layer: This is the outermost layer of the application and provides an interface for interacting with the user. It also provides a secure communication channel with the other tiers.
  • Logic Layer: This is where information is processed and application logic is executed. It processes input from the presentation layers, processes it and also communicates with the data layer when necessary. it is also known as the application layer or middleware.
  • Data Layer: This is where the database management system sits, thus providing it with a secure, isolated environment for storing and managing application information.

In this guide, we are going to design a very fault-tolerant, highly scalable Flask application on AWS using the three-tier architecture design pattern.

Architectural Design

The Presentation layer will comprise of; Cloudfront, Elastic Load Balancer, Internet Gateway, NAT Gateway and two Bastion Hosts.
The Application (Logic) Layer will consist of EC2 instances based on EBS volumes, provisioned through an Auto Scaling group.
The Data Layer will be made up of a PostgresQL Database with a Read Replica. The EC2 instances provisioned in the application layer will be connected to an Elastic Filesystem for data storage. So technically, the EFS is also residing in this layer.


  • AWS Route 53 will be used to provide a domain name for the application.
  • We will integrate ClodFront with the Application Load Balancer to provide worldwide accelerated content delivery and reduce latency.
  • Terraform will be used as the Infrastructure-as-code (IAC) tool to automate this whole process from end to end.

To automatically create this architecture from end to end, click on this Link to get the Terraform code.

This is a first-part of a three series project.
In the second project, we will implement DevOps on AWS via CodePipeline, CodeBuild and CodeDeploy.
The third project introduces a Reporting Layer into the architecture.
This part focuses soley on designing and deploying three-tier architecture for a Flask application on AWS.

Let's begin. Shall we?
Image description

Step 1: Create The Environment.

Firstly we will create a VPC. A virtual private cloud is a secure, isolated, networking environment hosted in a public Cloud. A VPC is more or less a virtual data center in the cloud. This is where most of our application resources will be hosted.

AWS VPC environment
My VPC has a name tag of project-x. This means that all other resources created within this VPC will have the prefix of "project-x".
You can also notice that I am assigning a CIDR block of to my VPC. This is a block of private IP addresses from which my VPC resources will get their local addresses.

AWS VPC Console
Notice that I am in the us-east-1 region (North Virginia).
Our VPC will be provisioned in three availability zones. 6 subnets will be created in total. Three public and three private ones. The public subnets are public soley because an internet gateway will be attached to them.
Also notice that I am creating a NAT gateway. This NAT gateway will be used by the EC2 instances hosting our application to connect to the internet (E.g: For Updates).

Step 2: Create A Security Group

A security group acts like a firewall and determines how traffic will enter or exit instances.
Since our web servers will be hosted in the logic-tier, it is important that we restrict the type of traffic entering them.

The name of our security group will be "project-x-logic-tier-sg"
The security group must allow inbound traffic on port 5000 since that is the port on which Flask listens on.

Creating a security group

Step 3: Create A Launch Template

A Launch Template is a way of specifying important configuration details such that we can then launch instances using the details in the template. This template will be used by an auto scaling group to create instances in our Application tier.

AWS EC2 Management Console
I assigned a name and description to my template. Notice also that i duly tagged the environment as "prod".

Ec2 Console
I am using a "t3.xlarge" instance type.
Be sure to select the "project-x-logic-tier-sg" security group.
Leave every other detting as is.
Under "Advanced details", I am going to paste in the following under user data:


# Mount EFS # You must change this value to represent your EFS DNS name.
mkdir /efs
mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport $fsname:/ /efs

# install and set up Flask
apt update
apt upgrade -y
apt install python3-flask mysql-client mysql-server python3-pip python3-venv -y
apt install sox ffmpeg libcairo2 libcairo2-dev -y
apt install python3-dev default-libmysqlclient-dev build-essential -y

Enter fullscreen mode Exit fullscreen mode

Step 4: Create an Autoscaling Group, Elastic Load balancer And Target Group

The Load balancer is the entry point to the application.
The Application Load Balancer, residing in the presentation layer, will route traffic through the AutoScaling Group to logic-tier instances residing in the logic layer.

EC2 Management Console

Creating an autoscaling group
I am naming this Autoscaling group "project-x-asg"
Click on next. Select the "project-x-vpc" we created earlier. Also make sure that you select only the private subnets from the three availability zones. This is very crucial since our VPC will be launched in the logic tier. The subnets must not be public.

Creating an ASG

Click on next.
Under "Configure advanced options", select "Attach to a new load balancer". Also select "Create a target group".
On the next page, we'll configure our ASG to have a minium capacity of 2, a desired capacity of 4 and a maximum capacity of 6. We'll also set up scaling based on target tracking metrics to scale based on average CPU Utilization.

Create an ASG
You can decide to add tags. I'll add a tag name of "Environment" with a value of "Prod". this will be needed during the CodeDeploy stage.

creating an ASG

Create your ASG.

Step 5: Attach An Elastic Filesystem

AWS EFS is a fully managed, highly scalable shared storage solution in the cloud. It is NFS compatible.
This Elastic filesystem will provide shared storage for all our application tier servers. Since it provides storage, EFS sits in the data layer of the three tier architecture.

First Create a new security group. This security group should allow only inbound NFS traffic from the security group of our logic-tier instances. You can get a detailed guide on how to create that Here
Go to the EFS console. Click on "
Create filesystem*" and then click on **customize*.

EFS Console

Assign a name to your EFS. Leave every other setting as default. Click on Next.

EFS Console
On the Network access page, select your propject-x VPC. Select the EFS security group you have created. Click on next.

Under Filesystem Policy, leave everything as default.
EFS Console

Skip to Review and then Create your filesystem.
Now we must update our user data to look like this:


# Use Google's DNS
echo "nameserver" >> /etc/resolv.conf

# Force apt to use IPV4
apt-get -o Acquire::ForceIPv4=true update

# Change hostname
echo "project-x-app-server" > /etc/hostname

# Install efs-utils
apt-get install awscli -y
mkdir /efs
sudo apt-get -y install git binutils
git clone
cd /efs-utils
apt-get -y install ./build/amazon-efs-utils*deb

# Mount EFS
fsname=$(aws efs describe-file-systems --region us-east-1 --creation-token project-x --output table |grep FileSystemId |awk '{print $(NF-1)}')
mount -t efs $fsname /efs

# Get DB credentials
DB=$(aws rds describe-db-instances --db-instance-identifier --region us-east-1 database-1 --output table |grep DBName |awk '{print $(NF-1)}')
HOST=$( aws rds describe-db-instances --db-instance-identifier --region us-east-1 database-1 --output table |grep Address |awk '{print $(NF-1)}')
ARN=$(aws secretsmanager list-secrets --region us-east-1 --filters "Key=tag-value, Values=project-x-rds-mysqldb-instance" --output table |grep ARN |awk '{print $(NF-1)}')
USER=$(aws secretsmanager get-secret-value --region us-east-1 --secret-id $ARN --output table |grep -w SecretString |awk '{print $3}' |cut -d: -f2 |sed 's/password//' |tr -d '",')
PRE_PASSWORD=$(aws secretsmanager get-secret-value --region us-east-1 --secret-id $ARN --output table |grep -w SecretString |awk '{print $3}' |cut -d: -f3 |tr -d '"')

# install and set up Flask
apt-get update -y && apt-get upgrade -y 
apt-get install python3-flask mysql-client mysql-server python3-pip python3-venv -y 
apt-get install sox ffmpeg libcairo2 libcairo2-dev -y 
apt-get install python3-dev default-libmysqlclient-dev build-essential -y 

# Clone the app
cd /
git clone
cd /terra-tier

# Populate App with environmental variables
cd /terra-tier/application
echo "MYSQL_DB=$DB" > .env
echo "MYSQL_HOST=$HOST" >> .env
echo "MYSQL_USER=$USER" >> .env
echo "SECRET_KEY=08dae760c2488d8a0dca1bfb" >> .env # FLASK EXTENSION KEY. NOT NECESSARILY A "SECRET".
echo "API_KEY=f39307bb61fb31ea2c458479762b9acc" >> .env 

# Setup virtual environment
cd /terra-tier
python3 -m venv venv
source venv/bin/activate

# Run Flask Application
pip install -r requirements.txt
export FLASK_ENV=production
flask run -h
Enter fullscreen mode Exit fullscreen mode

Here is where you find your filesystem's DNS name.
AWS EFs Console
Make sure to change the value of the "fsname" variable to represent your own filesystem's DNS name.

Step 6: Create A Bastion Host

A Bastion Host is a special server used to manage access to servers sitting in an internal network or other private AWS resources from an external network. The bastion host sits in the public network and provides limited access to administrators to log in to servers sitting in an isolated network. It is also commonly referred to as a Jump Box or Jump Server.

From the bastion host, we'll be able to gain SSH access into our application layer servers, for administrative purposes.

Here is a good resource on how to create your bastion host and connect to your logic layer servers through it.
Security Caution: Connecting to bastion hosts using ssh key-pairs is no longer the recommended practice. Instead use AWS Systems Manager for a more secure connection and tunneling through the bastion host to your private instances.

Step 7: Create A Database

The database solidly sits in the data layer, together with the Elastic filesystem.
Our Flask application will need to connect to a relational database. We will be using MYSQL for this architecure.
MYSQL is a widely relational database management system (DBMS) which is free and open source. MYSQL is renowned for its ability to support large production workloads, hence its suitability for our project.

We will however be utilizing Amazon RDS for MYSQL. It is a fully managed database solution in the cloud.

On the RDS Console, click on Databases and then clcik on "Create database".

RDS Console
I am selecting a Production template and choosing Multi-AZ DB-instance.
AWS RDS Console

Select your master username and DB Instance Class. We'll be using AWS SEcrets manager for managing our DB credentials.

Creating RDS database

Under "Connectivity", select the project-x-vpc. Click on "Create a new security group".

Create a security group that only allows incoming traffic on port 3306 from security group of the application layer servers. Select the security group.

Under "Database Authentication", choose "password".

Go down to "Advanced Options", Under "database name", choose "newsreadb". (You must choose this exact name for the Flask App to be able to connect to the database).

Click on Create database.

Now our architecture is almost ready !!!
AWS 3-tier Architecture

To connect to the database, our application will execute API calls to AWS Secrets Manager in order to get the DB credentials. Therefore, we must create an IAM role that allows it to perform that. We will also modify the launch template to attach the role as instance profile for our application layer servers.

Step 8: Create An IAM Role And Modify Launch Template

Go to the IAM Dashboard and click on Roles.
Click on Create Role. Under UseCase, select EC2 and click Next.
Search for secrets and select the permission that pops up.

AWS IAM Dashboard

Click on next and click on Create Role.

Next, we'll need to modify our launch template to include this role so that our logic tier servers can assume it to communicate with AWS Secrets Manager.

Go to the EC2 Console, select launch templates, Actions and click on "Modify launch template"

Modify launch template

Go to "Advanced details", Click on the box under IAM instance profile and then select your newly created role. Click on Create.
Launch template

Finally, be sure to set the new version as the default version.

Launch template

AND YOU're DONE !!!!!!!
Our three-tier application should now be up and running.

Step 9: Accessing Your Application

If you have followed through with all these steps, Congrats.
Now to access your application, you have to visit the EC2 console to get the domain name of your load balancer.

Go to the EC2 Console and click on Load Balancers.

EC@ Load balancer

Copy the DNS Name

AWS EC2 Console: Load balancer

Now paste this into a web browser.

Flask app running on AWS

Flask app running on AWS using 3-tier architecture

Flask Appplication running on AWs using 3-tier architecture

That's it. If you made it this far, Congratulations.

Now Onto The Bonus Section.

Firstly,here terraform code that automates this process from end-to-end.

Now we will provide a domain name for our application using AWS Route53. Finally, we will integrate our Application Load Balancer with CloudFront for accelerated content delivery to users. This is a necessary step if you were to run this architecture in a production environment.

Step 1: Create A Domain Name

An active domain name is required to follow through with the remaining part of this project.
You can either purchase a domain directly from Route53 or you can use other providers such as GoDaddy or Namecheap.

Step 2: Create A Hosted Zone
A hosted zone is a container for records, which define how you want to route traffic to your domain.
To create a public hosted zone, go to the Route53 Console, Click on Hosted Zones and populate the fields with your values.

AWS Hosted Zone

Step 3: Create An SSL Certificate
An SSL/TLS certificate is a digital object that allows systems to verify the identity & subsequently establish an encrypted network connection to another system using the Secure Sockets Layer/Transport Layer Security (SSL/TLS) protocol.

AWS Certificate Manager is a service that provisons and manages public and private SSL/TLS certificates.

Go to the Certificate Manager Console and click on "Request Certificate".
Enter your fully qualified domain name and click on Request. It normally takes some time for AWS to approve this request, especially if you purchased a domain name outside of AWS. So you have to exercise a bit of patience here.

AWS Certificate Manager Console
You will need this certificate while provisioning your CloudFront distribution In a production environment.
However, I won't be using HTTPS here due to logistical reasons. So this step isn't necessary to follow through with the project.

Step 4: Create A CloudFront Distribution And Integrate With Your ALB
AWS CloudFront is a Contnet Delivery Network that provides your data globally to your users with very low latency. It does this by routing users request through the nearest edge location.
We'll integrate CloudFront with our ALB for accelerated dynamic contnet delivery.
N.B: In a production environment, you'd want to implement SSL termination on the ALB. But since we're not using SSL Certificates in this project, you won't need to do this to follow through.

Go to the CloudFront Console, Click on "Create Distribution".
Select your Application Load Balancer as the Origin Domain.
AWS CloudFront: Create distribution

Choose HTTP Only. Leave other fields as default.
AWS CloudFront Console

Select "CachingDisabled" as your caching policy. Origin request policy should be AllViewer.

Click on "Create Distribution"

Step 5 (Final Step): Copy The Distribution Domain Name And Paste In A Web Browser

CloudFront Console

Aaaaaand... Here we go!!!!
Flask Application On AWS

Wrap Up

Three-tier architectures are a well established pattern of software application deployment. They make the application architecture fully resilient, fault tolerant and well secure.

After going through this project, you should be well equipped to design and deploy similar architectures on AWS.
Remember to check out the second part of this project series which focuses on CI/CD.

Happy Clouding!!!

Top comments (13)

sohamds profile image
Soham De Sarkar

Thanks for this in-depth article on deploying a complex production-level application. Being a beginner to cloud, it helps people like me to bolster the learning from articles like this.

brandondamue profile image
Brandon Damue

Great article! Deploying a complex production-level application on AWS can be a daunting task, but your article breaks it down into clear and actionable steps. I particularly appreciate how you discuss the various AWS services used and their specific roles in the deployment process. Your insights and best practices provide valuable guidance for anyone embarking on a similar journey. Keep up the excellent work!

kelvinskell profile image
Kelvin Onuchukwu

Thank you.

huncyrus profile image

Nice walkthrough! Do you have any cost-effective suggestion also? (And some insight of the cost of such stack)

kelvinskell profile image
Kelvin Onuchukwu

In my day to day work, I use InfraCost. It is a really nice tool for performing cost analysis of your infrastructure before provisioning them - using terraform.

lakincoder profile image
Lakin Mohapatra

Nice post

kelvinskell profile image
Kelvin Onuchukwu


143umohsinkhan profile image
Mohsin Khan

Well !! this is greatly explained and described, but just question as mentioned to use PostgreSQL in architecture and using MySql ?

kelvinskell profile image
Kelvin Onuchukwu

I had a change of heart while building the project. The python application already uses MYSQL. Using Postgres for this walkthrough would have meant re-writing the python application to use Postgres instead of MYSQL.

ketharadha profile image

This is a great article. Hope explained each and every component is very nice.
I was looking for "The third project introduces a Reporting Layer into the architecture." Is the third part of this series is done? If yes, can you please share the link here or update in the start of this document?

felipegutierrez profile image
Felipe Oliveira Gutierrez

Hey. Thanks for the very comprehensive tutorial. I enjoyed going through all configurations of the 3 tiers.
I was wondering if you can suggest to use AWS KMS on the step 5 to mount the EFS on the EC2 instance and not get the '# Get DB credentials' on the update user data on the EC2 instance. I think that storing passwords on the EC2 is defenetelly not safe, isn't it?

lynnlangit profile image
Lynn Langit

this is really well-written - nice job and thanks for sharing!

parkerproject profile image

A lot of brain power needed to go through this. great write up by the way