Django Docker Intro

This tutorial aims to provide an introduction to using Docker with Django for developers who already have some familiarity with Django.

Contents:

Prerequisites

You should ensure that you have at least 5 GB of free disk space to install and build the required Docker images.

You will need git, which may already be installed, e.g. if you have installed the developer tools on macOS by typing xcode-select --install into a Terminal window (see below). If you don’t already have git installed, you can download it from https://git-scm.com/

macOS

On macOS, you can use the Terminal application, which can be found in macOS’s Spotlight Search Tool:

_images/macos-terminal.png

And you will need Docker, described further in the Getting Docker section.

Windows

On Windows, Docker advises that you can use either Command Prompt or Power Shell, but not Power Shell ISE: https://docs.docker.com/docker-for-windows/

If running Windows 10, you could use Windows Subsystem for Linux (WSL):

Linux

If you don’t already have git installed, you can following the instructions at https://git-scm.com/download/linux

Instructions for installing Docker will be provided in the Getting Docker section.

Getting Docker

Docker Community Edition

macOS and Windows 10 users can download a Docker Community Edition (CE) installer from:

Registration is required and can be done at https://store.docker.com/signup

Users of older operating systems may be able to use Docker Toolbox instead:

Docker on Linux

Digital Ocean has some excellent guides for installing Docker on Linux:

Linux users should note that docker-compose is required, but may not be installed automatically by following the guides above. On Ubuntu/Debian, after following the appropriate guide above, you can run:

sudo apt install docker-compose

Testing your Docker installation

Depending on your operating system and the way you install Docker, you may need to manually launch the Docker application / service after installation, before being able to run docker commands.

Try running pulling and running Docker’s hello-world image:

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
d1725b59e92d: Pull complete
Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

Docker Hello World

What Is Docker?

According to Wikipedia, Docker is a computer program that performs operating-system-level virtualization, also known as “containerization”.

Docker images are analogous to virtual machine images, but they can be much smaller than traditional virtual machine images, because instances of Docker images (which are called “containers”) usually only need to run one particular type of service or process.

Running Docker’s Hello World

Depending on your operating system and the way you install Docker, you may need to manually launch the Docker application / service after installation, before being able to run docker commands.

Try running pulling and running Docker’s hello-world image:

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
d1725b59e92d: Pull complete
Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

Listing Docker Images Stored Locally

We can run the docker image ls command to list the Docker images stored locally:

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              fce289e99eb9        2 months ago        1.84kB

In this case, we only have one Docker image. It was pulled from DockerHub (i.e. downloaded from DockerHub), which we could have done by typing:

$ docker pull hello-world

However the docker run command above pulled the image automatically, and then created a container (instance) of that image and ran the container, resulting in the Hello from Docker! message shown above.

Listing Running and Stopped Docker Containers

So if a container was created, is it still running? We can find out by running:

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

In this case, docker ps shows that no containers are currently running. To list all containers, included those which have stopped running, we can run:

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                         PORTS               NAMES
636651c57ab7        hello-world         "/hello"            About an hour ago   Exited (0) About an hour ago                       inspiring_goldwasser

which shows us that a container was created from the hello-world image, and that it exited with a (0) (success) status code.

As we no longer need this stopped container, we can remove it, using its CONTAINER ID:

$ docker rm 636651c57ab7

Tagging Docker Images and Pushing to DockerHub

Given an existing image, e.g. this one:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              fce289e99eb9        2 months ago        1.84kB

we can create one or more new tags for it, and push the tags to DockerHub:

$ docker tag fce289e99eb9 jameswettenhall/hello-world:1.0
$ docker tag fce289e99eb9 jameswettenhall/hello-world:latest
$ docker login -u jameswettenhall
$ docker push jameswettenhall/hello-world:1.0
$ docker push jameswettenhall/hello-world:latest

Now the image tags are available on DockerHub at: https://cloud.docker.com/repository/docker/jameswettenhall/hello-world

Django In Docker

Why Run Django in Docker?

Django is a web application framework which can be used to develop full-stack web applications (using Django templates to render the front-end pages), or it can be used to develop a backend API (e.g. using the Django REST framework) which a front-end application can interact with.

Docker can help with CI (Continuous Integration) and CD (Continuous Deployment) of Django applications.

Simulating a Production Environment in Local Testing

It is easy to test a Django application locally using your local operating system (e.g. macOS) and a simple database engine (e.g. SQLite), but what if you want to run your tests in the same environment that will be used in production? As you become more advanced with Docker, you will want to use an orchestration tool like Kubernetes to run Docker containers in production. But if your production environment is not yet containerized, you can run your Django application’s tests in a simulated version of your production server environment, by using a Docker image matching your server’s operating system version. For example, if your server is running Ubuntu 18.04, you could run docker pull ubuntu:18.04 to download a Docker image for Ubuntu 18.04, and run your tests in a container built from that image. You can also download an image for the database engine you use on your production server (e.g. MySQL or PostgreSQL) with docker pull mysql or docker pull postgres.

Continuous Integration

Each time you push a commit to your remote repository (e.g. GitHub / GitLab / BitBucket), you can set up a web hook so that your tests are automatically run in a Docker container matching your production environment, using a CI tool like Travis CI, Semaphore CI, CircleCI or Jenkins.

Continuous Deployment

If you containerize your production environment, and use a container orchestration tool like Kubernetes or Docker Swarm, then you can define replica sets (sets of Docker container instances) which can be updated potentially with zero downtime by replacing each instance within a replica set with a newer instance created from a new image pulled from DockerHub which has passed CI test before being pushed to DockerHub.

Containerizing the Django Polls Application

