How to Deploy a Production Grade Flask application to an AWS EC2 Instance using GitHub Actions: Part Four.

Logging and Database Integration.

Lyle Okoth
10 min readJun 18, 2022

This is the fourth article in a series that takes the reader through the process of deploying a production ready flask API to an AWS EC2 instance. In the previous article we:

  • We set up nginx to route HTTPS traffic to our server.
  • We then added a domain tour application for easier access from the internet.
  • We then used certbot to generate an SSL certificate for our site.

In this article, we will accomplish the following:

  • Set up databases locally for testing and development using docker compose.
  • Set up our application to connect to and make use of the created database.
  • Update our workflow by setting up a PostgreSQL service for testing our application using GitHub Actions.
  • Set up a database on AWS and connect our application to it.
  • Set up logging.

By the end of this tutorial, you will have an application that uses a PostgreSQL database hosted on AWS. Let’s get started.

Step 1: Test our application.

At the end of the previous tutorial, we had set up a domain name for our application and in an earlier tutorial, we had created a service that automatically starts up our application when the server is started up. To test it out, first boot up the AWS EC2 server that hosts our application, then navigate to https://dev.twyle.xyz (replace dev.twyle.xyz with your own domain name) and you will get:

Step 2: Test our application locally

Let us ensure that our local code works. If you don’t have tho code, just follow the previous tutorial to get up to speed, then on the command-line:

source venv/bin/activate # activate the virtual environmentmake update # update the package managermake install && make install-dev # install the project requirements (just to be safe)make run # start the application curl localhost:5000/

You will get:

{“hello”:”from template api auto-deployed with GitHub actions and being tested!”}

Terminate the application.

Step 3: Set up the databases

We then need to set up the database for local use. We could install and use a PostgreSQL database, but I think it would be easier and more convenient to spin up a docker container for the development and testing. We will use docker compose, since we need two databases, one for development and one for testing. However in this article, we will not use the test database. In the project root, create a new file called docker-compose.yml and enter the following:

  • We create two services one called test_db and the other called dev_db, both of which spin up a PostgreSQL database running on the local-host, with a user called postgres with password lyle and database name set to lyle. The test database is accessible on port 5435 and the development database is available on port 5436. I chose those ports since I have PostgreSQL installed locally and running on port 5432.
  • We also included a volume, for the development database since we do not want to lose our application data.

Spin up the two databases:

sudo docker-compose up -d --build

To test the databases, let us connect to them:

sudo docker-compose exec dev_db psql --username=postgres --dbname=lyle --port=5432

You should get a prompt for the development database, called lyle.

\q # at the prompt to exit.

Stop the running containers:

sudo docker-compose down -v

Step 4: Set up our application to access the database

The database that we created will enable our application to store users and admins. The admins will have the authority to create new users by supplying a JSON web token that will be issued out to them after they have logged into their accounts. So, with a database, how would our application look like?

  • We will have two database models, Admin and User, built with flask-sqlalchemy. The User model, as it name suggests will represent a user in our application, with an id, a name and whether or not they are active. The Admin model, will represent an admin in our application, with an id, name, email and password.
  • We will add another blueprint, called auth, that will hold all the functionality that is associated with the admin. This includes registration, and logging in. The default blueprint will be updated to allow the creation, reading, updating and deletion of a user.
  • We will create a configuration, that tells our application how to connect to the database, then add extensions that enable the application to interact with the database.
  • We will also add helpers, that would make the overall application structure better.

Let us start with the application configuration. For that, we will first need to supply the database connection information, including it’s host address, the user , the password and the database. Open up the .env file at the project root and update it with the following:

Replace the POSTGRES_HOST (192.168.xxx.x) with your computer’s IP address.

For our application to make use of this information, we need a way to read them. We will create a config object that later will be used by the application. Open up the api/config.py file and update it with the following:

We create configurations for development, testing, staging and production all of which inherit from the base config. Then to use the configuration, we need to pas it to the flask app instance.Open up api/__init__.py and update it with the following:

  • This module first imports all the necessary modules, as well as the blueprints, extensions and helpers.
  • It starts by loading the environment variables, line 16, then checks if the all the environment variables are set in line 19 to 22. The function are_environment_variables_set is declared in the helpers module. Find it on the project repo and add it to your project.
  • It then creates the app instance on line 25, then registers the blueprints. At line 33, the environment is set using the set_flask_environment function also from the helpers module by passing in the app instance. This function uses the config class that we created earlier on. Check it out in the project repo.
  • We then initiate the database, the migration script as well as the flask JSON web tokes extension, then finally register an error handler for HTTP 400 error. The migrate instance is declared in the extensions module, while the error handler is declared in the error_handlers module.

