Kleinere Docker Images mit uv

Die CI/CD-Pipeline dauert ewig? Das lokale Bauen eines Container-Images dauert viel zu lange? Ein möglicher Grund dafür könnte die Größe der Container-Images sein – oft sind sie unnötig aufgebläht. In diesem Artikel werden verschiedene Strategien vorgestellt, um Images zu optimieren und effizienter und schneller zu gestalten. 🚀

Keine unnötigen Dependencies

Ein gewachsenes Projekt mit zahlreichen Abhängigkeiten in der pyproject.toml kann schnell unübersichtlich werden. Bevor der nächste Schritt – beispielsweise die Containerisierung mit Docker – angegangen wird, lohnt es sich zunächst zu prüfen, welche Abhängigkeiten tatsächlich noch benötigt werden und welche mittlerweile obsolet sind. So lässt sich die Codebasis verschlanken, potenzielle Sicherheitsrisiken reduzieren und die Wartbarkeit verbessern.

Eine Möglichkeit wäre, alle Dependencies sowie das Virtual Environment zu löschen und anschließend den Quellcode Datei für Datei durchzugehen, um nur noch die wirklich benötigten Abhängigkeiten hinzuzufügen. Eine effizientere Strategie bietet das Command-Line-Tool deptry. Es übernimmt diese mühsame Aufgabe und hilft dabei, überflüssige Abhängigkeiten schnell zu identifizieren. Die Installation erfolgt mit

uv add --dev deptry

Anschließend lässt sich die Analyse des Projekts direkt im Projektordner mit folgendem Befehl starten

deptry .

Danach listet deptry die Dependencies auf die nicht mehr benutzt werden

Scanning 126 files...
pyproject.toml: DEP002 'pandas' defined as a dependency but not used in the codebase
Found 1 dependency issue.

In diesem Fall scheint pandas nicht mehr genutzt zu werden. Es empfiehlt sich, dies zu überprüfen und anschließend alle nicht mehr benötigten Abhängigkeiten zu entfernen.

uv remove pandas 

Alternativer Index

Wenn ein Paket wie pytorch, docling oder sparrow genutzt wird, das torch(vision) als Abhängigkeit enthält, und ausschließlich die CPU zum Einsatz kommen soll, kann auf die Installation der CUDA-Bibliotheken verzichtet werden. Dies lässt sich erreichen, indem für torch(vision) ein alternativer Index angegeben wird. uv sucht das Paket dann zunächst dort, wobei in diesem Index keine Abhängigkeiten zu den CUDA-Bibliotheken für torch(vision) definiert sind. Dazu wird in der pyproject.toml unter dependencies folgender Eintrag ergänzt:

[tool.uv.sources]
torch = [
    { index = "pytorch-cpu" },
]

torchvision = [
    { index = "pytorch-cpu" },
]


[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"

So sehen die Images mit und ohne alternativem Index aus:

REPOSITORY           TAG       IMAGE ID       CREATED              SIZE
sample_torchvision   gpu       f0f89156f089   5 minutes ago        6.46GB
sample_torchvision   cpu       0e4b696bdcb2   About a minute ago   657MB

Mit dem alternativen Index ist das Image nur noch 1/10 so groß!

Das richtige Dockerfile

Unabhängig davon, ob das Python-Projekt gerade erst startet oder bereits länger besteht, lohnt sich ein Blick auf die von uv bereitgestellten Beispiel-Dockerfiles: uv-docker-example.

Diese bieten eine sinnvolle Grundkonfiguration und sind darauf optimiert, möglichst kleine Images zu erzeugen. Sie sind ausführlich kommentiert und nutzen ein minimales Base-Image mit vorinstalliertem Python und uv. Dependencies und das Projekt werden in separaten Befehlen installiert, sodass das Layer-Caching optimal funktioniert. Dabei werden nur die regulären Dependencies installiert, während Dev-Dependencies wie das oben installierte deptry ausgelassen werden.

Im Multi-Stage-Sample werden zudem nur das Virtual Environment und die Projektdateien in das Runtime-Image kopiert, sodass keine überflüssigen Build-Artefakte im finalen Image landen.

Bonustipp für Azure WebApp Nutzer

Dieser Tipp verkleinert zwar nicht das Image, kann einem aber im Ernstfall einige Kopfschmerzen ersparen.

Wenn das Docker-Image in einer Azure WebApp deployed wird, sollte /home oder darunterliegende Pfade nicht als WORKDIR verwenden. Der /home-Pfad kann genutzt werden, um Daten über mehrere WebApp-Instanzen hinweg zu teilen. Dies wird durch die Umgebungsvariable WEBSITES_ENABLE_APP_SERVICE_STORAGE gesteuert. Ist diese auf true gesetzt, wird der gemeinsame Speicher nach /home gemountet, wodurch die im Image enthaltenen Dateien im Container nicht mehr sichtbar sind.

(Wenn das Dockerfile sich an den uv-Beispielen orientiert, dann ist das WORKDIR bereits korrekt unter „/app“ konfiguriert.)