Home TutorialsDocker Dockerise a Python app with PostgreSQL

Dockerise a Python app with PostgreSQL

by Atif Azad

In this tutorial, you will learn how to Dockerise a Python app with PostgreSQL database. We will dockerise the Python Flask app that we created in previous tutorial Build API with Python Flask. However the same setup can be used to dockerise Django apps or any other Python apps.

Prerequisites

This tutorial requires basic level Docker understanding. You can follow the two other articles in my Docker tutorial series.

  1. Getting started with Docker
  2. Build and publish your own Docker image

Following sections assume that you have installed Docker on your machine and you have basic understanding of Docker.

Create a Virtual Network

In sections ahead, we will run two separate docker containers; one hosting the PostgreSQL and the other hosting our flask-api-example app. Now think of these two separate containers as two separate machines which means that there needs to be a network over which our app can communicate with the DB.

Docker lets us create a virtual network with a simple command. We’ll add both our containers on that network and will use it to create DB connection from our dockerised app.

To create a Docker network, execute following command in your Terminal.

> docker network create dock-net

This command creates a simple bridge network with name dock-net which is enough for our needs 🙂

You can use following command to see which networks are available.

> docker network ls

In the output, you should see the dock-net network along with the default ones.

Run a PostgreSQL Docker Container

This step is very simple and straightforward. Like we did in Getting Started tutorial, we’ll use a Docker image from Dockerhub but this time it will be a PostgreSQL image. You can choose a postgres image tag of your choice based on which version of PostgreSQL you need. This tutorial, however, uses PostgreSQL 12.2 and accordingly the Docker image tag is postgres:12.2.

Open Terminal app and execute following command.

> docker container run -p 5433:5432 --network dock-net --name pg-container -e POSTGRES_PASSWORD=1234 postgres:12.2

This command will create a new container running a PostgreSQL instance. Let’s take a look at the components of this command.

-p 5433:5432:

-p (or --publish) is used to publish a container’s port(s) to the host. Here we are publishing container’s port 5432 (Postgres default port) and mapping it to host’s port 5433.

--network dock-net:

With --network option we are adding this container on the network dock-net that we created above.

--name pg-container:

For easy reference, we can use optional --name option so we are specifying the name for this container as pg-container. If we don’t specify a name, Docker will give it a random name.

Although this name has other uses also but in our current example, its most important use is that this name pg-container is also the identifier of this container on our dock-net virtual network. Therefore, we will use pg-container as DB host name in DB connection in our app configs.

-e POSTGRES_PASSWORD=1234:

-e is used to pass environment variables in container. Here we are passing POSTGRES_PASSWORD which is mandatory to be provided for the DB user postgres.

postgres:12.2

This is the Docker image that we are using to create PostgreSQL container. postgres is the name of image while 12.2 is the tag depicting the PostgreSQL version as well.

When you execute the docker container run command, Docker looks into the local repository, if it doesn’t find this image, it downloads it from Dockerhub.

Testing the DB connection from Host

To test the PostgreSQL DB connection from host machine, you can use any DB client app like PGAdmin or you can also use command line utility psql as well.

