June 21, 2017

Dockerizing Stuff for Fun and Profit

When I want to play with $things, I mostly use virtual machines as throw-away systems that I can mess up with odd software I don't want to install on my Mac or that simply does not run on it. A good while ago I looked into docker as a more lightweight and flexible alternative to a VM. However, I put docker aside as it wasn't usable on a Mac at all. This has changed greatly since that point in time. Now installing docker is a no-brainer and it works fine on a Mac.

Time to play a little and to find out how I can dockerize a tiny Python app that I wrote a while ago. photogen.py takes photos as input, and generates markdown for my webpage that embeds those images, links to Google Maps links, etc. The app is written in Python 3 and uses some pips. And yes, I know that you can install all requirements on the Mac. This is just a fun project to test things.

As I never dockerized anything so far, my approach was to start with a very basic Dockerfile that I put into some empty folder and enhance the file gradually:

(File name: Dockerfile)
FROM debian
RUN apt-get update; apt-get upgrade -y

Now you can run docker build -t python . so that docker builds a new image named python based upon the official debian image. Furthermore, the new image is updated and upgraded. After the process completes, you can start the new python image: docker run --rm -it python /bin/bash. Congrats. You are now using the bash of your new docker container.

Note: the --rm flag takes care that docker purges the state of this running container when it is shut down; We do not need it at the moment. The command(s) -it take care that bash is started in an interactive terminal session.

Some short words about images and containers: Images are built from Dockerfiles and contain all those $things that are specified in the Dockerfile. Besides altering the Dockerfile and building the image again, you have no option to modify the content of that image. This is because whenever you start an image, docker uses a copy-on-write file system to track the changes you make. These changes are stored in a container but never in the image itself.

As you mostly don't know the exact names and installation methods (apt, pip) of those things you need to install beforehand, you can now play with the system to figure this out interactively. After figuring correct names and install methods, you can update the Dockerfile. You could now build again…

(File name: Dockerfile)
FROM debian
RUN apt-get update; apt-get upgrade -y
RUN apt-get install -y python3 python3-pip
RUN pip3 install exifread geopy

… or rewrite the file in a tad nicer way by specifying what pip should install in a requirements.txt file:

(File name: requirements.txt)
exifread
geopy

(File name: Dockerfile)
FROM debian
RUN apt-get update; apt-get upgrade -y
RUN apt-get install -y python3 python3-pip
ADD requirements.txt /
RUN pip3 install -r requirements.txt

Note: ADD takes care that requirements.txt is copied into the image's root directory /.

Using the same trick, we can “install” your app, in my example photogen.py, in the image. Furthermore, we make it executable:

(File name: Dockerfile)
FROM debian
RUN apt-get update; apt-get upgrade -y
RUN apt-get install -y python3 python3-pip
ADD requirements.txt /
RUN pip3 install -r requirements.txt
RUN rm ./requirements.txt
ADD photogen.py /app/
RUN chmod +x /app/photogen.py

The interesting question now is how the images can be fed into the running container. The basic idea is to specify a workdir that can be accessed from within the container and “mount” the folder with the pictures living on the host computer into workdir. This requires a small addition to the Dockerfile:

(File name: Dockerfile)
FROM debian
RUN apt-get update; apt-get upgrade -y
RUN apt-get install -y python3 python3-pip
ADD requirements.txt /
RUN pip3 install -r requirements.txt
RUN rm ./requirements.txt
ADD photogen.py /app/
RUN chmod +x /app/photogen.py
VOLUME /workdir

After rebuilding, you need to start the image with additional parameters: docker run --rm -v $(pwd):/workdir -it python /bin/bash. Note: we mount the current directory into the container's workdir.

In the container, I started the Python script and immediately got an error that indicated that no locales are set. Hence the container cannot deal with UTF-8 encoded characters. In the bash of my container I set the locale as follows export LC_CTYPE=C.UTF-8 and the Python app worked fine. Time for another modification of the Dockerfile to include this setting to the image:

(File name: Dockerfile)
FROM debian
RUN apt-get update; apt-get upgrade -y
RUN apt-get install -y python3 python3-pip
ADD requirements.txt /
RUN pip3 install -r requirements.txt
RUN rm ./requirements.txt
ADD photogen.py /app/
RUN chmod +x /app/photogen.py
VOLUME /workdir
ENV LC_CTYPE C.UTF-8

Now let us try to run photogen.py directly from the shell of the host computer: docker run --rm -v $(pwd):/workdir python /app/photogen.py /workdir. Works flawlessly.

Testing the Dockerfile as above and building new images again and again creates a lot of deprecated image versions: docker images shows such images with “none” in their names. You can delete such images by running docker rmi $(docker images | grep none | awk '{ print $3 }').

All files are on my GitHub.

© holger 2015 - 2020 |