Step-by-Step tutorial to deploy your Flask app to EC2 AWS Ubuntu 20.04 with Apache, WSGI, and Python 3

jQN
10 min readJun 19, 2021

Why this tutorial?

Python 2 is now deprecated as of January 1st, 2020 so it’s time to move on to Python 3, and to make this transition easier is ideal to move from Ubuntu Server 16.04 instances, which are still running Python 2 by default to a newer version Ubuntu 20.04.

In this article, I will walk you through deploying your Flask app to an AWS EC2 environment on top of Ubuntu 20.04, Apache 2, wsgi, Python3, and MySQL step by step.

Prerequisites

  • AWS Account
  • Ubuntu 20.04 EC2 Instance
  • Shell Access
  • Comfortable with the command line/terminal

1. Step 01 — Launch and configure a new instance

Let’s start by opening up the EC2 console in your AWS account. From here launch a new instance.

- Select Ubuntu Server 20.04 LTS — 64-bit(86) — ami-0885b1f6bd170450c for your AMI.
- You can use a T2 micro version for your instance type, mostly because it has a free tear option. If you have more demanding requirements feel free to choose one that better meets your needs.

- Create a new or select an existing security group in the next step.

Note: to avoid cluttering your AWS account with multiple security groups that do the same thing, you can reuse an existing security group with these inbound rules, or create a new one if you like:

  • SSH
  • HTTP
  • HTTPS

The SSH setting allows ssh access from any IP address. If you prefer you can set it to only allow access to your Public IP which you would need to update every time it changes unless you have a Static IP. HTTP allows you to access your new instance from the browser and HTTPS is for running secure connections if you have an SSL certificate.

- Choose to create a new key pair or reuse an existing one.

There is also an option to reuse public keys, just like security groups it is best to reuse them if it makes sense. Make sure to download and save the key pair somewhere where you can find it later, I’ll be referring to this key as microservices.pem . Once everything is configured click on the Launch Instances button.

Go back to your EC2 dashboard and remember to give your new instance a meaningful name to keep better track of it and wait for the instance state to finish launching. I’m going to be using the name microservices for the remainder of this article because we’ll be using the microservices app from this repo.

https://github.com/jqn/microservices

Step 02 — Connect to your new instance via SSH

There are several ways to connect to an instance running on AWS. My favorite one is to connect via SSH. You can use FTP if you like but that is no longer the most efficient approach.

- To get the connection details select your new instance and click on the connect button in your EC2 dashboard.

- Follow the instructions listed on the next screen, make sure to click on the SSH client tab.

  • 1 — when someone refers to an SSH Client, it could be your Mac terminal, Windows Powershell, iTerm, or my personal favorite Termius.
  • 2 — locate the private key file we previously downloaded and let’s move somewhere where is easier to find. I like to keep the key in my root directory in a folder called .ssh Create a new one if you don’t have one. Run the following commands in the terminal.
 $ cd
$ mkdir ~/.ssh

The dot in front of that folder means it will be a hidden file. To show it in finder use this keyboard shortcut `cmd + shift + .` to toggle hidden files.

  • 3 — let’s update the permissions for our private key, in our case, this would be:
$ chmod 400 ~/.ssh/microservices.pem
  • 4 — we’ll be using our instance’s Public DNS to connect to the new instance we just created. Copy it and let’s move on to the next step.

- Now that you have the setup ready let’s connect to our instance. Like I previously mentioned you can use any SSH client you like. I’ll be using iTerm and later on Termius. Open up iTerm and paste the connection string from the connect to instance example in AWS.

$ cd
$ ssh -i “~/.ssh/microservices.pem” ubuntu@ec2–54–226–215–250.compute-1.amazonaws.com

Notice the connection string point to the directory where we stored the private key. You will end up with something similar to this:

  • Hit the enter key on your keyboard to connect and you’ll be presented with the Ubuntu welcome screen.

Like I mentioned previously Termius is a great choice for easy management of your servers. It allows you to save all the connections settings so you don’t have to log in to AWS EC2 to retrieve them every time. Here is an example screenshot.

Step 03 — Update and upgrade

Before we do anything else, let’s make sure we update the local package index and upgrade the system. This makes sure everything is up to date and prevents any errors due to deprecations.

