Skip to content

Optimize Dockerfile: squash 5 layers#91

Merged
jancwe merged 1 commit intomainfrom
copilot/optimize-dockerfile-layer-order
Mar 22, 2026
Merged

Optimize Dockerfile: squash 5 layers#91
jancwe merged 1 commit intomainfrom
copilot/optimize-dockerfile-layer-order

Conversation

Copy link
Contributor

Copilot AI commented Mar 21, 2026

Every source/HTML change was busting the pip install layer (~200 MB, 60–90s) because COPY src/ appeared before RUN pip install. Separately, three LABEL instructions and two trailing RUN commands added unnecessary layers.

Changes

  • Labels: Collapsed 3 LABEL instructions into one multi-line statement (−2 layers)
  • Dep caching: Copy only pyproject.toml, create a minimal dummy src/formflow/__init__.py, run pip install to cache deps, then delete the dummy — so the heavy install layer is only invalidated when pyproject.toml actually changes
  • Source install: After COPY src/ src/, run pip install --no-cache-dir --no-deps . to install the package itself without re-downloading dependencies
  • Targeted COPYs: Replace COPY . . with COPY app.py . / COPY forms/ forms/ — eliminates double-copying of src/ and pyproject.toml and removes unpredictable build-context behavior
  • Combined final RUN: Merge static-file copy and /data dir creation into one instruction (−1 layer)
# Before: COPY src/ before pip install → any code change re-runs pip
COPY pyproject.toml .
COPY src/ src/
RUN pip install --no-cache-dir .        # busted on every HTML edit

# After: deps layer immune to source changes
COPY pyproject.toml .
RUN mkdir -p src/formflow && \
    echo '__version__ = "0.1.0"' > src/formflow/__init__.py && \
    pip install --no-cache-dir . && \
    rm -rf src/formflow                 # cached unless pyproject.toml changes

COPY src/ src/
RUN pip install --no-cache-dir --no-deps .   # fast: just installs the package

Cache impact for a typical HTML/code change: ~60–90 s → ~5–10 s.

Original prompt

Problem

Das aktuelle Dockerfile erzeugt 12 Layer/Blobs. Selbst kleine HTML-Änderungen invalidieren mehrere Layer, einschließlich des teuren pip install-Layers (~200+ MB, 30–60 Sek.), weil die Layer-Reihenfolge nicht optimal ist.

Aktuelles Dockerfile (12 Layer)

FROM python:3.14-slim

LABEL org.opencontainers.image.version="0.1.0"
LABEL org.opencontainers.image.title="formflow"
LABEL org.opencontainers.image.description="Dynamischer Formular- & PDF-Generator"

WORKDIR /app

