Imágenes Docker de Python en menos de 50MB
Escribo Python y lo despliego en Docker. Me gusta Python: es simple y las cosas salen rápido. Lo que no es, es una buena opción cuando quieres un distribuible pequeño, y las imágenes lo delatan.
Es muy habitual ver un proyecto Python empaquetado en una imagen Docker de varios cientos de MB. Incluso en proyectos diminutos. Aquí va un truco para reducirlas.
Supongamos que queremos empaquetar un proyecto con la estructura de siempre:
$ 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
El plan:
-
Escribir un Dockerfile multietapa.
-
Partir de una imagen base de Python.
-
Añadir una etapa que construya los wheels, con los flags de optimización de Python activados.
-
Construir la capa final e instalar esos wheels sin pasar por el índice de PyPI.
Que se traduce en este 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"]
Solo hay dos líneas que merecen una mirada de cerca.
La primera construye los wheels, tanto de las dependencias como de nuestro propio proyecto:
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/
La segunda instala únicamente los wheels que acabamos de construir. Con --no-index, pip no puede ir a PyPI a descargar nada más:
RUN python -m pip install --no-cache --no-index /root/wheels/*
Así que la imagen final solo lleva el código generado por los wheels, compilado con los flags de optimización de Python. El compilador y todo lo demás de la etapa builder se queda fuera.