$ sudo apt-get update
$ sudo apt-get -y upgrade

Step 04 — Set up Python 3

  • Check the Python version installed in the system, as of this article Python 2 is officially deprecated so you should be using Python 3 on new projects. Ubuntu 20.04 comes with Python 3 pre-installed.
$ python3 -V

You should get an output similar to this.

Python 3.8.5
  • Install PIP to manage software packages for Python.
$ sudo apt install -y python3-pip
  • Check that pip3 installation was successful.
pip3 -V
  • Output
pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.8)
  • Install other required dependencies to make sure you have a robust development environment.
$ sudo apt install -y build-essential libssl-dev libffi-dev python3-dev

Step 05 — Version Control

For this tutorial, you will be using GIT to clone our project into this server. Version control allows for easier codebase management and team collaboration. Ubuntu 20.04 also comes with GIT pre-installed.

$ git --version
git version 2.25.1

Step 06 — Install And Configure Apache and WSGI

Let’s install the Apache webserver with the mod_wsgi module to interface with your Flask app. We need it because web servers don’t natively speak Python and WSGI makes that communication happen.

$ sudo apt-get install -y apache2 

When installing mod_wsgi make sure to install the version in this guide. If you install libapache2-mod-wsgi instead you might run into an error where it can’t find the Python packages required for your app because that version only works with Python 2.

$ sudo apt-get install -y libapache2-mod-wsgi-py3

If you point to your browser at your instance’s Public DNS you should see Apache’s default page, indicating the installation is working correctly.

Step 07 — Create And Configure A Simple Flask App

To make this section less error-prone let’s first create a simple Flask app with minimal requirements to test everything out before deploying a full-fledged application.

  • Create a directory for your Flask app in your home directory.
$ mkdir ~/microservices.com
$ cd ~/microservices.com
$ touch run.py
  • Put the following content in run.py
# run.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, I love Flask!"
if __name__ == "__main__":
app.run()
  • Create a symlink to the site root defined in Apache’s configuration.
$ cd
$ sudo ln -sT ~/microservices.com /var/www/html/microservices.com
  • Install Flask with PIP
$ cd ~/microservices.com 
$ sudo pip3 install Flask
  • Add a .wsgi script file to load the app.
$ touch production.wsgi
  • Put the following in the production.wsgifile.
# production.wsgi
import sys

sys.path.insert(0,"/var/www/html/microservices.com/")

from run import app as application

Step 08 — Configure Virtual Hosts

Apache displays HTML pages by default but to serve dynamic content from Flask make the following changes. The default Apache configuration file is located at etc/apache2/sites-available/000-default.conf. Instead of overriding that file let's create a new one.

$ sudo vi /etc/apache2/sites-available/microservices.com.conf
  • Add the following to microservices.com.conf
# microservices.com.conf
<VirtualHost *:80>
ServerAdmin webmaster@localhost
ServerName your_domain
ServerAlias www.your_domain
DocumentRoot /var/www/html/microservices.com
WSGIDaemonProcess microservices.com threads=5
WSGIScriptAlias / /var/www/html/microservices.com/production.wsgi
<Directory microservices.com>
WSGIProcessGroup microservices.com
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
  • Disable the default Apache config by running the following command.
$ sudo a2dissite 000-default.conf-- Output
Site 000-default disabled.
To activate the new configuration, you need to run:
systemctl reload apache2
  • Now enable our new conf
$ sudo a2ensite microservices.com.conf
  • Reload apache to implement the changes made.
$ sudo systemctl reload apache2
  • If you run into any error you can look at the logs with this command.
$ sudo tail -f /var/log/apache2/error.log
  • Reload your browser and instead of the default Apache page you should now see Hello, I love Flask!

Step 09 — Clone And Run A More Complex Project

The example repo is located in GitHub under version control. Here you can download it or clone it. First, remove everything inside the microservices directory.

