We have discussed the project setup and creation of endpoints for our todo application over the last parts. In today's post, we will see how we can dockerize this django application so that it can be deployed on any cloud with minimal to no configuration.

What is Docker

Docker is a tool that leverages the concept of linux containers and makes it easier to use these containers with ease. But what is a container in the first place? There are a lot of formal definitions available for a container on the internet. but I find it very easy to think of container as a set of processes and their dependencies packaged in a file system isolated from the host operating system. You can think of them as light weight virtual machines for time being ( but containers are not virtual machines ).

Why use Docker

There are 2 major problems that docker solves really well

  • Configuration Management
    • Docker lets the developer package all the dependencies in a distributable package
    • So the operations team doesn't have to worry about configuration management every time they deploy the application
  • Scalability
    • Docker containers are light weight processes, it usually takes only a few seconds to bring up a container which is very fast when compared to bringing up a virtual machine and setting up all dependencies
    • Thus it makes your application scale horizontally according to the demands of traffic quickly

Docker Terminology

Engine

This is the heart of any docker deployment. This is made of two major components

  • Daemon
    This is the process that manages all docker objects like images, containers and networks. Functionalities of daemon are exposed via the docker API.
  • Client
    This is the primary way to interact with docker daemon. It sends commands to docker daemon according to user inputs

Images

Images are the templates to start and run containers. These are anologous to OS disk images that we use in virtual machines. Users can create their own images or download and use existing images from a registry like dockerhub.

Containers

Containers are runnable instances of docker images. Like we defined before, it is isolated set of processes with all dependencies. User can run, stop, delete or modify a container using the docker client or API.

Registry

Registry is a server where image repositories can be stored and served from efficiently. We can use a public registry or can setup our own private registry. This is anologous to github

Creating todo Docker Image

Our docker container should contain our application code and all its dependencies. We need to provide instructions to docker for it to build an image for us. We must provide these instructions in a file called DockerFile.

Lets get started by creating Dockerfile in the root directory of project.

The first instruction in a typical docker file is to specify a base image, we can do that with the use of FROM command. We will be using ubuntu 18.04 as our base image. Now we need to install all the required dependencies like python and pip and all for our django app to run on it. If it were a ubuntu machine, we install any package by running apt install command on a shell like bash. In Dockerfile, we have the RUN command to tell docker to execute any shell command.

The logical next step is to copy all the application files to the container, we can do this in Dockerfile by COPY command. Now we will write the RUN  command to install required python packages using pip.

The next command we used in Dockerfile is EXPOSE. This command is to inform docker engine that our application listens on a given port and protocol.

Finally we need to tell docker to start our application every time the container starts, for this purpose we will use CMD.  This command takes a list of arguments, first being the actual command to be executed on shell and rest all will be passed as arguments to it.

Here in CMD we havent started our application using manage.py runserver instead we used a command called uwsgi which is an application server. This server needs some configuration like the module it has to start and the port on which it has to run, this can be provided either in command line arguments or in a file. We have created a basic configuration file for uwsgi as below. Please do not use this in production.

Building the Image

So now we have a Dockerfile, lets go ahead and build an image with this Dockerfile. We can do this by using docker build command. We need to pass the path to the location where Dockerfile is. Docker tries to find the Dockerfile automatically and attempts to build the image by running commands given in the Dockerfile. If you named your Dockerfile something else, you need to specify the filename in the file option. We can name our image using the tag option.

docker build -t todo .

Don't miss the '.'(dot) at the end of the command, I am running the command from the root directory of project, where Dockerfile is located and this '.' is used for current directory in linux. If you are running this from some other path, replace the dot with the path to the folder in which your Dockerfile is present.

Output of docker build command

If everything went well, you should be able to see the image that you just built by running the command docker images.

Output of docker images command

Running the container

We can run the container with docker run todo, if you open up your browser and try hitting your service at http://localhost:8000, you will see a error page. This is because we havent bound the container port with the host port yet. This means that the app has started and is listening on the specified port, but on the docker's internal network only. To make it accessible from the host machine ( your laptop/desktop ) you must tell docker which host port to bind with which container port. We can do that using the -p option on docker run command like this docker run -p 8001:8000 todo. You can now goto http://localhost:8001 in your browser and can see your django app page. If it worked, great job, kudos to you for making it so far. However the container is still attached to current terminal i.e. the container processes' standard input and output are attached to the current terminal, you can chose to run your container in background by specifying the -d flag on the run command. So the final command looks like this docker run -d -p 8001:8000 todo. You can see the running containers with command docker ps.

With this we mark the end of this part. In the next part of this series we will quickly demo how the same container can be run on multiple cloud providers.

Thanks a lot for reading this article. Please feel free to share any feedback or ask any questions that you have by mailing me at vinodkv2511@gmail.com.