The Docker framework can be an extremely useful tool for any Python developer who wants to run their Python programs (either for development or testing purposes) in a certain isolated environment with a pre-defined set of system and Python packages installed. Docker can also help with testing your Python code against multiple versions of the Python packages in multiple operating systems.

The beauty of Docker is that you don’t need to understand the intricate details of how Docker technology works to take advantage of it. This blog post contains recipes any Python developer can benefit from regardless how experienced you are with Docker and containerization techniques.

Each recipe or scenario is based on a problem that one may need to solve and provides a solution in form of a Dockerfile file, a docker build, and a docker run command.

Basic setup

The base Docker image you’ll be using will likely to be different depending on a number of factors, however, to keep the build time short, I’ll be using the alpine image in most cases.

Dockerfile contents:

FROM python:3.7-alpine
CMD ["python3", "--version"]

The build step contains the -t flag which defines the tag name of the image that will be built. The dot (. ) tells Docker to use the the file named Dockerfile in the current directory.

Building a Docker image:

$ docker build -t snake .

When an image is run, the CMD command found in the Dockerfile is executed. The --rm (remove) flag will make sure to delete the container once the run command is complete.

Running a Docker image:

$ docker run --rm snake
# Python 3.7.9

Experiment with a Python REPL of any version

FROM python:3.7.8-alpine
CMD ["python3"]

By changing the version, you can get into an interactive Python console for the given version. This is very handy when you want to test how a certain feature works in a newer or an older Python version.

When a docker run command is executed, it will run the CMD command and exit, so you won’t be able to interact with the REPL. To work with the container in an interactive mode, the -it flag should be used.

$ docker run --rm -it snake
# Python REPL becomes available

Passing a command to a Python Docker image

FROM python:3.7.8-alpine

When running a Docker container, it’s possible to pass a command, optionally, with additional arguments. Python provides support for running a command with the -c option so that it’s possible to supply the program code as a string. This can be very handy when you need to have a one-liner for an operation that will return a value you may need later as input for the subsequent operations.

$ docker run --rm -it snake python3 --version
# Python 3.7.8

$ docker run --rm -it snake python3 -c "import sys; print(sys.platform)"
# linux

Run a Python program from the host in a Docker container

FROM python:3.7.8-alpine

It is possible to mount a local directory (on your disk) as a volume to a Docker container which is done with the -v parameter.

Running the command below will make the app directory files available in the Docker container. This approach can be used when you want to run a Python program in a Docker container likely having a different system environment and Python version installed.

Having this Python program (stored at app/main.py):

import sys
print(sys.version_info)

you can execute it with:

$ docker run -v ${PWD}/app:/app snake python3 /app/main.py
# sys.version_info(major=3, minor=7, micro=8, releaselevel='final', serial=0)

It is also possible to copy files to the Docker image when the image is being built if you don’t want to mount any volumes at the run time.

FROM python:3.7.8-alpine
WORKDIR /opt/project/app
COPY ./app
CMD ["python3", "main.py"]

You can now run the Docker container to execute the main.py file that was copied:

$ docker run --rm snake
# sys.version_info(major=3, minor=7, micro=8, releaselevel='final', serial=0)

Alternatively, you could also make the CMD command a part of the docker run:

FROM python:3.7.8-alpine
WORKDIR /opt/project/app
COPY ./app

Then you pass the arguments to the container from the shell instead:

$ docker run --rm snake python3 main.py
# sys.version_info(major=3, minor=7, micro=8, releaselevel='final', serial=0)

Get into a Docker container shell and run a command

FROM python:3.7.8-alpine
WORKDIR /opt/project/app
COPY ./app .

It is possible to start a Docker container and run a shell console to execute arbitrary commands. This resembles connecting to a remote machine via an SSH connection or using a local Bash console.

