Eigenes Docker-Image bauen: Dockerfile-Grundlagen

Fertige Images vom Docker Hub reichen oft nicht – sobald du eigenen Code oder eine spezielle Konfiguration mitliefern willst, brauchst du ein eigenes Image. Dafür gibt es das Dockerfile. In dieser Anleitung lernst du die Grundlagen: die wichtigsten Anweisungen, wie du ein eigenes Docker-Image baust und startest – und wie du es schlank hältst.

Ein eigenes Docker-Image zu bauen ist der Moment, in dem du von einem reinen Docker-Nutzer zum Entwickler eigener Container wirst. Statt fertige Container-Images vom Docker Hub zu übernehmen, beschreibst du im Dockerfile reproduzierbar, wie deine Anwendung samt Abhängigkeiten, Konfiguration und Startbefehl aussehen soll. Das Ergebnis läuft auf deinem Laptop genauso wie auf dem Server – ein großer Vorteil gegenüber manueller Installation. In diesem Guide gehen wir über die reinen Grundlagen hinaus und schauen uns auch Build-Kontext, Layer-Caching und Multi-Stage-Builds an, damit deine Images klein, sicher und schnell gebaut sind.

Was ist ein Dockerfile?

Ein Dockerfile ist eine Textdatei mit einer Bauanleitung. Docker liest sie Zeile für Zeile und erzeugt daraus ein Image – eine unveränderliche Vorlage, aus der du beliebig viele Container startest. Wenn dir die Begriffe noch fremd sind, hilft Was ist Docker?.


Die wichtigsten Anweisungen

AnweisungBedeutung
FROMBasis-Image, auf dem aufgebaut wird
WORKDIRArbeitsverzeichnis im Image
COPYDateien vom Host ins Image kopieren
RUNBefehl beim Bauen ausführen (z. B. Pakete installieren)
EXPOSEDokumentiert den genutzten Port
CMDStandardbefehl beim Start des Containers

Ein einfaches Dockerfile

Das folgende Beispiel baut ein Image für eine kleine Python-Anwendung:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]

Image bauen und starten

Im Verzeichnis mit dem Dockerfile baust du das Image mit docker build. Der Parameter -t vergibt einen Namen (Tag):

docker build -t meine-app:1.0 .

Anschließend startest du einen Container daraus:

docker run -d -p 8000:8000 --name app meine-app:1.0

Tipp: Images schlank halten

  • Schlanke Basis wählen: Varianten wie -slim oder alpine sparen viel Platz.
  • .dockerignore nutzen: Schließe unnötige Dateien (z. B. .git) vom Build aus.
  • Layer zusammenfassen: Fasse zusammengehörige RUN-Befehle mit && zusammen.

Große, ungenutzte Build-Reste entfernst du später beim Aufräumen.


Build-Kontext und .dockerignore verstehen

Wenn du docker build -t meine-app:1.0 . ausführst, ist der Punkt am Ende der sogenannte Build-Kontext: das gesamte Verzeichnis, das Docker an den Daemon überträgt. Alles, was dort liegt, kann per COPY ins Image gelangen – aber alles wird auch erst einmal übertragen. Bei großen Projektordnern verlangsamt das den Build spürbar und schleust im schlimmsten Fall Geheimnisse ein.

Die Lösung ist eine .dockerignore-Datei neben dem Dockerfile. Sie funktioniert wie eine .gitignore und hält unnötige oder sensible Dateien aus dem Kontext heraus:

  • .git – die komplette Versionshistorie gehört nicht ins Image.
  • node_modules oder venv – Abhängigkeiten installierst du im Build ohnehin neu.
  • .env und Schlüsseldateien – Secrets haben in einem Image nichts verloren.
  • *.log, tmp/, Testdaten – aufblähen ohne Nutzen.

Layer-Caching clever nutzen