Let us briefly look at the two database models. The user model is declared in the default package within the models module. Open up api/blueprints/default/models.py and add the following:

This is a very simple model that represents a user with only an email address and whether or not they are active, which defaults to True. The id is automatically generated by the database.

Then create a new folder called auth in the blueprints folder. Within the auth folder, create a __init__.py file to mark it as a package, the create the models.py module to hold our models. The auth package declares all the functionality associated with creating and authorizing our admins. Open up the models.py file and enter the following:

  • It first imports the dataclass, some constants as well as the database instance. The constants are declared in the constants module within the blueprint package while the database instance db is declared in the extensions module in the blueprints package. The extensions module also contains the declaration of the flask JSON web token extensions declaration as well as the creation of the application logger.
  • The admin is a bit more complex, with an email, name and password.

Some notes about the models:

  • The user’s id and email cannot be null, and the id has to be an integer while the email has to be a string. Both have length limits and the email has a specific format that has to be adhered to. The email address also has to be unique. When creating the user, these are checked for, and if not met, an exception is raised. Check the helpers module in the default package, where these checks are performed. The exceptions module in the blueprints package declares the exceptions that are raised when the user attributes fall outside of the specified values.
  • The admin’s email, name and password cannot be null. They also have length limits. The name and password have to be unique and alphanumeric, while the email has to adhere to a specific format. If any of these rules are not met, an exception is raised. Check the helpers module in the auth package for details.

Finally let us look at the views that allow our application user to interact with our database. First, open up the api/blueprints/default/views.py file and update it with:

  • There’s the default route that simply informs you that this is an API.
  • Then we have routes for creating, updating, reading and deleting a user all which require authorization, the @jwt_required tag. There is also a route for getting all the users. These routes make use of methods declared in the helpers module to handle their respective requests.

Then open up the api/blueprints/auth/views.py file and update it with the following:

  • In this module, we are mostly interested in the register and login functionality. The other routes are still under development. The register route allows one to create an admin user. Once created, the login route allows the admin to log in and obtain an access token which they can use to create a new user.

Step 5: Test the application.

To test the application, we first need to update the requirements. Open up the requirements.txt file and add the following:

Then create a script for creating the database and a few users. Create a file called manage.py in the project root and add the following:

  • The create_db function simply creates all the database tables.
  • The seed_db function creates two dummy users.

Then on the command-line:

source venv/bin/activate # activate the virtual environmentmake install # install the project requirementssudo docker-compose up -d — build # start the databasespython manage.py create_db # create the databasepython manage.py seed_db # create two default records in the databasemake run # start the application

To test the application:

curl http://localhost:5000/

You get:

{
“hello”: “from template api”
}

curl http://localhost:5000/users

You get:

[
{
“active”: true,
“email”: “test@example.com”,
“id”: 1
},
{
“active”: true,
“email”: “test1@example.com”,
“id”: 2
}
]

Then to try the authorization, open up postman. Create a post request to the create route to create a new admin. This route will be localhost:5000/auth/register:

Then create a post request to the login endpoint, localhost:5000/auth/login to get a refresh and access tokens:

Create a new post request to localhost:5000/user:

Then to add authorization, copy the access token that you got when you registered a new admin, then under the Authorization tab, in the Type drop-down select Bearer Token:

Then paste it into the Token field and press send:

You should have created a new user:

With that, we have created a flask app that enables an admin to register and login in, then authorizes them to create new users using JSON web tokens. Stop the flask server as well as the running containers.

sudo docker-compose down -v # stop the running containers

Since this tutorial is already long enough, we will set up the database in the next article.

Conclusion

In this article, we did the following:

  • Tested that our application works, both on AWS and on our local computer.
  • Set up two databases by creating two postgres containers, one for development and one for testing, using docker-compose.
  • Connected to the created databases using docker compose.
  • Set up our application to connect to the created databases as well as log the interactions.
  • Created authorization for admin users using the flask JSON web extension
  • Tested that our application worked by connecting to the database and creating new users

In the next tutorial, we will install a database on AWS and connect our application to the created database. We will also update the GitHub workflow to create postgres database during the workflow run.

I hope you enjoyed it and learnt something. Give it a clap or share it out and do not hesitate to reach out to me in-case of an issue. The code for this application is here flask-ec2-deployment. I am Lyle, a junior software engineer with a passion for developing, testing and deploying scalable services. You can find me on twitter, linkedin, github and here’s my portfolio. See you next time.

--

--

Lyle Okoth

Conversational AI Engineer | Building conversational AI agents that work with humans to automate various workflows.