A Minimal Python Image with Dependencies

This page describes a starting point for a simple containerized Python deployment with a single script and single dependency (‘boto3’, but that's not important, any other would be included in the same way).

Following the recommendation of pythonspeed.com, I am starting FROM python:3.7-slim-buster.

The motivation for creating this is that I encountered a few Python-based Dockerfiles that ignored best practice and did other strange things, from using Python 2.7 (in 2019), to installing dependencies with unknown versions, omitting a WORKDIR (a useful bit of functional documentation) and creating needlessly large images. Often no care is taken over the order of creating layers, which increases rebuild times. Oh, and prefer the simpler COPY over ADD for files.

I have avoided the above problems. If you see any different mistakes that I have successfully demonstrated, please let me know so I can fix them.

hello.py:

import boto3
print(f'Successfuly imported boto3 {boto3.__version__}')

requirements.txt:

#Only list required versions for direct dependencies,
#unless a conflict needs to be resolved.
boto3==1.9.248

Dockerfile:

FROM python:3.7-slim-buster
LABEL maintainer="r.luxury-yacht@exolete.com"

WORKDIR /usr/workdir
COPY requirements.txt .
RUN pip3 install -r requirements.txt
COPY hello.py .

CMD ["python", "hello.py"]

With those three files in a single directory, build and execute the image to show that everything is working.

$ docker build -t minimalpython:1.0 .
[boring build log omitted]
$ docker run minimalpython:1.0
Successfully imported boto3 1.9.248

… or, if you have renounced daemons:

$ buildah bud -t minimalpython:1.0 .
[similar build log omitted]
$ podman run minimalpython:1.0
Successfully imported boto3 1.9.248

Footnote on timing

The official guidance suggests that: “if your build contains several layers, you can order them from the less frequently changed (to ensure the build cache is reusable) to the more frequently changed”

That's what the above does, so rebuilding the image after a change to the deployed script is quick. Changing the Dockerfile to perform both COPYs at once (COPY requirements.txt hello.py ./) inflates the build time, by causing the requirements to be downloaded again needlessly. Timings obviously depend on the system, but the increase in time to rebuild on my system is from 0.3±0.2s for the suggested version above, to 8.5s plus any network pauses for a version that naively COPYs both at once.