Docker is an open-source software container management system. It allows you to create an isolated, self-contained environment to run your application. In this article we will walk thought steps needed to create a Docker image containing a Django application and demonstrate how to run it and access the container.
Benefits of Docker
So why is this Docker thing so popular these days? The basic answer is that it makes your applications portable. Django applications can make use of Python’s
virtualenv to create isolated environments, but if some of your apps use Python 2 and some use Python 3, you may need to install a whole slew of additional libraries on your host server.
With Docker on the other hand, each container includes a specific version of the Linux kernel and all other dependencies of your app. You install dependencies such as
libjpeg in the container, not on the host, so if your apps need different version of this or any other system libraries, you won’t run into problems.
- An application running in a Docker container is sandboxed and doesn’t have access to the host machine. This limits the potential security implications of a breach if someone were to exploit a bug in your app.
- Docker containers are much lighter then full VMs, because they don’t run the operating system and rely on the host’s kernel. The only processes running in the container are the ones your application requires.
- Once created the same container can be shared among developers and run in your testing and production environments.
You can find more information about Docker in its FAQ pages.
In this article
- Installing Docker
- Creating a Docker image with your Django application
- Creating an entry-point script which starts Gunicorn
- Creating a Dockerfile
- Building a Docker container image
- Running a Docker container with your application
- Running the container in detached mode
- Passing additional arguments to Gunicorn
- Running Django management commands in the container
- Backing up user media files
- Writing logs to the Docker host
- Running the container with custom Django settings
The following procedure was tested on systems running Debian 7 and Ubuntu 14.04LTS. Everything should also work other Debian-based distributions. If you’re using an RPM-based distro (such as CentOS), you will need to replace the
apt-get commands by their
yum counterparts and if you’re using FreeBSD you can install the components from ports. If you don’t have a server to play with, I can recommend the inexpensive VPS servers offered by Digital Ocean.
A Docker package is available in the Debian 8 repositories, so installing is as simple as:
$ sudo apt-get install docker.io
On Debian 7 and Ubuntu, you will have to run an installation script provided by Docker:
$ sudo apt-get install wget $ wget -qO- https://get.docker.com/ | sh
If you want to be able to run Docker containers as your user, not only as
root, you should add yourself to the group called
docker using the following command.
$ sudo usermod -aG docker `whoami`
Remember to log out and back in to pick up your new groups.
Once Docker is installed, you can test it by running the following command. This will take a few minutes, so be patient.
$ docker run -i -t ubuntu:14.04 /bin/bash Unable to find image 'ubuntu:14.04' locally 14.04: Pulling from ubuntu 511136ea3c5a: Pull complete f3c84ac3a053: Pull complete a1a958a24818: Pull complete 9fec74352904: Pull complete d0955f21bf24: Already exists Digest: sha256:2a214fd5c1c2048ef34bb79b5411efe4aa1e082b53ac1de3191992fe3ec64395 Status: Downloaded newer image for ubuntu:14.04 [email protected]:/#
The above command actually downloaded (pulled) a docker container with Ubuntu 14.04 from the official Docker base images repository. After the image was downloaded, Docker fired up the container and started
Feel free to look around. It looks like a normal Ubuntu system, except that only your
bash process is running here:
[email protected]:/# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 18160 1984 ? Ss 13:07 0:00 /bin/bash root 23 0.0 0.0 15560 1148 ? R+ 13:10 0:00 ps aux
exit or hit
Ctrl-D to exit
bash and stop the container.
You can list all running Docker containers using the
docker ps command. If you add the
--all switch you will also list containers, which were running previously, but are currently closed.
$ docker ps --all CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a8fd0ab40b7e ubuntu:14.04 "/bin/bash" 6 minutes ago Exited (0) sad_galileo
Creating a Docker image with your Django application
Let’s proceed to create the Docker image for our Django application.
We will need a directory to work in and a copy of our application’s source. Let’s create a directory called
dockyard, where we will be making our containers and a subdirectory for the Docker image we are creating.
$ mkdir -p dockyard/hello_django_docker $ cd dockyard/hello_django_docker
For the purposes of this article I uploaded a sample Django app to Github, so we can grab a copy of the source code using the following command:
$ git clone https://github.com/postrational/hello_django.git
We should now have a working directory containing the source code of our Django application. Please note that I assume that a PIP-compatible
requirements.txt file and the Django
manage.py script are in the main source code directory named
hello_django. The project’s
settings.py file is located in the subdirectory
~/dockyard/hello_django_docker # Our working directory `-- hello_django # Main project source directroy (from repo) |-- hello | |-- __init__.py | |-- settings.py # Project settings | |-- urls.py | `-- wsgi.py # Project's WSGI start script |-- manage.py # Django's management command |-- project_application_1 | `-- (more files...) |-- project_application_2 | `-- (more files...) `-- requirements.txt # File generated using pip freeze
With the code in place, let’s proceed to create Docker-related files.
Create an entry-point script
A Docker container can use an script as the default command which will be fired when the container is run. In our case we will use the following script as the entry point.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
The above entry point script does a few things:
- starts by running a few management commands, to apply the changes made in the application source code
- proceeds to create two log files in
/srv/logs/and to run the
tail -fcommand which will output the logs to the console
- finally is starts
gunicornwhich will serve our Django application
"$@"notation in the last line will allow you to pass additional arguments to
gunicornwhen you start the container.
Save the script as
docker-entrypoint.sh and change its permissions to make it executable:
$ chmod u+x docker-entrypoint.sh
Create the Dockerfile
We will now make the container definition file named
Dockerfile. Create the file and give it the following content.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
Dockerfile specifies the steps needed to create our container image:
- Use Ubuntu 14.04 as the base image of our container. Docker will download the image named
ubuntu:14.04and all subsequent commands will be executed inside of a running container with this base OS.
- We set some environment variables in our container using the
ENVcommands. These variables can be used later in the
Dockerfile, but will also be available in the environment of all programs executed in the container. For this reason we use a prefix (
DOCKYARD), so that our variables don’t accidentally override anything else.
- We run
apt-getto install any system tools and libraries we may need.
- We prepare all directories our application will use in the
/srv/directory of our container. Using the
VOLUMEcommand we make some of these directories available to other containers. This will come in handy later, see “Backing up user media files”.
- Next we use the
COPYcommand to copy the source code of our app into the container.
- We use the
requirements.txtfile from the source code to install Python dependencies.
- We use the
EXPOSEcommand to make the Gunicorn port (8000) accessible outside of our container.
- Finally we copy over
docker-entrypoint.shand define it as the script which should execute when the container is started.
You may want to refer to the Dockerfile documentation for more information about the available commands.
Build the Docker container image
All the pieces are now in place. Let’s just review to make sure the files we created are in the right spot:
~/dockyard/hello_django_docker |-- docker-entrypoint.sh # We added the executable entry script here |-- Dockerfile # And the Dockerfile here `-- hello_django |-- hello | |-- __init__.py | |-- settings.py | |-- urls.py | `-- wsgi.py |-- manage.py |-- project_application_1 |-- project_application_2 `-- requirements.txt
We can now build the Docker container image. I will call the image
Docker image names follow the convention of
user-name/image-name. When you upload your image to a repository it will be added to your user account based on the name.
$ docker build -t michal/hello_django ~/dockyard/hello_django_docker Sending build context to Docker daemon 80.38 kB Sending build context to Docker daemon Step 0 : FROM ubuntu:14.04 (...) Successfully built 03c7aeb70a09
You will see output of many commands as the container is put together. At the end you should be able to see you newly created image when running the
docker images command:
$ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE michal/hello_django latest 17a441b8bdbd 2 seconds ago 394.6 MB
Running a Docker container with your application
Now that the container image is created, we can use it to start a container.
$ docker run --publish=8001:8000 michal/hello_django:latest
This command starts a new container from the
It also makes the container’s port 8000, which is the default Gunicorn port available on port 8001 of the Docker host. Reassigning ports in this way allows you to have multiple Django applications running in different containers. You just need to assign port 8000 of each container to a different port on the Docker host.
Once the container is started in this way, you should be able to navigate to port 8001 of the Docker host and see the famous Django start page declaring that “It worked!”.
Visit your docker host in a browser (use the IP or domain of you machine): http://docker.host:8001
You can stop the container by hitting Ctrl-C in the terminal.
Running the container in detached mode
Starting and stopping a container as we did above is useful for debugging, but in most other cases you will want to start the container without attaching it to a terminal session.
--detach=true argument when starting the container.
$ docker run --name=hello_django \ --detach=true \ --restart=always \ --publish=8001:8000 \ michal/hello_django:latest 81512acac0e4875a218587737ea31ce09aae746e4a8248461e16ab601bb1b0aa
Note, that we specified the name of the container (
--name=hello_django). We also specified that the container should always be restarted if the process inside stops or crashes (
You can now check that the container is running by listing all running Docker containers using the
docker ps command.
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2c7cbc6fd5b8 michal/hello_django:latest "/docker-entrypoint. 3 seconds ago Up 3 seconds 0.0.0.0:8001->8000/tcp hello_django
You can also follow the logs which are being output by the processes running in the container using the
docker logs command.
$ docker logs -f hello_django
You can stop and restart the container:
$ docker stop hello_django $ docker start hello_django $ docker restart hello_django
And you can delete the container when you’re done with it.
$ docker stop hello_django $ docker rm hello_django
Passing additional arguments to Gunicorn
Docker will pass any arguments specified after the name of the image to the command which starts the container. In our case those arguments will be passed to the
docker-entrypoint.sh script, which in turn will pass them to the
gunicorn command which it starts.
If we want to change the number of Gunicorn worker processes running in the container, we just need to add the
--workers argument to the end of
$ docker run \ michal/hello_django:latest \ --workers 5
Backing up user media files
As we noted earlier, the
VOLUME command in the Dockerfile made some directories accessible from other containers. If you would like to make a backup up of files stored in the
/srv/media/ directory, you can start another container using the
--volumes-from argument. The volume directories will be accessible in the newly stared container.
$ docker run --rm -i -t --volumes-from=hello_django ubuntu:14.04 /bin/bash [email protected]:/# cd /srv/media/ [email protected]:/srv/media# apt-get install -y ssh-client [email protected]:/srv/media# scp -r * [email protected]:~/path/to/backup
You can find more information about managing volumes in containers in the docs.
Writing logs to a directory on the Docker host
You can also mount a Docker host machine directory as a volume inside the container, using the
$ sudo mkdir -p /var/log/webapps/hello $ docker run --name=hello_django \ --detach=true \ --restart=always \ --publish=8001:8000 \ --volume=/var/log/webapps/hello:/srv/logs \ michal/hello_django:latest \ --workers 5 $ tail -f /var/log/webapps/hello/*.log
You can find more information about managing volumes in containers in the docs.
Using custom Django settings
In many cases you will want to use different settings when running your Django application in development, during testing and in production.
In order to do this, create a new settings file on the Docker host. Let’s assume you save it in the directory
/etc/webapps/hello_django/local_settings.py. In the file import all of your project’s default settings and override only what’s needed.
1 2 3 4
You can then use the
--volume argument to mount the single file
local_settings.py into your container and use the
--env argument to set the
DJANGO_SETTINGS_MODULE environment variable to the new settings module.
$ docker run --name=hello_django \ --detach=true \ --restart=always \ --publish=8001:8000 \ --env="DJANGO_SETTINGS_MODULE=hello.local_settings" \ --volume=/etc/webapps/hello_django/local_settings.py:/srv/hello_django/hello/local_settings.py \ michal/hello_django:latest \ --workers 5
Running Django management commands in the container
You will probably want to run some Django management commands on the Docker host. In order to do this you can start another container from the same image and specify another command which will be started instead of the entrypoint script. If you use
/bin/bash, you will arrive at a shell in the container. From here you can execute Django’s
$ docker run --rm -i -t --entrypoint=/bin/bash michal/hello_django:latest [email protected]:/srv/hello_django# ./manage.py createsuperuser Username (leave blank to use 'root'): michal Email address: [email protected] Password: Password (again): Superuser created successfully.
Thanks for reading. If you find any issues with this article of have any other ideas, leave a comment below.