Django und Tailwind CSS: Multi-Stage-Build-Strategie zur Optimierung von Docker-Images
Für Backend-Entwickler ist CSS oft ein ewiges Problem. Nachdem wir die Zeiten manuell
style.css zu schreiben hinter uns gelassen haben, kamen wir mit Bootstrap auf die Bühne und jubelten. Doch je mehr wir es pflegen, desto mehr wird das Anpassen außerhalb des vorgegebenen Rahmens zu einer Qual.
Der aktuelle Trend ist eindeutig Tailwind CSS. Das Utility‑First‑Modell, bei dem Styling durch reine Klassenzusammenstellung erledigt wird, ist für Backend‑Ingenieure fast schon ein Rettungsanker. Ich habe selbst Bootstrap aus bestehenden Projekten entfernt und auf Tailwind umgestellt – es ist ein unverzichtbares Werkzeug in modernen Web‑Entwicklungen, besonders bei Django.
In diesem Beitrag zeige ich, wie man in einem Django‑Projekt Tailwind CSS nutzt und gleichzeitig die Docker‑Images für die Produktion optimiert, insbesondere durch einen Multi‑Stage‑Build, um die Images leicht zu halten.
Problemstellung: Überdimensionierte Docker‑Images
Um Tailwind in Django zu integrieren, wird häufig das Paket django‑tailwind verwendet. Die Installation und Dokumentation sind gut, sodass es in der lokalen Entwicklungsumgebung keine Probleme gibt.
Das Problem tritt jedoch in der Deployment‑Phase, insbesondere in der Docker‑Container‑Umgebung, auf.
In einer lokalen oder Bare‑Metal‑Umgebung kann man CSS einfach mit:
python manage.py tailwind build
bauen. Dieser Schritt benötigt jedoch intern Node.js und npm. Das bedeutet, dass Node.js in das Produktions‑Django‑Docker‑Image aufgenommen werden muss – was in vielerlei Hinsicht ineffizient ist.
- Problem 1: Für einen einmaligen CSS‑Build muss die Laufzeit‑Umgebung Node.js enthalten.
- Problem 2: Das Image wird unnötig groß.
- Problem 3: Sicherheitsmäßig ist es schlecht, Binärdateien zu haben, die zur Laufzeit nicht benötigt werden.
Unser Ziel ist klar:
"Node.js nicht installiert, sondern nur die gebauten CSS‑Dateien in ein reines Python‑Runtime‑Image übernehmen."
Dafür nutzen wir den Multi‑Stage‑Build von Docker.
Lösung: 3‑Stufen‑Multi‑Stage‑Build
Mit dem Multi‑Stage‑Build von Docker lässt sich das Problem sauber lösen. Der Build-Prozess wird in drei Stufen aufgeteilt:
- Python Builder: Python‑Pakete als Wheel bauen.
- Node Builder: Tailwind CSS kompilieren (
npm). - Final Runtime: Nur die Ergebnisse der beiden vorherigen Stufen kopieren und das finale Image erstellen.
So bleibt das End‑Image ohne Node.js oder Build‑Tools – nur die für die Ausführung benötigten Dateien.
Beispiel‑Dockerfile
Hier ein Beispiel‑Dockerfile für ein Django + Tailwind‑Projekt. Passe Pfade und Dateinamen an deine Projektstruktur an (App‑Name theme, statische Dateien usw.).
# -------------------------------------------------------------------
# Stage 1: Python Builder
# Installiere Linux‑Abhängigkeiten und baue Python‑Pakete als Wheel
# -------------------------------------------------------------------
FROM python:3.11-slim as python-builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --upgrade pip && \
pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt
# -------------------------------------------------------------------
# Stage 2: Node Builder
# Nutze ein Node‑Image, um Tailwind CSS zu kompilieren
# -------------------------------------------------------------------
FROM node:20-alpine as node-builder
WORKDIR /app
# Kopiere das gesamte Projekt, damit Tailwind JIT die Templates/py‑Dateien scannen kann
COPY . .
WORKDIR /app/theme/static_src
# Installiere Abhängigkeiten und führe den Build aus
RUN npm ci
RUN npm run build
# Angenommen, das Ergebnis liegt in /app/theme/static/css/dist.css
# -------------------------------------------------------------------
# Stage 3: Final Runtime
# Endgültiges Image ohne Node.js, nur Python und statische Dateien
# -------------------------------------------------------------------
FROM python:3.11-slim
WORKDIR /app
# 1. Installiere die von Python Builder gebauten Pakete
COPY --from=python-builder /app/wheels /wheels
COPY --from=python-builder /app/requirements.txt .
RUN pip install --no-cache-dir /wheels/*
# 2. Kopiere den gesamten Anwendungscode
COPY . .
# 3. Kopiere nur die gebauten CSS‑Dateien aus Node Builder
COPY --from=node-builder /app/theme/static/css/dist.css \
/app/theme/static/css/dist.css
# 4. Führe collectstatic aus, damit Django die statischen Dateien sammelt
RUN python manage.py collectstatic --noinput
# Startbefehl
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "config.wsgi:application"]
Weitere Optimierungen je nach Umgebung:
.dockerignorenutzen, um unnötige Dateien (z. B..git, lokale Medien) auszuschließen.- Umgebungsvariablen wie
PYTHONDONTWRITEBYTECODE=1undPYTHONUNBUFFERED=1setzen. - Bei Alpine‑Images nur die benötigten Systembibliotheken installieren.
Kernpunkte zusammengefasst
1. Trennung der Node‑Builder‑Stufe
In Stage 2 konzentriert sich die Stufe ausschließlich auf den CSS‑Build. Wichtig ist:
- Tailwinds
content‑Einstellung scannt oft Django‑Templates (templates/**/*.html) und Python‑Code. - Im Beispiel wird das gesamte Projekt kopiert, damit JIT die Klassen erkennt.
- Bei sehr großen Projekten oder klar definierten
content‑Pfaden kann man auch nur die notwendigen Verzeichnisse kopieren.
2. Nutzung des npm run build‑Skripts
django‑tailwind erzeugt in der Regel ein package.json mit einem Build‑Skript, z. B.:
{
"scripts": {
"build": "tailwindcss -c tailwind.config.js -o ../static/css/dist.css --minify"
}
}
Im Multi‑Stage‑Build nutzt man einfach npm run build oder ein bereits definiertes Skript. Man muss nur den Pfad der Ausgabe kennen.
3. Leichtgewichtiges und sicheres End‑Image
Im Stage 3:
npm,node,tailwindcssCLI sind nicht enthalten.- Python‑Pakete werden als Wheel installiert, was Build‑Zeit und Cache‑Nutzung verbessert.
- Das Ergebnis ist ein schlankes Image mit geringem Angriffs‑Surface.
Langfristig profitieren CI/CD‑Pipelines, RegistrierungsKosten und Sicherheitsprüfungen von dieser Struktur.

Fazit
Beim Erstellen von Docker‑Images neigt man oft dazu, alles in ein Bild zu packen, um „funktioniert einfach“. In Produktionsumgebungen sind jedoch Image‑Größe, Build‑Zeit und Sicherheit entscheidend.
Wenn du Django und Tailwind zusammen nutzt, probiere die oben beschriebene 3‑Stufen‑Multi‑Stage‑Build‑Strategie aus:
- Node.js‑Abhängigkeiten nur im Build‑Schritt.
- Das finale Image enthält ausschließlich die reine Python‑Runtime und die gebauten statischen Dateien.
- Das Ergebnis ist ein leichter, vorhersehbarer und wartbarer Deploy‑Prozess.
Falls du bereits ein laufendes Projekt hast, versuche es beim nächsten Release‑Pipeline‑Schritt umzusetzen. Sobald die Struktur steht, lässt sich das Muster fast überall wiederverwenden.
Es sind keine Kommentare vorhanden.