Tiny Python Docker Images
I was looking for a way to create a very small docker image for a Python project. The python project I created is a Telegram bot quizzes me in German. The python-telegram-bot
pip package requires gcc
to fully build- so using alpine image did not do the trick for me. I searched online, found a few example bot none that would actually do what I needed with a reasonable image size.
I was able to achieve what I wanted by using python:3.8
image to build the dependencies and a python:3.8-alpine
as base for container image. Some examples I found were doing that, but the trick to bridge the dependencies between one image and the other was to use a virtualenv and copy all dependencies to the target image. Maybe there is a way to do this without using virtualenv
and copying the packages directly to the filesystem python
uses.
Here’s how the Dockerfile
looks like at the end of the process:
FROM python:3.8 as base
COPY requirements.txt /requirements.txt
RUN mkdir /install
RUN pip install --prefix=/install --no-cache-dir -r /requirements.txt
FROM python:3.8-alpine
RUN python3 -m venv /usr/src/app/venv
COPY --from=base /install/lib/python3.8/site-packages/ /usr/src/app/venv/lib/python3.8/site-packages/
WORKDIR /usr/src/app
COPY *.py .
COPY nouns .
COPY start.sh .
CMD ["/bin/sh", "./start.sh"]
The steps to build up the layers are as follows:
- Create a build image based on
python:3.8
- Install python dependencies from
requirements.txt
into a folder/install
- Create a container image based on
python-3.8-alpine
- Create a virtual environment using
venv
- Copy all dependencies installed in base image
/install
folder to the newly created virtual environment - Copy the actual python project to
WORKDIR
and start the process
The start.sh
bash script was just a workaround to activate venv virtual environment before starting the python process. It looks something like this:
source ./venv/bin/activate
python ./main.py
Comparing with Buster
Well, it turns out my idea is good but not as good as I thought it was. Multi-stage builds for docker images seem to be a generally good idea, but in this case, alpine Linux can raise some complications specially due to it’s limited dependencies - not only for building - but running Python applications. I created to examples, one with the alpine
linux multi-stage build (see above) and one using a smaller python image python:3.8-slim-buster
which is a bit bigger, but for dependency heavy application that doesn’t make much of a difference.
The example I created was an application with the following dependencies:
matplotlib
pandas
These dependencies require gcc
to build them. So they seemed like a good example to use.
This is how the buster Dockerfile looks like:
FROM python:3.8-slim-buster
WORKDIR /src/usr/app
COPY requirements.txt .
RUN pip install --no-cache-dir -r ./requirements.txt
COPY *.py .
CMD ["python", "main.py"]
REPOSITORY TAG IMAGE ID CREATED SIZE
fredrb/pyimg-test-alpine latest bb3d079ca5c5 5 hours ago 204MB
fredrb/pyimg-test-buster latest ed3b808ec1ca 5 hours ago 265MB
My Multi-stage Alpine images are still smaller, but the Dockerfile is more complex, I think it depends if these 61MB would make a big difference. In general, I think I would prefer to have the buster image with ~5 LoC Dockerfile than a more complex Dockerfile only for a 20% reduction of size.
If you hated this post, and can't keep it to yourself, consider sending me an e-mail at fred.rbittencourt@gmail.com or complain with me at Twitter X @derfrb. I'm also occasionally responsive to positive comments.