Dockerizing Flask App with Postgres: A Step-by-Step Guide

In the previous episodes about Flask, I explained how to initiate your Flask application, and also how to set up a Postgres database for it. In this episode, I will show you how to dockerize your Flask application and its database.

Before we start, I suggest taking a look at the previous episodes since we are using the same source code and application:

Note: we use docker and docker-compose in this post. You need to install them in case you did not already:

https://docs.docker.com/engine/install/

https://docs.docker.com/compose/install/

Let’s start!

Dockerfile for the App

The first step is to create a Docker file for our application. A docker file acts as a blueprint for our application container.

Create a new file name Dockerfile in your application’s root directory. Then follow the steps in this section to implement it.

First, we set our base image (python:3.9-slim). We also update the Debian package manager and install gcc package. (Some of the Python extensions require gcc)

Note: “-y” option is essential since Debian asks for consent before installing a package. This option says yes to that automatically.

FROM python:3.9-slim

RUN apt-get -q -y update 
RUN apt-get install -y gcc

The next step is to define some environment variables and the working directory. The working directory is the path that your docker file commands execute in. We also set our container username to use later.

ENV USERNAME=cool-app
ENV WORKING_DIR=/home/cool-app

WORKDIR ${WORKING_DIR}

After that, we copy the needed directories and files to the app container.

Note: Create the file service_entrypoint.sh in your app root directory and leave it empty. I explain why we need it and what to put inside it later in this post.

COPY best_app best_app
COPY requirements.txt .
COPY service_entrypoint.sh .

The next step is to create the container user (with the username we defined before) and gives it the needed permissions.

Note: It is not recommended to run your app in the container as the root user. Always create an application user.

  • First, we create the user and the group
  • Then, we give the new user suitable access permissions for the working directory.
  • After, we switch to the new user.
  • At last, we add a new path to our Debian path list. (pip needs this for installations)
RUN groupadd ${USERNAME} && \
    useradd -g ${USERNAME} ${USERNAME}

RUN chown -R ${USERNAME}:${USERNAME} ${WORKING_DIR}
RUN chmod -R u=rwx,g=rwx ${WORKING_DIR}

USER ${USERNAME}
ENV PATH "$PATH:/home/${USERNAME}/.local/bin"

Then, we install the needed Python packages with pip.

  • We upgrade the pip first to the latest version
  • Then we install our packages
  • We also add our Flask application as an environment variable. FLASK_APP is an environment variable used in Flask to specify the name of the Python module that contains the Flask application.
  • Finally, we make our service_entrypoint (that we defined before) executable.
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

ENV FLASK_APP=best_app
RUN chmod +x service_entrypoint.sh

In the end:

  • We open port 5000 in our container. This is the port that we run our Flask app on.
  • We initiate our migration scripts using the init command for Flask.
  • Finally, we run the service_entrypoint.sh script that completes our app running. (check the next section)
EXPOSE 5000
RUN flask db init

ENTRYPOINT [ "./service_entrypoint.sh" ]

Our Dockerfile is ready now!

Service Entrypoint

Sometimes, our Dockerfile cannot run our service. The reason is that sometimes our service demands some initialization configuration that is not available when we are building our container.

In these situations, we usually use a bash script so-called Entrypoint to do the job for us.

Do you remember the service_entrypoint.sh file that you created earlier? Put this script inside it:

#!/bin/bash

sleep 10
flask db migrate
flask db upgrade 
waitress-serve --port 5000 --call 'best_app:create_app'

tail -f /dev/null

What does it do?

Note1: You can use other Python Web Server Gateway Interfaces also.

Note2: Never run your Flask app with the Flask built-in dev server.

Note3: The last line “tail -f /dev/null” is essential to keep the service running. If you miss it, the docker engine brings down the container after finishing the script successfully.

Docker Compose

The last step in dockerizing our app is to write the docker-compose file. This is the way we run our Flask app and the Postgres database in one virtual network as two separate services.

To do this, create a new YAML file in your project directory named docker-compose.yml

First, we add the Postgres Service:

version: '3.0'
services:
  db:
    image: postgres
    container_name: db
    restart: always
    ports:
      - 5123:5432
    env_file:
      - .db.env    
    volumes:
      - ./pData:/var/lib/postgresql/data
    healthcheck:      
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB} -t 1"]
      interval: 10s
      timeout: 10s
      retries: 10
      start_period: 10s   
  • The service name is db. This name is important since we need it later.
  • We map the Postgres default port 5432 to the localhost 5123. This part is optional. You need it if you want to check the Postgres container from your host server.
  • Very Important: We bind the Postgres data directory to the pData directory in our root directory. (Docker creates this automatically). This way we persistent our database. Without this, you will lose the data if you bring down the container.
  • We also need a health check. We will use it in the next section for writing the Flask app part. The reason is our Flask app requires the database to be up and ready in order to run.

Next and last, we write the Flask app service part in the docker-compose:

best_app:
    container_name: best-app
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 8008:5000    
    depends_on:
      db:
        condition: service_healthy    
    links: 
        - db
  • Our container name is best-app
  • We map port 5000 of our container (the one we used for running Waitress) to the host 8008 (you can choose other ports also)
  • We state that our service depends on the db service. This will check based on the health check condition we set for the Postgres container
  • We also link our service to the service db. This way our service can connect to Postgres.

Hint: You can define a named network and run both services on that instead of using a link

Now that our docker-composer is ready, we need one more last step to run our service.

Last step and run

The last step is to change the Postgres connection setting in our Flask app config.

We were using localhost as the host for Postgres. We need to change that to db (our Postgres service name in the docker network).

Why? Because localhost would be our Flask app container which does not host any Postgres.

SQLALCHEMY_DATABASE_URI = "postgresql://cool_user:1234@db:5432/cool_db"

Everything is ready and we can run our dockerize service. To do this, navigate to your app root directory (where docker-compose.yml is) and run:

sudo docker-compose up --build

Congrats! Our services are up and running!

You can find the source code for this writing here: https://github.com/Pooya-Oladazimi/flask-cool-app/tree/master

The End.

One thought on “Dockerizing Flask App with Postgres: A Step-by-Step Guide

Comments are closed.