Dockerize a Flask Project

I’ve been a Python programmer for a while, but only recently have I been exposed to Docker.

For those of you who don’t know, Docker is the leading software container platform. Running LXC (Linux Containers) in its core, it allows you recreate the same environment for an application regardless of where you run it. Docker has many different uses, and allows from great team collaboration. You can read more on Docker and Containers in their page:

Essentially, if a Docker image works in your computer, it should work in all of them, because the image is independent of whatever dependencies you might have installed in your system. This is why it comes in handy when making your Flask application; you can make sure the exact versions of the dependencies you need are installed, and when it’s time to run the image, just tell it which port you want to use.


So, let’s get started. For this tutorial I will be using Ubuntu 17.04 64-bit inside of a VirtualBox VM, but you’re free to sue whatever you want. The commands should be similar in Mac or other Unix based systems.

$ sudo apt-get install docker.io

This command will install Docker in Ubuntu, but check out their page on instructions for all other OS, it should straight forward and painless.

To make sure you installed it correctly, run the docker version command:

$ docker version
Client:
Version: 1.12.6
API version: 1.24
Go version: go1.7.4
Git commit: 78d1802
Built: Tue Mar 14 09:47:15 2017
OS/Arch: linux/amd64
Cannot connect to the Docker daemon. Is the docker daemon running on this host?

If you are running Ubuntu like myself you might see this last line. No worries though, it’s just because our user doesn’t have permission to access the folder /var/run/docker.sock which we can fix by running the same command as sudo

Now if you’d rather not have to sudo every time, we can add permissions to our user by adding user to the docker group so that it’s not necessary to do this again:

sudo usermod -aG docker $USER

If you restart your system (or manually run the daemon this time if you don’t want to restart) and run docker version again, you should have no problems.


Perfect! Now let’s make a sample Flask application to try this on. Don’t worry, it will be essentially the same process for bigger projects.

This is pretty simple as Flask projects go, but if you don’t understand what’s going, maybe you should go learn Flask first! Check out this tutorial which goes over all the basics as well as showing you how to set up a virtual environment.

We will need to install Flask. We’ll do so using pip , which in Ubuntu is installed running apt-get install python-pip but you can find how to easily install in other platforms here.

Now that we have pip, install Flask by running pip install Flask and we’re ready to go.

I made a folder named flask-docker-tutorial and put my python file inside there. We can test it out by running python flask-docker.py

$ python flask-docker.py 
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 135-043-124

We go to the URL in our browser and it works!

To make our live easier in the Docker building process, let’s also create a requirements.txt file inside our project folder.

If you have multiple dependencies you can run pip freeze > requirements.txt inside the project directory, but be careful because the requirements file will also contain many dependencies that are not available directly through pip but that other libraries resolve for you.


Great, now let’s get to “Dockerizing” our app.

To automatize building Docker images, Docker uses a file named Dockerfile. This file specifies everything from the files you need to use, to the commands you need to run. For this we write instructions that will be executed in order from top to bottom. There’s a few instructions that can be used and I’ll quickly describe them:

ADD: Takes as input a file and a destination. It essentially grabs files from the host machine (your computer) and places them inside the Docker image. You can also use url’s as arguments so that it downloads data from the internet into the Docker image instead, as well as decompressing files from known compression formats (I’ve been told not to trust this too much though). COPY does pretty much the same without the URL or decompression features.

# Usage: ADD [source directory or URL] [destination directory]
ADD /my_app_folder /my_app_folder

CMD: It is used to run a specific command, but this command is executed at the moment of initiating the container, not when building the image. For example we would use CMD to run our python file when the image is initiated. If you want to run a command at the moment of building the image, we would use RUN, which I’ll describe later.

# Usage 1: CMD application "argument", "argument", ..
CMD "echo" "Hello docker!"

ENTRYPOINT: Tells our image what is the default application we will be using for our commands. So if we were to tell our image that our entry point is “python” then, when we run CMD, we would just give it the arguments, as it would know that python is the application to use to run these commands.