If you are familiar with Django, then you are probably familiar with the “Polls” application, implemented in the Writing your first Django app tutorial at https://docs.djangoproject.com/en/2.1/intro/tutorial01/

This tutorial demonstrates how to run the “Polls” application in a Docker container. It also demonstrates how to add a RESTful API to the “Polls” application, so that it can be split into separate front-end and back-end applications which could run in separate Docker containers.

The Django Polls Application

We’ll use the result of running through Django’s “Writing your first Django app” tutorial as the starting point for this tutorial. The Django tutorial can be found at https://docs.djangoproject.com/en/2.1/intro/tutorial01/

Security Disclaimer

Please note that we will not be listing all of the steps required to secure the application for a production environment. See: https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/

Repository Implementing Polls Application

We’ll use the following repository to illustrate how to run the “Polls” application in a Docker container: https://github.com/jameswettenhall/django-polls-tutorial

Each step of the tutorial will use a different branch of the repository:

Polls Application Without Docker

Firstly, we’ll run the Polls application without Docker, using the master branch: https://github.com/jameswettenhall/django-polls-tutorial/blob/master/README.md

Adding a RESTful API

Then we’ll add a RESTful API to the Polls application (still without Docker), using the api branch: https://github.com/jameswettenhall/django-polls-tutorial/blob/api/README.md

Serializing Settings With YAML

Then we’ll discuss how to serialize the application’s settings in YAML and make them available to the containerized application, using the yaml branch: https://github.com/jameswettenhall/django-polls-tutorial/blob/yaml/README.md

Docker Implementation of Polls Application

Then we’ll discuss how to run the application in a Docker container, with a MySQL database server provided by another Docker container, using the docker branch: https://github.com/jameswettenhall/django-polls-tutorial/blob/docker/README.md

Exercises and Further Reading

Exercise: Separate Front-End from Polls application

Re-write the front-end of the Polls application in a front-end framework of your choice, e.g. React, Angular or Vue.js, instead of using Django templates. You can use the REST API we created as the data source for for your front-end application.

Kubernetes in Under 3 Hours

Read the “Learn Kubernetes in Under 3 Hours” guide here: https://medium.freecodecamp.org/learn-kubernetes-in-under-3-hours-a-detailed-guide-to-orchestrating-containers-114ff420e882 and learn how to install Minikube to run Kubernetes locally.

Exercise: Create a Kubernetes (Minikube) deployment of the Polls application

Using Minikube, create a local Kubernetes deployment of the Polls application and experiment with changing the number of replicas required of each container (front-end, back-end, MySQL).

Docker Cheat Sheet

Listing images, containers and volumes

List all of your images:

docker image ls

or to list images used by the created containers:

docker-compose images

List all of your running containers:

docker ps

List all of your containers (including stopped containers):

docker ps -a

or:

docker-compose ps

List services launched from your docker-compose.yml:

docker-compose config --services

List all of your volumes:

docker volume ls

Inspect a volume, including its Mountpoint:

docker volume inspect <volume_name>

List named volumes referenced in your docker-compose.yml:

docker-compose config --volumes

Building a Docker image from a Dockerfile

To build an image from a Dockerfile:

docker build

or if you have a docker-compose.yml:

docker-compose build

Running an interactive shell inside a container

You can either launch a new container to run an interactive shell allowing you to browse the contents of an image, or you can launch a shell inside an already running container.

To launch a new container which runs an interactive bash shell from an existing image, the method depends on whether the image has an entrypoint, and whether you want to override it.

If the image (e.g. monashmerc/mytardis_django) has an entrypoint, you can run /bin/bash instead of the image’s default entrypoint script and then look at the entrypoint script from the container you just created:

$ docker run -it --entrypoint /bin/bash monashmerc/mytardis_django
root@47d3e3917ed2:/usr/src/app# cat /docker-entrypoint.sh

If the image (e.g. nginx) doesn’t have an entrypoint, you can create a container to run bash as follows:

docker run -it IMAGE /bin/bash
# e.g. docker run -it nginx /bin/bash

To launch a bash shell inside an already running container:

docker exec -it CONTAINER /bin/bash
# e.g. docker exec -it mytardis_celery_1 /bin/bash

To launch a bash shell inside a service (listed by docker-compose config –services):

docker-compose exec SERVICE /bin/bash
# e.g. docker-compose exec celery /bin/bash

Pulling in images required in your docker-compose.yml

To pull images (from DockerHub) required in your docker-compose.yml:

docker-compose pull

Starting up services defined in docker-compose.yml

To start up the services / containers defined in docker-compose.yml:

docker-compose up -d

where ‘-d’ is “Detached mode” for running containers in the background.

To check the logs of services / containers launched with docker-compose up -d:

docker-compose logs -f

where ‘-f’ means “Follow log output” and will continue displaying updates to logs until you press Control-C.

To stop services / containers launched with docker-compose up -d:

docker-compose down

Deleting all images, containers and volumes

WARNING: The commands below are destructive and are intended to be used when you want to clean up a test Docker environment and you don’t have any valuable Docker images or volumes you want to keep.

Remove all stopped containers, and receive an error for each container which can’t be removed because it is still running:

docker rm $(docker ps -aq)

Remove all containers, even those which are still running:

docker rm -f $(docker ps -aq)

Remove all volumes not being used by a container:

docker volume prune

Remove an image:

docker rmi <image_id>

Remove all images:

docker rmi $(docker image ls -q)

If attempting to remove images gives an error like image is referenced in multiple repositories (because you have created multiple tags from an image), you can add -f to remove them forcefully:

docker rmi -f $(docker image ls -q)