Le problème de départ
Mes premières images Docker pour le projet e-commerce:
- API Clients: 847 MB
- API Catalogue: 823 MB
- API Commandes: 891 MB
Totalement ridicule pour des APIs Django qui font juste du CRUD.
Le pire ? Mes workers Kubernetes galéraient à pull les images. Ça prenait 2-3 minutes par image. Sur un cluster de 2 workers avec 4 services, faites le calcul...
Ce que j'ai changé
Après optimisation:
- API Clients: 127 MB (-85%)
- API Catalogue: 119 MB (-86%)
- API Commandes: 132 MB (-85%)
Et le meilleur : elles marchent EXACTEMENT pareil.
La technique : multi-stage build + from scratch
Étape 1 : Builder (Alpine)
FROM python:3-alpine AS builder
# Installer les outils de compilation
RUN apk add --no-cache gcc python3-dev musl-dev mariadb-dev pkgconf
# Créer un venv
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Virer pip (on en a plus besoin)
RUN pip uninstall pip -y
# Nettoyage agressif
RUN find /opt/venv -type d \( \
-name 'tests' -o \
-name '__pycache__' -o \
-name '*.dist-info' \
\) -exec rm -rf {} + 2>/dev/null || true
Cette étape compile tout. C'est gros, c'est moche, mais c'est temporaire.
Étape 2 : Syscollector (récupérer juste ce qu'il faut)
FROM python:3-alpine AS syscollector
RUN apk add --no-cache mariadb-connector-c
RUN mkdir -p /rootfs/lib /rootfs/usr/lib /rootfs/usr/local/bin
# Copier Python + stdlib complète
RUN cp /usr/local/bin/python3* /rootfs/usr/local/bin/ && \
cp -r /usr/local/lib/python3* /rootfs/usr/local/lib/
# Copier sh (pour le entrypoint)
RUN cp /bin/sh /rootfs/bin/
# Récupérer les dépendances avec ldd
RUN ldd /usr/local/bin/python3* /bin/sh 2>/dev/null | \
grep "=>" | awk '{print $3}' | \
sort -u | xargs -I '{}' cp '{}' /rootfs/lib/
# Dynamic linker
RUN cp /lib/ld-musl-*.so.1 /rootfs/lib/
# Libs MariaDB
RUN find /usr/lib -name 'libmariadb*.so*' -exec cp {} /rootfs/usr/lib/ \;
Ici, je récupère UNIQUEMENT ce dont j'ai besoin. Pas de package manager, pas d'outils de debug, rien.
Étape 3 : Image finale (from scratch)
FROM scratch
COPY --from=syscollector /rootfs /
COPY --from=builder /opt/venv /opt/venv
COPY . /app
ENV PATH="/opt/venv/bin:/usr/local/bin:/bin" \
LD_LIBRARY_PATH="/lib:/usr/lib:/usr/local/lib" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app/project
EXPOSE 8000
ENTRYPOINT ["sh", "/app/docker-entrypoint.sh"]
FROM scratch = littéralement RIEN. Même pas un shell de base. C'est pour ça qu'il faut copier /bin/sh dans l'étape 2.
Les pièges que j'ai rencontrés
Piège #1 : Supprimer les *.dist-info
Au début j'ai fait:
find /opt/venv -name '*.dist-info' -exec rm -rf {} +
Résultat ? Plus rien marchait. Python utilisait ces fichiers pour importer les packages.
Solution: Garder les métadonnées critiques, virer que le superflu.
Piège #2 : Oublier le dynamic linker
standard_init_linux.go:228: exec user process caused: no such file or directory
Ce message cryptique = j'avais oublié de copier /lib/ld-musl-*.so.1.
Piège #3 : MariaDB qui fait des siennes
MariaDB a besoin de plein de libs partagées. J'utilisais ldd pour les trouver:
ldd /usr/lib/libmariadb.so.3
Et je copiais toutes les dépendances dans /rootfs/lib.
Comparaison avant/après
Avant (image basique)
FROM python:3
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . /app
WORKDIR /app
CMD ["gunicorn", "app:app"]
Taille: 847 MB
Layers: 12
Temps de pull: 2min 34s (sur mon cluster)
Après (optimisée)
Taille: 127 MB
Layers: 7
Temps de pull: 14s
Le gain de temps sur les déploiements est ÉNORME.
Le docker-entrypoint.sh
Petit bonus, mon script d'entrypoint fait les migrations automatiquement:
#!/bin/bash
set -e
echo "Running migrations..."
python3 manage.py makemigrations api
python3 manage.py migrate --noinput
echo "Collecting static..."
python3 manage.py collectstatic --noinput
echo "Starting Gunicorn..."
exec gunicorn project.wsgi:application --bind 0.0.0.0:8000
Métriques finales
| Métrique | Avant | Après | Gain |
|---|---|---|---|
| Taille image | 847 MB | 127 MB | -85% |
| Temps de build | 4min 12s | 3min 8s | -25% |
| Temps de pull | 2min 34s | 14s | -91% |
| Layers | 12 | 7 | -42% |
Conclusion
Est-ce que ça vaut le coup ? Pour moi, OUI:
- Déploiements plus rapides
- Moins de bande passante
- Images plus sécurisées (surface d'attaque réduite)
Par contre, c'est plus complexe à débugger. Pas de apt install, pas de curl, rien. Si vous avez besoin de débugger, utilisez une image de dev normale.
Le code complet est sur mon GitHub : [lien vers le repo]
Des questions ? Ping moi sur Twitter [@tonhandle]
Top comments (0)