# Usage: ENTRYPOINT application "argument", "argument", ..
# Remember: arguments are optional. They can be provided by CMD
# or during the creation of a container.
ENTRYPOINT echo

# Usage example with CMD:
# Arguments set with CMD can be overridden during *run*
CMD "Hello docker!"
ENTRYPOINT echo

ENV: Sets environment variables by giving it a key = value pair. Really makes your life easier.

# Usage: ENV key value
ENV SERVER_WORKS 4

EXPOSE: It’s used to associate a specific port to communicate with other containers. I would avoid using EXPOSE, as usually the port redirection is done by each host at the moment of running. However it can be useful when having some inter-container communication. This StackOverflow answer is pretty good, take a look if you’re interested in reading more about it.

# Usage: EXPOSE [port]
EXPOSE 8080

FROM: It needs to be the first command declared in the Dockerfile. It indicates what image you are basing your image on. It can be any image, even one you’ve made yourself before. If not in the machine, Docker will get it from DockerHub.

# Usage: FROM [image name]
FROM ubuntu

MAINTAINER: Used to give yourself some credit. Can be placed anywhere, and it doesn’t execute.

# Usage: MAINTAINER [name]
MAINTAINER authors_name

RUN: I mentioned it a bit earlier, this is how you run commands at the moment of building your Docker image. You would use this to install all dependencies your system will have, and have it ready to just go and run your application.

# Usage: RUN [command]
RUN aptitude install -y riak

USER: Used to specify the uid or username of the user you want to run the container based on. This can be used to manage permissions to be had within the container. On my research I stumbled on this story that helps understand a bit more about uid and gid, check it out:

# Usage: USER [UID]
USER 751

VOLUME: Allows a container to have access to a folder on the host machine.

# Usage: VOLUME ["/dir_1", "/dir_2" ..]
VOLUME ["/my_files"]

WORKDIR: Indicates in which folder the CMD commands should be executed.

# Usage: WORKDIR /path
WORKDIR ~/

Most of this info on Dockerfile and more can be found in this DigitalOcean tutorial, it’s very good and goes over setting up a Docker image with MongoDB..


Let’s make our Dockerfile!

Alright, so, we’ll finally make our own Dockerfile. Let’s use what we learned about Dockerfile and put it to work.

First lines should be FROM and MAINTAINER. We’ll build our image based on Ubuntu, and under maintainer we’ll put our info.

FROM ubuntu:latest
MAINTAINER Angello Maggio "angellom@jfrog.com"

Then we’ll install everything we need to get our system.

RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential

This should get us python and pip ready to go.

ADD . /flask-app
WORKDIR /flask-app
RUN pip install -r requirements.txt

With these 3 lines we copy all of our project into a folder /flask-app inside of the Docker image, and once there we tell pip to install of our requirements.

ENTRYPOINT ["python"]
CMD ["flask-docker.py"]

Finally we tell the image to use Python as our application, and whenever initiated run our Flask server.

Great! Let’s test it out. Run the following command in our project folder:

docker build -t my-flask-image:latest .

The option -t asks for an argument name:tag, and the ‘.’ at the end is the path to our folder. Once ran, the output in the command line should go step by step through the Dockerfile, and hopefully no errors will pop up! Common errors for me have had to do with me creating my requirements.txt file using pip freeze so watch out for that.

And that’s it! Try running $ docker images and see if your image is there.

images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-flask-image latest 65024886bcee About a minute ago 430 MB
ubuntu latest d355ed3537e9 2 weeks ago 119.2 MB

It’s there, let’s run it:

$ docker run -d -p 5000:5000 my-flask-image

-p 5000:5000 tells docker to mirror the container’s 5000 port into the host machine’s 5000 port, and there we go!

run docker ps to make sure it’s running, and check your http://localhost:5000

You have built your first Docker image. This should run on any computer whether they have Python and Flask installed or not. Play around with it, make more complex projects, and make your own Dockerfile to keep learning.

This tutorial was based on this site’s tutorial that goes more to the point:

but I hope the details and step-by-step helped you along the way.