Jede Anweisung im Dockerfile erzeugt einen Layer. Docker speichert diese Layer und verwendet sie beim nächsten Build wieder, solange sich Eingaben nicht geändert haben. Genau deshalb steht im Beispiel oben COPY requirements.txt . und der RUN pip install vor dem COPY . . des restlichen Codes: Solange sich deine Abhängigkeiten nicht ändern, bleibt der teure Installationsschritt im Cache und der Build dauert Sekunden statt Minuten.

Als Faustregel gilt: Ordne die Anweisungen von selten geändert (Basis-Image, System-Pakete, Abhängigkeiten) nach häufig geändert (dein Quellcode). Sobald ein Layer neu gebaut werden muss, werden auch alle nachfolgenden Layer verworfen – deshalb kostet eine ungünstige Reihenfolge bei jedem kleinen Codeänderung unnötig Zeit.

Multi-Stage-Builds für schlanke Images

Compiler, Build-Tools und Entwicklungs-Bibliotheken brauchst du zum Bauen, aber nicht zur Laufzeit. Mit einem Multi-Stage-Build nutzt du eine erste Stufe zum Kompilieren und kopierst nur das fertige Artefakt in ein schlankes finales Image. Dazu definierst du mehrere FROM-Anweisungen und kopierst mit COPY --from=builder gezielt Dateien aus der Build-Stufe.

Das Ergebnis ist ein Image, das oft nur einen Bruchteil der ursprünglichen Größe hat – ein Go- oder Node-Build schrumpft so schnell von mehreren Hundert MB auf wenige Dutzend. Kleinere Images bedeuten schnelleres Deployment, weniger Angriffsfläche und geringeren Speicherverbrauch in deiner Docker-Compose-Umgebung.

Bevor du dein Image auf einen Server bringst, lohnt sich ein kurzer Blick auf ein paar bewährte Praktiken, die Sicherheit und Wartbarkeit deutlich verbessern:

  • Feste Versionen taggen: Nutze python:3.12-slim statt latest, damit Builds reproduzierbar bleiben.
  • Nicht als root laufen: Lege mit RUN useradd einen eigenen Benutzer an und wechsle per USER-Anweisung dorthin.
  • Nur nötige Ports öffnen: EXPOSE dokumentiert klar, worüber dein Dienst erreichbar ist.
  • Regelmäßig neu bauen: So landen Sicherheitsupdates des Basis-Images automatisch in deinem Container-Image.

Häufige Fragen

Rund um das Bauen eigener Images tauchen immer wieder dieselben Fragen auf. Hier die wichtigsten kurz beantwortet.

Was ist der Unterschied zwischen CMD und ENTRYPOINT?

CMD legt den Standardbefehl fest, den du beim docker run leicht überschreiben kannst. ENTRYPOINT definiert einen festen Startbefehl, an den zusätzliche Argumente angehängt werden. Für einfache Anwendungen reicht CMD; brauchst du ein Image, das sich wie ein einzelnes Kommando verhält, kombinierst du beide.

Warum ist mein Docker-Image so groß?

Meist liegt es am Basis-Image oder an zwischengespeicherten Paketlisten. Wähle eine -slim– oder alpine-Variante, räume im selben RUN-Layer auf (etwa rm -rf /var/lib/apt/lists/*) und prüfe mit docker image ls sowie docker history meine-app:1.0, welche Layer den Platz belegen.

Wie aktualisiere ich ein Image nach Codeänderungen?

Du baust es einfach neu mit docker build -t meine-app:1.1 .. Dank Layer-Caching werden nur die geänderten Schritte neu ausgeführt. Anschließend startest du einen frischen Container aus dem neuen Tag – der alte Container übernimmt Änderungen nicht automatisch.


Fazit

Mit FROM, COPY, RUN und CMD hast du die vier wichtigsten Bausteine eines Dockerfiles beisammen. docker build macht daraus ein Image, aus dem du Container startest. Achte auf eine schlanke Basis, dann bleiben deine Images klein und schnell. Im nächsten Schritt kombinierst du dein Image mit Docker Compose und einem Healthcheck.

Siehe auch  unRAID Docker: GUID und PGID erklärt
Nach oben scrollen