$ docker run --rm -it snake /bin/sh           
# /opt/project/app # ls
# main.py
# /opt/project/app # python3 main.py
# sys.version_info(major=3, minor=7, micro=8, releaselevel='final', serial=0)

Access files created in the Docker container on the host

Accessing files that are created by processes run in a Docker container is possible by mounting a volume.

FROM python:3.7.8-alpine
WORKDIR /opt/project/app

Now you can run the container in the interactive mode and attach a shell console:

$ docker run -it -v ${PWD}/app:/opt/project/app snake /bin/sh 
# > touch foo.bar

The foo.bar file will appear on your host disk under the app directory. This makes it possible to create arbitrary files within your Docker container saving them on your host disk making them accessible for further usage locally.

Copy files produced by Docker build command to the host

In certain cases, a Dockerfile may create new files which can be accessed when starting the container.

FROM alpine
RUN echo "some data" > /home/data.out

The trick here is to mount your host’s directory to some other directory than container’s home and then copy the file(s) you need to this intermediate location.

$ docker run --rm -it -v ${PWD}/datadir:/home/datadir snake
# cp /home/data.out /home/datadir/

At this point, the data.out file should appear on your host’s disk under the datadir directory in your current working directory.

Run Python tests stored on your host inside a Docker container

To do this, one would need to have a Docker container with the pip installed and the necessary Python packages that are required by your tests. It can be wise to install a tested version of pip to avoid getting the latest one in case it’s broken (this has happened before a few times).

FROM python:3.7.8-alpine

RUN wget -q https://bootstrap.pypa.io/get-pip.py \
    && python3 get-pip.py pip==19.1.1 \
    && rm get-pip.py

COPY app/requirements.txt ./

RUN pip install -q --no-cache-dir \
    -r requirements.txt \
    && rm requirements.txt

Given that the requirements.txt contains pytest and the app folder has modules with test functions, you should be able to run pytest against the mounted directory:

$ docker run --rm -v ${PWD}/app:/opt/project/app snake pytest /opt/project/ -v
# pytest output

Because your current working directory is mounted as a volume, the files generated by the commands you run will be available on the host. This means you can run the coverage command in the Docker container and the .coverage file will be available on the host. Given this file, you’ll be able to generate the HTML report to view in your host’s web browser.

Make host environment variables available in a Docker container

It is common to use environment variables for storing some settings that Python programs may depend on. Keep in mind that a more robust strategy for storing sensitive information is to use Docker secrets.

FROM python:3.7.8-alpine

By default, host’s environment variables (both permanently stored and temporarily exported) are not available in a Docker container.

Given this Python program,

import os
print(f'Token: {os.getenv("SITE_TOKEN")}')

you can run it in a Docker container:

$ export SITE_TOKEN='mytoken'
$ docker build -t snake .
$ docker run -v ${PWD}/app:/opt/project/app snake python3 /opt/project/app/main.py
# Token: None

The -e (--env) parameter will let you specify the environment variables you want to propagate into the Docker container.

$ export SITE_TOKEN='mytoken'
$ docker run -e SITE_TOKEN=${SITE_TOKEN} -v ${PWD}/app:/opt/project/app snake \
    python3 /opt/project/app/main.py
# Token: mytoken

Attach to a running Docker container

When running a Python program in a Docker container in debugging mode, it can be useful to be able to pause the program and connect to the container to be able to inspect the file system.

A few IDEs such as PyCharm and VSCode provide support for remote Python debugging and will be able to start a Docker container running a Python program and then later tell you its id. This is especially useful when the Python program is expected to produce some files and you would like to inspect them to verify the program produces correct results.

If you know the container id, you can attach to it with:

$ docker exec -it <container_id> /bin/bash

If you don’t know the container id, you will need to get it first which can be done with:

$ docker ps --filter status=running

The CONTAINER ID field will contain the id of the Docker container you will need to attach to. If you have multiple running containers, the one you need is likely to be the first one in the list.

Happy containerization!


Published

Category

python

Tags