Flask+Pipenv+Postgres+Docker+Nginx+uWSGI

16 minute read

As a Machine Learning Engineer, you would have to deploy your models to production. In this tutorial, we are going to learn how you can do that. We will start with creating our virtual environment using Pipenv. Then we will create a basic FlaskApp. Then we will connect that FlaskApp to a Postgres database. Then we will launch our FlaskApp using uWSGI. Then we will put a Nginx at the front of our FlaskApp and finally we will dockerize this whole ecosystem.

Highlights:

  • Creating virtual environemnt for our project using Pipenv
  • Creating a Flaskapp
  • Creating postgres script in python to execute queries
  • Creating Dockerfile for our Flaskapp
  • Creating Dockerfile for our Postgres db
  • Creating Nginx Dockerfile
  • Creating docker-compose.yml file
  • Starting flaskapp using uWSGI
  • Running the whole ecosystem with docker-compose

This is going to be lengthy post. Let’s get started.

Creating a virtual environment using Pipenv

pipenv install flask psycopg2 uwsgi

Please note that i am not going to use Flask-SQLAlchemy to connect to postgres db. Instead i will be using psycopg2 library, since i like writing SQL queries better than the ORM. Also, you can write SQL queries in SQLAlchemy as well but i like psycopg2 more.
One more thing, if you are on windows, the above command will fail on uWSGI installation. In such case, you can just remove uwsgi from above command and we will install that later using Docker directly in our container.

pipenv lock

Creating FlaskApp

This is going to be a very simple Flaskapp that will take the name and location in input of the GET request from browser and store that in a postgres db.
Then we will define another view to fetch the records from db based on name field. Create a tutorial.py file at the root directory.

from database.db import execute_query
from flask import Flask

app = Flask(__name__)


@app.route('/<string:name>/<string:location>')
def index(name, location):
    return execute_query('insert', name, location)


@app.route('/<string:name>')
def fetch(name):
    return execute_query('select', name)

Creating Postgres python script

Create a folder inside the root folder database and inside it create a file db.py

import psycopg2

def execute_query(operation, name=None, location=None):

    connection = psycopg2.connect(
        user='postgres',
        password='admin',
        host='localhost',
        port='5432',
        database='flask'
    )
    cursor = connection.cursor()
    if operation == 'select':
        try:
            query = f"SELECT * FROM users where name='{name}';"
            cursor.execute(query)
            records = cursor.fetchone()
            cursor.close()
            connection.close()
            return f'{records}'
        except Exception as e:
            cursor.close()
            connection.close()
            return f'{e}'

    elif operation == 'insert':
        try:
            query = f"INSERT INTO users (name,location) VALUES ('{name}','{location}');"
            cursor.execute(query)
            connection.commit()
            cursor.close()
            connection.close()
            return f'User added successfully'
        except Exception as e:
            cursor.close()
            connection.close()
            return f'{e}'

Creating Dockerfile for our Flaskapp

This will go inside the root directory

FROM python:3.8

RUN pip3 install pipenv
WORKDIR /app
COPY Pipfile Pipfile.lock ./
RUN pipenv install --system --deploy --ignore-pipfile
COPY . .
CMD [ "python","tutorial.py" ]

Creating Dockerfile for our Postgres

If you look at the connection string in our postgres code above, we are connecting with database flask and then we are executing our queries in the table users. We have to manage that via our Dockerfile because we have to ensure that once our postgres container is up and running, it is fully loaded with all the requirements our main code needs.
This will go inside the database directory

FROM postgres:alpine

ENV POSTGRES_PASSWORD admin
ENV POSTGRES_DB flask

COPY init.sql /docker-entrypoint-initdb.d/

Create a init.sql file and paste the below code:

CREATE TABLE IF NOT EXISTS users (
    id serial PRIMARY KEY,
    name varchar(50) UNIQUE NOT NULL,
    location varchar(50)
);

Creating Nginx Dockerfile

Create a new folder nginx inside our main flask folder. Inside this folder, create a Dockerfile and a nginx.conf file

FROM nginx

RUN rm /etc/nginx/conf.d/default.conf

COPY nginx.conf /etc/nginx/conf.d/

Create a nginx.conf file and paste the following:

server {
    listen 80;

    location / {
        include uwsgi_params;
        uwsgi_pass flaskapp:8080;
    }
}

Our nginx will listen on port 80 and will forward the requests to our flaskapp on port 8080

Creating a docker-compose file

Create a docker-compose.yml file at the root directory

version: "3.5"

services:
  flaskapp:
    build: .
    image: muuzii/flaskapp
    container_name: flask_app
    restart: always
    environment:
      - APP_NAME=MyFlaskApp
    expose:
      - 8080

  postgres:
    build: ./database
    image: muuzii/postgresdb
    container_name: flask_db
    volumes:
      - pgdata:/var/lib/postgresql/data

  nginx:
    build: ./nginx
    container_name: flask_nginx
    restart: always
    ports:
      - "80:80"

volumes:
  pgdata:
    external: false

Volume is created to make the postgres data persistent i.e. we doesn’t lose our data once we shutdown the containers.

Starting Flaskapp using uWSGI

For this we have to make few changes in our original Dockerfile of our Flaskapp

FROM python:3.8

RUN pip3 install pipenv
RUN pip3 install uwsgi
WORKDIR /app
COPY Pipfile Pipfile.lock ./
RUN pipenv install --system --deploy --ignore-pipfile
COPY . .
CMD [ "uwsgi","app.ini" ]

Next, create a file at the root directory app.ini and paste the following into it:

[uwsgi]
wsgi-file = tutorial.py
callable = app
socket = :8080
processes = 4
threads = 2
master = true
chmod-socket = 660
vacuum = true
die-on-term = true

After all this is done, goto the terminal and into the root path where docker-compose.yml file has been created. Run the following command:

docker-compose up --build

And there you have a fully functional, production ready Flaskapp :)

Leave a comment