Below is the image showing the connection details in PGAdmin 4. Please note that we are using port 5433 of localhost. This is because we mapped port 5432 (default PostgreSQL port) to host’s port 5433. As we are now connecting from host (which is out of dock-net virtual network so we must use the mapped port of the localhost.

PGAdmin4 connection settings for a Dockerised PostgreSQL instance

Create Application Database

Now that we have connected to PostgreSQL host, we can create the database for our Python Flask app. So just go head and create a new DB with name of your choice 🙂

At this step we have an empty DB having no tables!

Dockerise the Python app

So we are using the same app that we created in our previous tutorial, Build API with Python Flask. In that tutorial we did the app setup in traditional way. Now we will dockerise it by adding a Dockerfile.

Dockerfile

  • Add a folder named docker under application root folder. Naming this folder as docker is not mandatory, you can name it as you wish.
  • Under this docker folder add file Dockerfile with following content:
# Start with Python 3.8 base image
FROM python:3.8-alpine

# Show stdout and stderr outputs instantly without buffering
ENV PYTHONUNBUFFERED 1

# Create application root directory in container
RUN mkdir /dockerised-example

# Set dockerised-example as working directory
WORKDIR /dockerised-example

# Copy requirements.txt in application root directory
ADD requirements.txt /dockerised-example/

# Install dependencies
RUN \
    # Install curl
    apk add --no-cache curl && \
    # Install postgres utilities which are required by `psycopg2` lib being used by app
    apk add --no-cache postgresql-libs && \
    apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev && \
    # Install Python packages required by the app
    python3 -m pip install -r requirements.txt --no-cache-dir && \
    apk --purge del .build-deps

# Add all files from current directory on host to dockerised-example directory in container 
ADD ./ /dockerised-example/
COPY docker/entrypoint.sh /sbin/entrypoint.sh

# Expose a port so that we can map it with a host port
EXPOSE 5000

# Execute command when container starts (not executed with image build)
ENTRYPOINT ["/sbin/entrypoint.sh"]

I have explained all steps in the Dockerfile with inline comments. Please go through them. You can once again note that Dockerfile essentially contains a set of commands that we’d otherwise perform manually at the time of deployment.

Please note that every step written in the Dockerfile becomes an image layer. When you next time build your image, Docker checks if a layer exists already then it uses it from cache so saving the resources and time.

If your Docker file has 9 steps and in the next build of image, if the change has made at step 6, all layer 6-onwards will be built again. This means that arranging your steps in a way that most frequently changed parts are at the bottom will save you quite much build time.

entrypoint.sh

  • Add another file entrypoint.sh under folder docker. The content of this file should be:
#!/bin/sh
set -o errexit -o nounset -o pipefail

cd /dockerised-example

flask run --host=0.0.0.0
  • Please note that you can write any commands that you’d like to execute at the time of your application startup. It can be a good place for database migrations as well.

For our purpose, we have to execute Flask’s development server which is enough within the scope of this tutorial so we just add flask run command and before that we obviously cd to application folder.

Set Application Config

We are using .env file to set the the environment based application configs. So let’s make that one important change in .env file we created in previous tutorial Build api with Python Flask.

  • As it was mentioned above that the identifier of our DB host container is pg-container on the virtual network dock-net. Our application container will also join this network so it needs to use DB_HOST=pg-container.
  • Also set DB_NAME, DB_USERNAME and DB_PASSWORD.
  • Finally, .env file should appear like below:

FLASK_ENV=development
FLASK_DEBUG=true

APP_BASE_URL=127.0.0.1
APP_SECRET_KEY=dev

DB_HOST=pg-container
DB_PORT=5432
DB_NAME=<db_name>
DB_USERNAME=<username>
DB_PASSWORD=<password>

SESSION_LENGTH=2592000

Build the Application’s Docker image

As you know, to run a container, we need an image. So we have to first build the Docker image.

  • Make sure you are in application’s root folder.
  • Run the following command:
> docker build -t flask-docker-exp:latest . -f docker/Dockerfile

Let us see what are the components of this command.

-t flask-docker-exp:latest:

-t (or --tag) option is used the specify the name and tag of the image. Here we have named the image flask-docker-exp and latest is the tag.

.

This . specifies the context as current folder. As we are executing the docker build command from application’s root folder, so it will be the context for docker build.

-f docker/Dockerfile

-f (or --file) option is used to specify the path of Dockerfile. This is an optional param and if it is not specified Docker will look for a Dockerfile current folder.

If you want to file name other than default name Dockerfile, you can do so. In that case too, you can use -f option to specify the custom name of your Dockerfile.

Run the application Docker container

Now that we have build the Docker image for our application. Its time to run our Dockerised application!!

  • Remember that our database host is running in its own container.
  • We created, in steps above, a virtual network named dock-net.
  • We already added our DB container pg-container on that network dock-net. pg-container is therefore the identifier on this network of our DB host.
  • Also we said (as it makes sense!) that we’ll add our application container on dock-net network so that both containers can communicate.

So keeping all that in mind, let us run our first dockerised application!

  • Run the following command in your Terminal.
> docker container run -p 8000:5000 --network dock-net --name flask-api-exp flask-docker-exp:latest

Again, let us go through each component of docker container run command and see what they do.

-p 8000:5000:

Same as explained above. We are publishing container’s port 5000(default port that Flask development server uses) and mapping it to the host’s port 8000. This means that localhost:8000 (or 127.0.0.1:8000) will send the requests to port 5000 of this container.

--network dock-net:

With --network option, We are adding our application’s container on the virtual network dock-net.

--name flask-api-exp:

For easy reference to our app container, we are using optional --name parameter and naming the container flask-api-exp. If we don’t tell a name, Docker will assign it a random name.

flask-docker-exp:latest:

flask-docker-exp is the name and latest is the tag of our Docker image which we are using to run a container.

Test the API endpoints

Now that our first Dockerised application is running so we can go ahead and test the APIs like before. We can use curl or using any API client tool like Postman to test the APIs we created in our Python Flask app and have dockerised it today!

Below are the Postman images, showing the requests and responses. Please note that we are using port 8000 of the host localhost (127.0.0.1) as we mapped it with port 5000 of our application container.

Test results of Registration API (Dockerised app) in Postman
ImageUser registration API (/api/users) – A Dockerised app
Test results of Login API (Dockerised app) in Postman
ImageLogin API (/api/login) – A Dockerised app

What’s more to do?

So far we have done very basic Docker setup. There is a lot more to do.

DB data is not persistent

If you remove your container, you’ll lose the DB data. However this shouldn’t happen. We will fix this issue in our next tutorial.

App container’s image needs to be build every time when code changes

This is important especially if we want to setup our development environment using Docker. Current setup is not enough for development environment as we need to build image every time we change the code.

In next tutorial we’ll focus on these issues to improve our Docker setup 🙂

5 1 vote
Article Rating

You may also like

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More

Privacy & Cookies Policy
0
Would love your thoughts, please comment.x
()
x