Dockerizing a Python Django Web Application
Get an understanding of how to dockerize your Django application, using the Gunicorn web server, capable of serving thousands of requests in a minute.
This article will cover building a simple 'Hello World'-style web application written in Django and running it in the much talked about and discussed Docker. Docker takes all the great aspects of a traditional virtual machine, e.g. a self contained system isolated from your development machine, and removes many of the drawbacks such as system resource drain, setup time, and maintenance.
When building web applications, you have probably reached a point where you want to run your application in a fashion that is closer to your production environment. Docker allows you to set up your application runtime in such a way that it runs in exactly the same manner as it will in production, on the same operating system, with the same environment variables, and any other configuration and setup you require.
By the end of the article you'll be able to:
- Understand what Docker is and how it is used,
- Build a simple Python Django application, and
- Create a simple
Dockerfileto build a container running a Django web application server.
What is Docker, Anyway?
Docker's homepage describes Docker as follows:
"Docker is an open platform for building, shipping and running distributed applications. It gives programmers, development teams, and operations engineers the common toolbox they need to take advantage of the distributed and networked nature of modern applications."
Put simply, Docker gives you the ability to run your applications within a controlled environment, known as a container, built according to the instructions you define. A container leverages your machines resources much like a traditional virtual machine (VM). However, containers differ greatly from traditional virtual machines in terms of system resources. Traditional virtual machines operate using Hypervisors, which manage the virtualization of the underlying hardware to the VM. This means they are large in terms of system requirements.
Containers operate on a shared Linux operating system base and add simple
instructions on top
to execute and run your application or process. The difference being
that Docker doesn't require the often time-consuming process of
installing an entire OS to a virtual machine such as VirtualBox or
VMWare. Once Docker is installed, you create a container with a few
commands and then execute your applications on it via the
manages the majority of the
operating system virtualization for you, so you can get on with writing
applications and shipping them as you require in the container you have
built. Furthermore, Dockerfiles can be shared for others to build
containers and extend the instructions within them by basing their
container image on top of an existing one. The containers are also
highly portable and will run in the same manner regardless of the host
OS they are executed on. Portability is a massive plus side of Docker.
Before you begin this tutorial, ensure the following is installed to your system:
- Python 2.7 or 3.x,
- Docker (Mac users: it's recommended to use docker-machine, available via Homebrew-Cask), and
- A git repository to store your project and track changes.
Setting Up a Django web application
Starting a Django application is easy, as the Django dependency provides you with a command line tool for starting a project and generating some of the files and directory structure for you. To start, create a new folder that will house the Django application and move into that directory.
$ mkdir project $ cd project
Once in this folder, you need to add the standard Python project
dependencies file which is usually named
requirements.txt, and add the
Django and Gunicorn dependency to it. Gunicorn is a production standard web
server, which will be used later in the article. Once you have created and added
the dependencies, the file should look like this:
$ cat requirements.txt Django==1.9.4 gunicorn==19.6.0
With the Django dependency added, you can then install Django using the following command:
$ pip install -r requirements.txt
Once installed, you will find that you now have access to the
django-admin command line tool, which you can use to generate the
project files and directory structure needed for the
simple "Hello, World!" application.
$ django-admin startproject helloworld
Let's take a look at the project structure the tool has just created for you:
. ├── helloworld │ ├── helloworld │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ └── manage.py └── requirements.txt
You can read more about the structure of Django on the official website.
django-admin tool has created a skeleton application. You control the
application for development purposes using the
manage.py file, which allows you to start the development test web
server for example:
$ cd helloworld $ python manage.py runserver
The other key file of note is the
urls.py, which specifies
what URL's route to which view. Right now, you will only have the default
admin URL which we won't be using in this tutorial. Lets add a URL that will
route to a view returning the classic phrase "Hello, World!".
First, create a new file called
views.py in the same directory as
urls.py with the following content:
from django.http import HttpResponse def index(request): return HttpResponse("Hello, world!")
Now, add the following URL
url(r'', 'helloworld.views.index') to the
urls.py, which will route the base URL of
/ to our new view. The
contents of the
urls.py file should now look as follows:
from django.conf.urls import url from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'', 'helloworld.views.index'), ]
Now, when you execute the
python manage.py runserver command and visit
http://localhost:8000 in your browser, you should see the newly added
"Hello, World!" view.
The final part of our project setup is making use of the
Gunicorn web server.
This web server is robust and built
to handle production levels of traffic, whereas the included development
server of Django is more for testing purposes on your local
machine only. Once you have dockerized the application,
you will want to start up the server using Gunicorn. This is much
simpler if you write a small startup script for Docker to execute.
With that in mind, let's add a
start.sh bash script to the root of the
project, that will start our application using Gunicorn.
#!/bin/bash # Start Gunicorn processes echo Starting Gunicorn. exec gunicorn helloworld.wsgi:application \ --bind 0.0.0.0:8000 \ --workers 3
The first part of the script writes "Starting Gunicorn" to the
command line to show us that it is starting execution. The next part of
the script actually launches Gunicorn. You use
exec here so that the
execution of the command takes over the shell script, meaning that when
the Gunicorn process ends so will the script, which is what we want here.
You then pass the
gunicorn command with the first argument of
helloworld.wsgi:application. This is a reference to the
Django generated for us and is a Web Server
Gateway Interface file which is the Python standard for
web applications and servers. Without delving too much into WSGI, the
file simply defines the
application variable, and Gunicorn knows how to
interact with the object to start the web server.
You then pass two flags to the command,
bind to attach the running
server to port 8000, which you will use to communicate with the running
web server via HTTP. Finally, you specify
workers which are the
number of threads that will handle the requests coming into your
application. Gunicorn recommends this value to be set at
(2 x $num_cores) + 1. You can read more on configuration of
Gunicorn in their
Finally, make the script executable, and then test if it works by changing
directory into the project folder
helloworld and executing the script
as shown here. If everything is working fine, you should see similar output to
the one below, be able to visit
http://localhost:8000 in your browser, and
get the "Hello, World!" response.
$ chmod +x start.sh $ cd helloworld $ ../start.sh Starting Gunicorn. [2016-06-26 19:43:28 +0100]  [INFO] Starting gunicorn 19.6.0 [2016-06-26 19:43:28 +0100]  [INFO] Listening at: http://0.0.0.0:8000 (82248) [2016-06-26 19:43:28 +0100]  [INFO] Using worker: sync [2016-06-26 19:43:28 +0100]  [INFO] Booting worker with pid: 82251 [2016-06-26 19:43:28 +0100]  [INFO] Booting worker with pid: 82252 [2016-06-26 19:43:29 +0100]  [INFO] Booting worker with pid: 82253
Dockerizing the Application
You now have a simple web application that is ready to be deployed. So far, you have been using the built-in development web server that Django ships with the web framework it provides. It's time to set up the project to run the application in Docker using a more robust web server that is built to handle production levels of traffic.
One of the key goals of Docker is portability, and as such is able to be installed on a wide variety of operating systems.
For this tutorial, you will look at installing Docker Machine on MacOS. The simplest way to achieve this is via the Homebrew package manager. Instal Homebrew and run the following:
$ brew update && brew upgrade --all && brew cleanup && brew prune $ brew install docker-machine
With Docker Machine installed, you can use it to create some virtual
machines and run Docker clients. You can run
docker-machine from your
command line to see what options you have available. You'll notice that
the general idea of
docker-machine is to give you tools to create and
manage Docker clients. This means you can easily spin up a virtual
machine and use that to run whatever Docker containers you want or need
You will now create a virtual machine based on VirtualBox that will be
used to execute your
Dockerfile, which you will create shortly.
The machine you create here should try to mimic the machine
you intend to run your application on in production. This way, you
should not see any differences or quirks in your running application
neither locally nor in a deployed environment.
Create your Docker Machine using the following command:
$ docker-machine create development --driver virtualbox --virtualbox-disk-size "5000" --virtualbox-cpu-count 2 --virtualbox-memory "4096"
This will create your machine and output useful information on completion. The machine will be created with 5GB hard disk, 2 CPU's and 4GB of RAM.
To complete the setup, you need to add some environment variables to
your terminal session to allow the Docker command to connect the machine
you have just created. Handily,
docker-machine provides a simple way
to generate the environment variables and add them to your session:
$ docker-machine env development export DOCKER_TLS_VERIFY="1" export DOCKER_HOST="tcp://123.456.78.910:1112" export DOCKER_CERT_PATH="/Users/me/.docker/machine/machines/development" export DOCKER_MACHINE_NAME="development" # Run this command to configure your shell: # eval "$(docker-machine env development)"
Complete the setup by executing the command at the end of the output:
$(docker-machine env development)
Execute the following command to ensure everything is working as expected.
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE
You can now dockerize your Python application and get it running using
Writing the Dockerfile
The next stage is to add a
Dockerfile to your project. This will
allow Docker to build the image it will execute on the Docker Machine
you just created. Writing a
Dockerfile is rather
straightforward and has many elements that can be reused and/or found on
the web. Docker provides a lot of the functions that you will
require to build your image. If you need to do something more custom on
your project, Dockerfiles are flexible enough for you to do so.
The structure of a
Dockerfile can be considered a series of
instructions on how to build your container/image. For example, the vast
majority of Dockerfiles will begin by referencing a base image provided
by Docker. Typically, this will be a plain vanilla image of the latest
Ubuntu release or other Linux OS of choice. From there, you can set up
directory structures, environment variables, download
dependencies, and many other standard system tasks before finally
executing the process which will run your web application.
Dockerfile by creating an empty file named
the root of your project. Then, add the first line to the
that instructs which base image to build upon. You can create your own base
image and use that for your containers, which can be beneficial in a department
with many teams wanting to deploy their applications in the same way.
# Dockerfile # FROM directive instructing base image to build upon FROM python:2-onbuild
It's worth noting that we are using a base image that has been
created specifically to handle Python 2.X applications and a set of
instructions that will run automatically before the rest of your
Dockerfile. This base image will copy your project to
/usr/src/app, copy your requirements.txt and execute
against it. With these tasks taken care of for you, your
can then prepare to actually run your application.
Next, you can copy the
start.sh script written earlier to a path that
will be available to you in the container to be executed later in the
Dockerfile to start your server.
# COPY startup script into known file location in container COPY start.sh /start.sh
Your server will run on port 8000. Therefore, your container must be set up
to allow access to this port so that you can communicate to your running
server over HTTP. To do this, use the
EXPOSE directive to make the
# EXPOSE port 8000 to allow communication to/from server EXPOSE 8000
The final part of your
Dockerfile is to execute the start
script added earlier, which will leave your web server running on port
8000 waiting to take requests over HTTP. You can execute this script
# CMD specifcies the command to execute to start the server running. CMD ["/start.sh"] # done!
With all this in place, your final
Dockerfile should look something
# Dockerfile # FROM directive instructing base image to build upon FROM python:2-onbuild # COPY startup script into known file location in container COPY start.sh /start.sh # EXPOSE port 8000 to allow communication to/from server EXPOSE 8000 # CMD specifcies the command to execute to start the server running. CMD ["/start.sh"] # done!
You are now ready to build the container image, and then run it to see it all working together.
Building and Running the Container
Building the container is very straight forward once you have Docker and
Docker Machine on your system. The following command will look for your
Dockerfile and download all the necessary layers required to get your
container image running. Afterwards, it will run the instructions in the
Dockerfile and leave you with a container that is ready to start.
To build your container, you will use the
docker build command and
provide a tag or a name for the container, so you can reference it later
when you want to run it. The final part of the command tells Docker
which directory to build from.
$ cd <project root directory> $ docker build -t davidsale/dockerizing-python-django-app . Sending build context to Docker daemon 237.6 kB Step 1 : FROM python:2-onbuild # Executing 3 build triggers... Step 1 : COPY requirements.txt /usr/src/app/ ---> Using cache Step 1 : RUN pip install --no-cache-dir -r requirements.txt ---> Using cache Step 1 : COPY . /usr/src/app ---> 68be8680cbc4 Removing intermediate container 75ed646abcb6 Step 2 : COPY start.sh /start.sh ---> 9ef8e82c8897 Removing intermediate container fa73f966fcad Step 3 : EXPOSE 8000 ---> Running in 14c752364595 ---> 967396108654 Removing intermediate container 14c752364595 Step 4 : WORKDIR helloworld ---> Running in 09aabb677b40 ---> 5d714ceea5af Removing intermediate container 09aabb677b40 Step 5 : CMD /start.sh ---> Running in 7f73e5127cbe ---> 420a16e0260f Removing intermediate container 7f73e5127cbe Successfully built 420a16e0260f
In the output, you can see Docker processing each one of your commands before outputting that the build of the container is complete. It will give you a unique ID for the container, which can also be used in commands alongside the tag.
The final step is to run the container you have just built using Docker:
$ docker run -it -p 8000:8000 davidsale/djangoapp1 Starting Gunicorn. [2016-06-26 19:24:11 +0000]  [INFO] Starting gunicorn 19.6.0 [2016-06-26 19:24:11 +0000]  [INFO] Listening at: http://0.0.0.0:9077 (1) [2016-06-26 19:24:11 +0000]  [INFO] Using worker: sync [2016-06-26 19:24:11 +0000]  [INFO] Booting worker with pid: 11 [2016-06-26 19:24:11 +0000]  [INFO] Booting worker with pid: 12 [2016-06-26 19:24:11 +0000]  [INFO] Booting worker with pid: 17
The command tells Docker to run the container and forward the exposed
port 8000 to port 8000 on your local machine. After you run
this command, you should be able to visit
in your browser to see the "Hello, World!" response. If you were
running on a Linux machine, that would be the case. However, if running
on MacOS, then you will need to forward the ports from VirtualBox, which
is the driver we use in this tutorial so that they are accessible on
your host machine.
$ VBoxManage controlvm "development" natpf1 "tcp-port8000,tcp,,8000,,8000";
This command modifies the configuration of the virtual machine created
docker-machine earlier to forward port 8000 to your host
machine. You can run this command multiple times changing the values
for any other ports you require.
Once you have done this, visit
http://localhost:8000 in your browser.
You should be able to visit your dockerized Python Django
application running on a Gunicorn web server, ready to take thousands of
requests a second and ready to be deployed on virtually
any OS on planet using Docker.
After manually verifying that the appication is behaving as expected in Docker, the next step is the deployment. You can use Semaphore's Docker platform for automating this process.
In this tutorial, you have learned how to build a simple Python Django web application, wrap it in a production grade web server, and created a Docker container to execute your web server process.
If you enjoyed working through this article, feel free to share it and if you have any questions or comments leave them in the section below. We will do our best to answer them, or point you in the right direction.