$ cd
$ rm -r ~/microservices/*
  • Run the following command to clone it directly to your server.
$ git clone https://github.com/jqn/microservices.git .

Step 10 — Install MySQL And Configure A Database

You could be using AWS RDS instead of installing and configuring your database locally on the same server. The reason this is frowned upon is if the server goes down the database goes down with it. Regardless this is a good exercise and is useful if you need a throw-out database, or simply don’t want to pay any more fees for using other services. Run the following command to install it.

$ sudo apt-get install -y mysql-server-- Output
update-alternatives: using /etc/mysql/mysql.cnf to provide /etc/mysql/my.cnf (my.cnf) in auto mode
mysqld will log errors to /var/log/mysql/error.log
  • Configure MySQL with a new password for the root user.
$ sudo mysql
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'y0urPa55w0rd';
mysql> FLUSH PRIVILEGES;

- Create a new database for your project.

$ mysql -u root -p
mysql> CREATE DATABASE microservices;
mysql> SHOW DATABASES;
-- Output
+--------------------+
| Database |
+--------------------+
| information_schema |
| microservices |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.01 sec)
  • Check MySQL server status to make sure everything is running smoothly.
$ systemctl status mysql.service-- Output
mysql.service - MySQL Community Server
Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2020-11-23 15:13:19 UTC; 35min ago
Main PID: 24850 (mysqld)
Status: "Server is operational"
Tasks: 39 (limit: 2372)
Memory: 385.8M
CGroup: /system.slice/mysql.service
└─24850 /usr/sbin/mysqld
  • It is also possible to connect to your new remote database through ssh using a client like SequelPro.
  • Install mysql_config
$ sudo apt-get install -y libmysqlclient-dev
  • Install your project dependencies with pip3
$ sudo pip3 install -r requirements.txt
  • Update your production.wsgiwith new global environment variables to allow you to connect the app to the new database.
# production.wsgi
import sys
import os
sys.path.insert(0,"/var/www/html/microservices.com/")def application(environ, start_response):
for key in ['DB_NAME', 'DB_USERNAME', 'DB_PASSWORD', 'DB_HOSTNAME', 'SECRET_KEY']:
os.environ[key] = environ.get(key, '')
os.environ['FLASK_CONFIG'] = 'production'
os.environ['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:React0r2020@localhost/microservices'
from run import app as _applicationreturn _application(environ, start_response)
  • To make the app more secure and to keep environment variables out of the directory we are serving out to the internet you can move the global environment variables to your virtual host.
$ sudo vi /etc/apache2/sites-enabled/microservices.com.conf 
  • After adding the environment variables the virtual host should look like this.
<VirtualHost *:80>
ServerName microservices.com
ServerAdmin admin@microservices.com
DocumentRoot /var/www/html/microservices.com
SetEnv DB_NAME microservices
SetEnv DB_USERNAME root
SetEnv DB_PASSWORD React0r2020
SetEnv DB_HOSTNAME localhost
SetEnv SECRET_KEY p9Bv<3Eid9%$i01
WSGIDaemonProcess microservices.com threads=5
WSGIScriptAlias / /var/www/html/microservices.com/production.wsgi
<Directory microservices.com/>
WSGIProcessGroup microservices.com
WSGIApplicationGroup %{GLOBAL}
Order allow,deny
Allow from all
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Step 11 — Initialize and migrate the database

  • Initialize the database
$ flask db init
  • Create the first migration
$ flask db migrate
  • Apply the migration to create the users table in the database
$ flask db upgrade

Step 12 — Restart Apache and check the new configuration

  • Restart apache to apply the changes.
$ sudo service apache2 restart
  • If the application throws errors after reloading the application on your browser you can run this command to quickly view the error logs.
$ sudo tail -f /var/log/apache2/error.log

Step 13— Now See Your New App Live

  • Point your browser to your server’s Public DNS for example
ec2–54–226–215–250.compute-1.amazonaws.com

Some tips and tricks

  • How to change MySQL user password
$ mysql -u root -p
mysql> ALTER USER 'user-name'@'localhost' IDENTIFIED BY 'NEW_USER_PASSWORD';
mysql> FLUSH PRIVILEGES;
  • How to create a MySQL database
mysql> CREATE DATABASE microservices;
  • How to show MySQL databases
mysql> SHOW DATABASES;

And that’s it, you should now have a fully working Flask application with authentication running on AWS. If you have any questions or would like to connect don’t hesitate to find me on Twitter. Don’t forget to clap, or watch the video of this tutorial on my Youtube channel, best!

--

--

jQN

I'm a Full-Stack Software Engineer who believes in the power of technology to change and improve lives. Connect with me on https://twitter.com/FullStackJQN