Zum Hauptinhalt springen

8. Wie funktioniert das Image-Building?

Dockerfile

Docker kann Container-Images erstellen, indem es Anweisungen aus einem sogenannten Dockerfile oder allgemeiner einem Containerfile liest. Die grundlegende Dokumentation dazu, wie Dockerfiles funktionieren, findest du unter https://docs.docker.com/engine/reference/builder/.

Schreibe dein erstes Dockerfile

Bevor wir unser Python-Image erweitern, werden wir uns generell anschauen, wie man ein Container-Image erstellt. Dafür erstelle ein neues Verzeichnis mit einem leeren Dockerfile darin.

mkdir myfirstimage
cd myfirstimage

Füge den folgenden Inhalt zum Dockerfile mit deinem bevorzugten Editor hinzu:

Dockerfile
FROM ubuntu
RUN apt-get update && \
apt-get install -y figlet && \
apt-get clean

Baue das Image

Führe einfach aus:

docker build -t myfirstimage .
  • -t gibt das Tag an, das auf das Image angewendet werden soll
  • . gibt den Ort des Build-Kontexts an (über den wir später noch mehr sprechen werden, aber im Grunde ist es das Verzeichnis, in dem unser Dockerfile liegt)
Hinweis

Verwende den zusätzlichen Parameter --build-arg, wenn du hinter einem Firmenproxy bist:

docker build -t myfirstimage --build-arg http_proxy=http://<username>:<password>@<proxy>:<port> .

Bitte beachte, dass das Tag in den meisten Docker-Befehlen und Anweisungen weggelassen werden kann. In diesem Fall ist das Standard-Tag latest. Abgesehen davon, dass es das Standard-Tag ist, hat latest nichts Besonderes. Trotz seines Namens identifiziert es nicht notwendigerweise die neueste Version eines Images. Je nach Build-System kann es auf das zuletzt gepushte Image, auf das zuletzt aus einem Zweig gebaute Image oder auf ein altes Image zeigen. Es kann sogar überhaupt nicht existieren. Aus diesem Grund solltest du niemals das Tag latest in Produktion verwenden, verwende immer eine spezifische Image-Version. Siehe auch: https://medium.com/@mccode/the-misunderstood-docker-tag-latest-af3babfd6375

Was passiert beim Build des Images

Die Ausgabe des Docker-Builds sieht folgendermaßen aus:

Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM ubuntu
---> ea4c82dcd15a
Step 2/2 : RUN apt-get update && apt-get install -y figlet && apt-get clean
---> b3c08112fd1c
Successfully built b3c08112fd1c
Successfully tagged myfirstimage:latest

Das Senden des Build-Kontexts an Docker

Sending build context to Docker daemon 84.48 kB
...
  • Der Build-Kontext ist das .-Verzeichnis, das an docker build übergeben wurde
  • Es wird (als Archiv) vom Docker-Client an den Docker-Daemon gesendet
  • Dies ermöglicht es dir, eine Remote-Maschine zum Bauen unter Verwendung lokaler Dateien zu verwenden
  • Sei vorsichtig (oder geduldig), wenn dieses Verzeichnis groß ist und deine Verbindung langsam ist

Untersuchung der Ausführungsschritte

...
Step 1/2 : FROM ubuntu
---> ea4c82dcd15a
Step 2/2 : RUN apt-get update && apt-get install -y figlet && apt-get clean
---> b3c08112fd1c
Successfully built b3c08112fd1c
Successfully tagged myfirstimage:latest
  • Ein Container (ea4c82dcd15a) wird aus dem Basis-Image erstellt
    • Das Basis-Image wird heruntergeladen, falls es vorher nicht heruntergeladen wurde
  • Der RUN-Befehl wird in diesem Container ausgeführt
  • Der Container wird zu einem Image (b3c08112fd1c) committed
  • Der Build-Container (ea4c82dcd15a) wird entfernt
  • Das Ergebnis dieses Schritts wird die Basis für den nächsten sein
  • ...

Das Caching-System

Wenn du denselben Build erneut ausführst, wird er sofort abgeschlossen. Warum?

  • Nach jedem Build-Schritt macht Docker ein Snapshot
  • Bevor ein Schritt ausgeführt wird, überprüft Docker, ob es dieselbe Sequenz bereits gebaut hat
  • Docker verwendet die exakten Strings, die in deinem Dockerfile definiert sind:
    • RUN apt-get install figlet cowsay unterscheidet sich von
    • RUN apt-get install cowsay figlet
    • RUN apt-get update wird nicht erneut ausgeführt, wenn die Spiegel aktualisiert werden
  • Alle Schritte nach einem geänderten Schritt werden erneut ausgeführt, da das Dateisystem, auf dem sie basieren, geändert worden sein könnte

