Python Docker images in less than 50MB
I write Python and I ship it in Docker. I like Python: it is simple and you get things done fast. What it is not is a good choice when you want a small distributable, and the images show it.
It is very common to see a Python project packaged as a Docker image of several hundred MB. Even for tiny projects. Here is a trick to get them down.
Say we want to package a project with the usual layout:
$ ls -lh demo_app/
-rw-r--r-- 1 Dani staff 1B May 25 10:31 Dockerfile
-rw-r--r-- 1 Dani staff 2B May 25 09:28 MANIFEST.in
drwxr-xr-x 2 Dani staff 64B May 25 09:27 app
-rw-r--r-- 1 Dani staff 9B May 25 09:27 requirements.txt
-rw-r--r-- 1 Dani staff 0B May 25 09:27 setup.py
The plan:
-
Write a multi-stage Dockerfile.
-
Start from a Python base image.
-
Add a stage that builds the wheels, with the Python optimization flags on.
-
Build the final layer and install those wheels without going to the PyPI index.
Which gives us this Dockerfile:
FROM python:3.8-alpine as base
RUN apk update && \
apk upgrade
FROM base as builder
RUN apk add --no-cache build-base && \
python -m pip install --no-cache-dir -U pip wheel
COPY ./ /app/
RUN python -OO -m pip wheel --no-cache-dir --wheel-dir=/root/wheels -r /app/requirements.txt && \
python -OO -m pip wheel --no-cache-dir --wheel-dir=/root/wheels /app/
FROM base
COPY --from=builder /root/wheels /root/wheels
RUN python -m pip install --no-cache --no-index /root/wheels/*
RUN rm -rf /root/wheels
ENTRYPOINT ["my-project"]
Only two lines deserve a closer look.
The first builds the wheels, both for the dependencies and for our own project:
RUN python -OO -m pip wheel --no-cache-dir --wheel-dir=/root/wheels -r /app/requirements.txt && \
python -OO -m pip wheel --no-cache-dir --wheel-dir=/root/wheels /app/
The second installs nothing but the wheels we just built. With --no-index, pip cannot reach PyPI to download anything else:
RUN python -m pip install --no-cache --no-index /root/wheels/*
So the final image only carries the code generated by the wheels, compiled with the Python optimization flags. The compiler and everything else from the builder stage stays behind.