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.
This tutorial requires basic level Docker understanding. You can follow the two other articles in my Docker tutorial series.
Following sections assume that you have installed Docker on your machine and you have basic understanding of Docker.
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.
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
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.
--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 option we are adding this container on the network
dock-net that we created above.
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 is used to pass environment variables in container. Here we are passing
POSTGRES_PASSWORD which is mandatory to be provided for the DB user
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
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.
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!
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.
- Add a folder named
dockerunder application root folder. Naming this folder as
dockeris not mandatory, you can name it as you wish.
- Under this
dockerfolder add file
Dockerfilewith 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.
- Add another file
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-containeron the virtual network
dock-net. Our application container will also join this network so it needs to use
- Also set
.envfile 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.
--tag) option is used the specify the name and tag of the image. Here we have named the image
latest is the tag.
. 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
--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
- We already added our DB container
pg-containeron that network
pg-containeris 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-netnetwork 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.
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
127.0.0.1:8000) will send the requests to port
5000 of this container.
--network option, We are adding our application’s container on the virtual network
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 is the name and
latest is the tag of our Docker image which we are using to run a container.
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
127.0.0.1) as we mapped it with port
5000 of our application container.
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 🙂