Du kannst einen Neubau mit docker build --no-cache ... erzwingen.

Wenn du nur einen teilweisen Neubau auslösen möchtest, z.B. apt-get update ausführen, um die neuesten Updates zu installieren, kannst du folgendes Muster verwenden:

ENV REFRESHED_AT 2020-03-13
RUN apt-get update

Wenn du den Wert von REFRESHED_AT aktualisierst, wird der Docker-Build-Cache dieses und aller folgenden Schritte invalidiert, wodurch die neuesten Updates installiert werden.

Führe es aus

Führe jetzt dein Image aus

docker run -ti myfirstimage

Du findest dich in einer Bash-Shell im Container wieder, führe aus

figlet hello

und du wirst folgende Ausgabe sehen:

root@00f0766080ed:/# figlet hello

_ _ _
| |__ ___| | | ___
| '_ \ / _ \ | |/ _ \
| | | | __/ | | (_) |
|_| |_|\___|_|_|\___/

root@00f0766080ed:/#

Verlasse den Container durch Ausführen von:

exit

Die CMD-Anweisung im Dockerfile

Mit der CMD-Anweisung im Dockerfile können wir den Befehl definieren, der ausgeführt wird, wenn ein Container gestartet wird.

🤔 Kannst du herausfinden, welche CMD-Anweisung das Ubuntu-Image hat?

Du hast dich in einer Shell befunden, daher muss die Anweisung entweder /usr/bin/bash oder /usr/bin/sh sein.

Modifiziere das zuvor erstellte Dockerfile wie folgt:

FROM ubuntu
RUN apt-get update && \
apt-get install -y figlet && \
apt-get clean

CMD ["figlet", "hello"]

Baue das Image mit:

docker build -t myfirstimagecmd .
Hinweis

Verwende erneut den zusätzlichen Parameter --build-arg, wenn du hinter einem Firmenproxy bist:

docker build -t myfirstimagecmd --build-arg http_proxy=http://<username>:<password>@<proxy>:<port> .

Und führe es aus:

docker run -ti myfirstimagecmd

Es führt direkt den definierten Befehl aus und gibt aus

 _          _ _
| |__ ___| | | ___
| '_ \ / _ \ | |/ _ \
| | | | __/ | | (_) |
|_| |_|\___|_|_|\___/

Weitere Informationen findest du unter https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact.

Frontend-App-Image bauen

Nachdem wir die Grundlagen des Image-Buildings verstanden haben, möchten wir nun den Quellcode unserer Frontend-App in ein bereits erstelltes Container-Image einbinden. Dazu werden wir ein Dockerfile erstellen.

Das Basis-Image ist unser php:8-apache-Image, das wir zuvor verwendet haben. Der ADD-Befehl ermöglicht es uns, Dateien aus unserem aktuellen Verzeichnis zum Docker-Image hinzuzufügen. Wir verwenden diesen Befehl, um den Anwendungsquellcode in das Image einzufügen.

Hinweis

Verwende .dockerignore, um Dateien vom Hinzufügen zum Container durch den Docker-Kontext auszuschließen. Es funktioniert genauso wie .gitignore: https://docs.docker.com/engine/reference/builder/#dockerignore-file

Im Verzeichnis, das das Unterverzeichnis python-app enthält, erstelle ein Dockerfile mit dem folgenden Inhalt:

FROM python:3.12-slim

# Kopiert den Python-Quellcode an den korrekten Ort
ADD ./python-app/ /app/

# Setzt das Arbeitsverzeichnis
WORKDIR /app

# Installiert Abhängigkeiten
RUN pip install -r requirements.txt

# Startet die Anwendung
CMD ["python", "app.py"]

Baue das python-app-Image

Hinweis

Stoppe und lösche den laufenden python-app-Container zuerst. Lasse den Datenbank-Container laufen.

Baue nun das Image:

docker build -t python-app .

Führe den python-app-Container aus

Nach einem erfolgreichen Build führe ihn aus:

docker run -d --network container-basics-training --name python-app -p 5000:5000 python-app

Öffne nun einen Browser und navigiere zu http://localhost:5000 (oder in der Webshell verwende curl http://localhost:5000). Du solltest eine Antwort erhalten, die besagt, dass die Verbindung erfolgreich hergestellt wurde.