RUN apt-get update && apt-get install -y \
    libpango-1.0-0 \
    libpangoft2-1.0-0 \
    libjpeg-dev \
    libopenjp2-7-dev \
    libffi-dev \
    curl \
    && rm -rf /var/lib/apt/lists/*

COPY pyproject.toml .
COPY src/ src/
RUN pip install --no-cache-dir .

COPY . .

RUN mkdir -p /app/static && cp -r src/formflow/static/. /app/static/

RUN mkdir -p /data/forms /data/pdf_templates

EXPOSE 5000

CMD ["gunicorn", "-w", "1", "--timeout", "120", "--max-requests", "500", "--max-requests-jitter", "50", "-b", "0.0.0.0:5000", "app:app"]

Identifizierte Probleme

  1. Drei separate LABEL-Anweisungen → 3 Layer statt 1
  2. COPY src/ src/ VOR pip install: Jede Code-Änderung in src/ invalidiert den pip-install-Cache, obwohl sich die Dependencies nicht geändert haben. Das ist das Hauptproblem – eine HTML-Template-Änderung erzwingt einen kompletten pip install Rebuild.
  3. COPY . . nach selektiven COPYs: Kopiert pyproject.toml und src/ doppelt und macht den Build-Context unvorhersehbar.
  4. Zwei separate RUN-Befehle am Ende (static-Dateien kopieren + /data-Verzeichnisse erstellen) → können kombiniert werden.

Gewünschte Änderungen

Das Dockerfile soll wie folgt optimiert werden:

FROM python:3.14-slim

LABEL org.opencontainers.image.version="0.1.0" \
      org.opencontainers.image.title="formflow" \
      org.opencontainers.image.description="Dynamischer Formular- & PDF-Generator"

WORKDIR /app

# 1) System-Deps (ändert sich sehr selten)
RUN apt-get update && apt-get install -y \
    libpango-1.0-0 \
    libpangoft2-1.0-0 \
    libjpeg-dev \
    libopenjp2-7-dev \
    libffi-dev \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 2) Python-Deps NUR bei pyproject.toml-Änderung
#    Trick: Nur pyproject.toml kopieren + Dummy-Package,
#    damit pip install die Dependencies cached.
COPY pyproject.toml .
RUN mkdir -p src/formflow && \
    echo '__version__ = "0.1.0"' > src/formflow/__init__.py && \
    pip install --no-cache-dir . && \
    rm -rf src/formflow

# 3) Anwendungscode (ändert sich häufig)
COPY src/ src/
RUN pip install --no-cache-dir --no-deps .

# 4) Restliche Dateien (app.py, forms/, etc.)
COPY app.py .
COPY forms/ forms/

# 5) Statische Dateien + Verzeichnisse
RUN mkdir -p /app/static /data/forms /data/pdf_templates && \
    cp -r src/formflow/static/. /app/static/

EXPOSE 5000

# Verwende Gunicorn als Produktions-WSGI-Server
# -w 1: 1 Worker-Prozess – ausreichend für Low-Traffic-Intranet-Apps.
#        Jeder Worker lädt WeasyPrint (~150-300 MB), daher bewusst gering halten.
#        Für höheren Durchsatz: Wert auf (2 * CPU-Kerne + 1) erhöhen.
# --timeout 120: WeasyPrint-Rendering kann bei komplexen Templates >30s dauern.
# --max-requests 500: Worker wird nach 500 Requests graceful neu gestartet (Schutz gegen Memory Leaks).
# --max-requests-jitter 50: Zufälliger Versatz beim Restart (zukunftssicher bei mehreren Workern).
# -b 0.0.0.0:5000: Binde an alle Interfaces auf Port 5000
CMD ["gunicorn", "-w", "1", "--timeout", "120", "--max-requests", "500", "--max-requests-jitter", "50", "-b", "0.0.0.0:5000", "app:app"]

Erwartete Verbesserungen

Aspekt Vorher (12 Layer) Nachher (8 Layer)
Anzahl Layer/Blobs 12 8
Labels 3 separate → 3 Layer 1 kombiniert → 1 Layer
RUN am Ende 2 separate 1 kombiniert
Cache bei HTML/Code-Änderung Layer 8–12 invalidiert (inkl. pip install ~200 MB, ~60-90 Sek.) Nur Layer ab COPY src/ betroffen; pip install deps gecached (~5-10 Sek.)
Doppelte Dateien COPY . . kopiert src/ und pyproject.toml nochmal Gezielte COPY-Befehle, keine Dopplung

Wichtige Hinweise

  • Die CI-Pipeline (.github/workflows/ci.yml) nutzt bereits docker/build-push-action mit GHA-Cache (cache-from: type=gha), profitiert also direkt von der besseren Layer-Struktur.
  • Die bestehenden Kommentare zu Gunicorn-Konfiguration sollen beibehalten werden.
  • Nur das Dockerfile muss geändert werden. Keine Änderungen an .dockerignore, docker-compose.yml, etc.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Mir ist aufgefallen, dass wir mit 12 "blobs" sehr viele docker layer haben und selbst die kleine HTML Änderung hat Änderungen an gleich mehreren layern verursacht. Analysiere die docker layer Situation und schreibe einen Bericht ob wir sie optimieren sollten.

Assistant:

Ich werde zunächst das Dockerfile ...

This pull request was created from Copilot chat.


💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.

Copilot AI changed the title [WIP] Optimize Dockerfile to reduce layer invalidation Optimize Dockerfile: 12 → 8 layers, fix dep-cache invalidation on code changes Mar 21, 2026
Copilot AI requested a review from jancwe March 21, 2026 15:35
@jancwe jancwe force-pushed the copilot/optimize-dockerfile-layer-order branch from 7246587 to dc9d571 Compare March 21, 2026 15:38
@jancwe jancwe marked this pull request as ready for review March 21, 2026 15:39
@jancwe jancwe force-pushed the copilot/optimize-dockerfile-layer-order branch from 4e2677e to c5e918e Compare March 22, 2026 05:03
@jancwe jancwe changed the title Optimize Dockerfile: 12 → 8 layers, fix dep-cache invalidation on code changes Optimize Dockerfile: squash 2 layers Mar 22, 2026
@jancwe jancwe changed the title Optimize Dockerfile: squash 2 layers Optimize Dockerfile: squash 5 layers Mar 22, 2026
@jancwe jancwe merged commit 40eae77 into main Mar 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants