Fortgeschrittene Dockerfile-Techniken

DockerBeginner
Jetzt üben

Einführung

In diesem Lab tauchen wir tiefer in die Welt der Dockerfile-Techniken ein. Wir untersuchen fortgeschrittene Konzepte, die Ihnen dabei helfen, effizientere und flexiblere Docker-Images zu erstellen. Wir behandeln detaillierte Dockerfile-Anweisungen, mehrstufige Builds (Multi-stage Builds) und den Einsatz von .dockerignore-Dateien. Zudem beschäftigen wir uns mit dem entscheidenden Konzept der Layer (Schichten) in Docker-Images. Am Ende dieses Labs werden Sie ein umfassendes Verständnis dieser Techniken haben und diese in Ihren eigenen Projekten anwenden können.

Dieses Lab ist speziell für Einsteiger konzipiert. Wir bieten detaillierte Erklärungen und gehen auf potenzielle Unklarheiten ein. Für alle Bearbeitungsaufgaben nutzen wir die WebIDE (VS Code), was das Erstellen und Ändern von Dateien direkt im Browser sehr komfortabel macht.

Dockerfile-Anweisungen und Layer verstehen

Beginnen wir damit, ein Dockerfile zu erstellen, das verschiedene Anweisungen nutzt. Wir bauen ein Image für eine Python-Webanwendung auf Basis von Flask. Dabei untersuchen wir, wie jede Anweisung zu den Layern unseres Docker-Images beiträgt.

  1. Zuerst erstellen wir ein neues Verzeichnis für unser Projekt. Führen Sie im Terminal der WebIDE folgenden Befehl aus:
mkdir -p ~/project/advanced-dockerfile && cd ~/project/advanced-dockerfile

Dieser Befehl erstellt einen neuen Ordner namens advanced-dockerfile im Verzeichnis project und wechselt direkt dorthin.

  1. Nun erstellen wir unsere Anwendungsdatei. Klicken Sie im Datei-Explorer der WebIDE (normalerweise links) mit der rechten Maustaste auf den Ordner advanced-dockerfile und wählen Sie "New File". Nennen Sie die Datei app.py.

  2. Öffnen Sie app.py und fügen Sie den folgenden Python-Code ein:

from flask import Flask
import os

app = Flask(__name__)

@app.route('/')
def hello():
    return f"Hello from {os.environ.get('ENVIRONMENT', 'unknown')} environment!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Dies ist eine einfache Flask-Anwendung, die eine Begrüßungsnachricht ausgibt und dabei anzeigt, in welcher Umgebung sie ausgeführt wird.

  1. Als Nächstes benötigen wir eine requirements.txt, um unsere Python-Abhängigkeiten zu definieren. Erstellen Sie eine neue Datei namens requirements.txt im selben Verzeichnis mit folgendem Inhalt:
Flask==2.0.1
Werkzeug==2.0.1

Wir geben hier exakte Versionen für Flask und Werkzeug an, um die Kompatibilität sicherzustellen.

  1. Jetzt erstellen wir unser Dockerfile. Erstellen Sie eine neue Datei namens Dockerfile (mit großem 'D') im selben Verzeichnis und fügen Sie diesen Inhalt hinzu:
## Use an official Python runtime as the base image
FROM python:3.9-slim

## Set the working directory in the container
WORKDIR /app

## Set an environment variable
ENV ENVIRONMENT=production

## Copy the requirements file into the container
COPY requirements.txt .

## Install the required packages
RUN pip install --no-cache-dir -r requirements.txt

## Copy the application code into the container
COPY app.py .

## Specify the command to run when the container starts
CMD ["python", "app.py"]

## Expose the port the app runs on
EXPOSE 5000

## Add labels for metadata
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="1.0"
LABEL description="Flask app demo for advanced Dockerfile techniques"

Lassen Sie uns diese Anweisungen analysieren und verstehen, wie sie die Layer unseres Images bilden:

  • FROM python:3.9-slim: Dies ist immer die erste Anweisung. Sie legt das Basis-Image fest. Dies erzeugt den ersten Layer unseres Images, der die Python-Laufzeitumgebung enthält.
  • WORKDIR /app: Legt das Arbeitsverzeichnis für alle folgenden Befehle fest. Es erzeugt keinen neuen Layer, beeinflusst aber das Verhalten der nachfolgenden Schritte.
  • ENV ENVIRONMENT=production: Setzt eine Umgebungsvariable. Diese erzeugen keine neuen Layer, werden aber in den Metadaten des Images gespeichert.
  • COPY requirements.txt .: Kopiert die Datei vom Host-Rechner in das Image. Dies erzeugt einen neuen Layer, der nur diese Datei enthält.
  • RUN pip install --no-cache-dir -r requirements.txt: Führt einen Befehl während des Build-Prozesses aus, um Abhängigkeiten zu installieren. Dies erzeugt einen neuen Layer mit allen installierten Paketen.
  • COPY app.py .: Kopiert den Anwendungscode und erzeugt einen weiteren Layer.
  • CMD ["python", "app.py"]: Legt den Standardbefehl beim Start des Containers fest. Erzeugt keinen Layer, sondern definiert das Standardverhalten.
  • EXPOSE 5000: Dient primär der Dokumentation. Es teilt Docker mit, dass der Container auf diesem Port lauscht, veröffentlicht ihn aber nicht automatisch. Erzeugt keinen Layer.
  • LABEL ...: Fügt Metadaten hinzu. Wie ENV-Anweisungen erzeugen sie keine Layer, sondern werden in den Metadaten gespeichert.

Jede RUN-, COPY- und ADD-Anweisung erzeugt einen neuen Layer. Layer sind ein Grundkonzept von Docker, das eine effiziente Speicherung und Übertragung ermöglicht. Wenn Sie Änderungen am Dockerfile vornehmen und das Image neu bauen, verwendet Docker unveränderte Layer aus dem Cache wieder, was den Prozess erheblich beschleunigt.

  1. Nachdem wir die Theorie verstanden haben, bauen wir das Image. Führen Sie im Terminal aus:
docker build -t advanced-flask-app .

Dieser Befehl erstellt ein Image mit dem Tag advanced-flask-app. Der Punkt . am Ende weist Docker an, das Dockerfile im aktuellen Verzeichnis zu suchen.

Sie sehen die Ausgabe für jeden Schritt. Achten Sie darauf, wie Docker bei wiederholten Builds "Using cache" anzeigt, wenn sich an einem Schritt nichts geändert hat.

  1. Sobald der Build fertig ist, starten wir einen Container basierend auf diesem Image:
docker run -d -p 5000:5000 --name flask-container advanced-flask-app

Erklärung der Parameter:

  • -d startet den Container im Hintergrund (Detached Mode).
  • -p 5000:5000 leitet den Port 5000 Ihres Hosts an den Port 5000 des Containers weiter.
  • --name flask-container gibt dem Container einen Namen.
  • advanced-flask-app ist das Image, das wir verwenden.

Prüfen Sie mit folgendem Befehl, ob der Container läuft:

docker ps
  1. Um die Anwendung zu testen, nutzen wir curl:
curl http://localhost:5000

Sie sollten die Nachricht "Hello from production environment!" sehen.

Falls curl nicht funktioniert, können Sie auch einen neuen Browser-Tab öffnen und http://localhost:5000 aufrufen. Bei Problemen helfen die Container-Logs:

docker logs flask-container

Mehrstufige Builds (Multi-stage Builds)

Nachdem wir die Grundlagen beherrschen, schauen wir uns eine fortgeschrittene Technik an: Multi-stage Builds. Diese erlauben es, mehrere FROM-Anweisungen in einem Dockerfile zu verwenden. Das ist besonders nützlich, um die finale Image-Größe zu reduzieren, indem man nur die notwendigen Artefakte von einer Stufe in die nächste kopiert.

Passen wir unser Dockerfile an, um ein kleineres Image zu erhalten:

  1. Öffnen Sie das Dockerfile in der WebIDE.
  2. Ersetzen Sie den Inhalt durch folgenden Code:
## Build stage
FROM python:3.9-slim AS builder

WORKDIR /app

COPY requirements.txt .

RUN pip install --user --no-cache-dir -r requirements.txt

## Final stage
FROM python:3.9-slim

WORKDIR /app

## Copy only the installed packages from the builder stage
COPY --from=builder /root/.local /root/.local
COPY app.py .

ENV PATH=/root/.local/bin:$PATH
ENV ENVIRONMENT=production

CMD ["python", "app.py"]

EXPOSE 5000

LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="1.0"
LABEL description="Flask app demo with multi-stage build"

Was passiert hier genau?

  1. Builder-Stufe (AS builder):

    • Wir nutzen Python 3.9-slim als Basis.
    • Wir installieren die Abhängigkeiten mit pip install --user. Dies speichert die Pakete im Home-Verzeichnis des Benutzers.
  2. Finale Stufe:

    • Wir starten erneut mit einem frischen Python 3.9-slim Image.
    • Wir kopieren nur die installierten Pakete aus der ersten Stufe (--from=builder), konkret aus /root/.local.
    • Wir kopieren den Anwendungscode.
    • Wir passen den PATH an, damit Python die installierten Pakete findet.

Der große Vorteil: Unser finales Image enthält keine Build-Tools oder Caches vom Installationsprozess, sondern nur das, was wirklich zum Ausführen benötigt wird.

  1. Bauen wir das neue Image:
docker build -t multi-stage-flask-app .
  1. Vergleichen wir die Größen der beiden Images:
docker images | grep flask-app
multi-stage-flask-app         latest     7bdd1be2d1fb   10 seconds ago   129MB
advanced-flask-app            latest     c59d6fa303cc   10 minutes ago   136MB

Das multi-stage-flask-app Image sollte kleiner sein als das ursprüngliche advanced-flask-app.

  1. Starten wir einen Container mit dem schlankeren Image:
docker run -d -p 5001:5000 --name multi-stage-container multi-stage-flask-app

Wir nutzen Port 5001, um Konflikte mit dem ersten Container zu vermeiden.

  1. Testen Sie die Anwendung:
curl http://localhost:5001
  1. Um die Unterschiede in der Struktur zu sehen, nutzen wir docker history:
docker history advanced-flask-app
docker history multi-stage-flask-app

Multi-stage Builds sind extrem mächtig, um effiziente Images zu erstellen, besonders bei kompilierten Sprachen oder komplexen Build-Prozessen.

Verwendung der .dockerignore-Datei

Beim Bauen eines Images sendet Docker alle Dateien des Verzeichnisses an den Docker-Daemon (den sogenannten Build-Kontext). Große, unnötige Dateien verlangsamen diesen Prozess. Mit einer .dockerignore-Datei können Sie festlegen, welche Dateien und Ordner ignoriert werden sollen.

  1. Erstellen Sie in der WebIDE im Ordner advanced-dockerfile eine neue Datei namens .dockerignore.
  2. Fügen Sie folgenden Inhalt hinzu:
**/.git
**/.gitignore
**/__pycache__
**/*.pyc
**/*.pyo
**/*.pyd
**/.Python
**/env
**/venv
**/ENV
**/env.bak
**/venv.bak

Bedeutung der Muster:

  • **/.git: Ignoriert den .git-Ordner überall im Projekt.
  • **/__pycache__: Ignoriert Python-Cache-Verzeichnisse.
  • **/venv: Ignoriert virtuelle Umgebungen.
  • Das Präfix ** bedeutet "in jedem beliebigen Unterverzeichnis".
  1. Um den Effekt zu demonstrieren, erstellen wir Testdateien, die ignoriert werden sollen:
mkdir venv
touch venv/ignore_me.txt
touch .gitignore
  1. Bauen wir das Image erneut:
docker build -t ignored-flask-app .
  1. Mit docker history können Sie verifizieren, dass keine unnötigen Schritte zum Kopieren dieser Dateien ausgeführt wurden:
docker history ignored-flask-app

Die .dockerignore-Datei sorgt für saubere Images und schnellere Build-Zeiten, besonders in großen Projekten.

Fortgeschrittene Dockerfile-Anweisungen

Im letzten Schritt schauen wir uns Anweisungen an, die Ihre Images sicherer, wartbarer und benutzerfreundlicher machen.

  1. Öffnen Sie das Dockerfile erneut.
  2. Ersetzen Sie den Inhalt durch:
## Build stage
FROM python:3.9-slim AS builder

WORKDIR /app

COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

## Final stage
FROM python:3.9-slim

## Create a non-root user
RUN useradd -m appuser

## Install curl for healthcheck
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

WORKDIR /app

