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
Dockerfile. Docker 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
wsgi file 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 documentation.
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 on it.
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 the
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
Dockerfile in the root of your project. Then, add the first line to the
Dockerfile 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
pip install against it. With these tasks taken care of for you, your
Dockerfile 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 port available:
# 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 using the
# 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 like this:
# 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 $ docker build -t designmycodes/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 designmycodes/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
http://localhost:8000 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 using
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.