## Dynamically determine Python version and site-packages path
RUN PYTHON_VERSION=$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') && \
    SITE_PACKAGES_PATH="/home/appuser/.local/lib/python${PYTHON_VERSION}/site-packages" && \
    mkdir -p "${SITE_PACKAGES_PATH}" && \
    chown -R appuser:appuser /home/appuser/.local

## Copy site-packages and binaries using the variable
COPY --from=builder /root/.local/lib/python3.9/site-packages "${SITE_PACKAGES_PATH}"
COPY --from=builder /root/.local/bin /home/appuser/.local/bin
COPY app.py .

ENV PATH=/home/appuser/.local/bin:$PATH
ENV ENVIRONMENT=production

## Set the user to run the application
USER appuser

## Use ENTRYPOINT with CMD
ENTRYPOINT ["python"]
CMD ["app.py"]

EXPOSE 5000

HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost:5000/ || exit 1

ARG BUILD_VERSION
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="${BUILD_VERSION:-1.0}"
LABEL description="Flask app demo with advanced Dockerfile techniques"

Neue Konzepte in diesem Dockerfile:

  • RUN useradd -m appuser: Erstellt einen Benutzer ohne Root-Rechte. Das Ausführen von Apps als Nicht-Root-User ist ein Sicherheitsstandard (Best Practice).
  • apt-get install -y curl: Installiert curl für den Gesundheitscheck (Healthcheck) und bereinigt danach den Cache, um Platz zu sparen.
  • USER appuser: Wechselt den aktiven Benutzer für die Ausführung der App.
  • ENTRYPOINT ["python"] kombiniert mit CMD ["app.py"]: ENTRYPOINT legt das Hauptprogramm fest, CMD liefert die Standardargumente. Dies erlaubt Flexibilität: Man kann den Container einfach starten oder die Argumente beim Start überschreiben.
  • HEALTHCHECK: Docker prüft regelmäßig, ob die App noch reagiert. Wenn der curl-Befehl fehlschlägt, wird der Container als "unhealthy" markiert.
  • ARG BUILD_VERSION: Definiert eine Variable, die nur während des Build-Vorgangs verfügbar ist.
  • LABEL version="${BUILD_VERSION:-1.0}": Nutzt das Argument für Metadaten. Falls kein Wert übergeben wird, wird standardmäßig 1.0 verwendet.
  1. Bauen wir das Image mit einer Versionsnummer:
docker build -t advanced-flask-app-v2 --build-arg BUILD_VERSION=2.0 .
  1. Überprüfen Sie das Image:
docker images | grep advanced-flask-app-v2
  1. Starten Sie den neuen Container:
docker run -d -p 5002:5000 --name advanced-container-v2 advanced-flask-app-v2
  1. Prüfen Sie, ob er läuft:
docker ps | grep advanced-container-v2

Falls er nicht läuft, prüfen Sie die Logs mit docker logs advanced-container-v2.

  1. Prüfen Sie den Gesundheitsstatus (warten Sie ggf. 30 Sekunden):
docker inspect --format='{{.State.Health.Status}}' advanced-container-v2
  1. Verifizieren Sie das Label:
docker inspect -f '{{.Config.Labels.version}}' advanced-container-v2

Sie sollten "2.0" sehen.

  1. Testen Sie die Anwendung final:
curl http://localhost:5002

Diese Techniken machen Ihre Images produktionsreif: Sicherer durch eingeschränkte Rechte, überwachbar durch Healthchecks und flexibel durch Build-Argumente.

Zusammenfassung

In diesem Lab haben wir fortgeschrittene Dockerfile-Techniken kennengelernt, um effiziente, sichere und wartbare Images zu erstellen. Wir haben behandelt:

  1. Detaillierte Anweisungen und Layer: Wie Befehle die Struktur und den Cache beeinflussen.
  2. Multi-stage Builds: Trennung von Build- und Laufzeitumgebung für minimale Image-Größen.
  3. .dockerignore: Ausschluss unnötiger Dateien für schnellere Builds.
  4. Erweiterte Befehle: USER für Sicherheit, ENTRYPOINT für Flexibilität, HEALTHCHECK für Monitoring und ARG für dynamische Builds.

Diese Methoden ermöglichen es Ihnen, professionelle Docker-Images zu erstellen, die den Anforderungen moderner Softwareentwicklung gerecht werden. Durch die Nutzung der WebIDE konnten wir den gesamten Prozess nahtlos im Browser durchführen.