Docker – Teil 4: Das Dockerfile

Die Docker Images aus dem letzten Teil der Reihe haben uns schon ein ganzes Stück weiter in Richtung Docker gebracht. Doch es gibt natürlich noch mehr coole Features. Wenn wir unsere Docker Images nicht jedes mal mühsam in der Konsole zusammenstellen wollen bevor wir sie dann wieder committen gibt es das sogenannte Dockerfile.

Ähnlich wie in anderen Umgebungen (zum Beispiel Vagrant) beschreibt das Dockerfile, wie die Umgebung aufgebaut werden soll. Was das genau bedeutet sehen wir uns jetzt genauer an.

Was ist das Dockerfile?

Ein Dockerfile ist eine simpel aufgebaute Textdatei. Darin wird der Aufbau eines Docker Images beschrieben. Am einfachsten können wir uns das als Schritt für Schritt Anleitung vorstellen, wie das Image aufgebaut werden soll:

  1. Starte Container vom Typ ubuntu
  2. Führe Befehl apt-get install apache2 aus
  3. Speichere neue Version des Images ab
  4. Führe Befehl apt-get install mysql aus
  5. Speichere neue Version des Images ab

Durch diesen Schritt für Schritt Vorgang kann das Image immer wieder auf dieselbe Art erzeugt werden. Dieses Dockerfile können wir dann natürlich auch unseren Kollegen schicken (oder es in einer Versionskontrolle wie z.B. GIT ablegen) und diese können dann genau dasselbe Image aus dem Dockerfile erzeugen.

 

Ein Image aus dem Dockerfile erzeugen

Um ein fertiges Image aus einem Dockerfile zu erzeugen genügt uns ein einfacher Befehl: docker build

Der build Befehl bekommt einen sogenannten Kontext als Parameter. Der Kontext ist einfach ein Verzeichnis dessen Inhalt an den Docker Daemon weitergeleitet wird. Dieser Inhalt steht dann für das Erzeugen des Images zur Verfügung.
Ganz einfach betrachtet könnte das zum Beispiel ein leeres Verzeichnis mit einem Dockerfile darin sein. Standardmäßig sucht Docker innerhalb des Kontextverzeichnis nach eben diesem Dockerfile, mit dem Parameter -f können wir aber auch einen anderen Ort dafür angeben.

Um unser Image zu bauen wechseln wir also innerhalb der Konsole in unser Verzeichnis und führen folgenden Befehl aus:

docker build -t ftiersch/mein-php-server .

Den Parameter -t kennen wir schon aus dem vorherigen Artikel. Dadurch weisen wir dem Image, das wir aus dem Dockerfile erzeugen, einen Namen zu den wir später wieder verwenden können. Ganz wichtig ist auch der Punkt am Ende. Der Punkt steht hier für das aktuelle Verzeichnis und beschreibt dementsprechend das Kontextverzeichnis, das zum Erzeugen des Images verwendet wird.

 

 

Aufbau des Dockerfiles

Unser Dockerfile ist herzlich einfach aufgebaut. In jeder Zeile steht genau ein Befehl (welche das sein können sehen wir uns gleich an) mit einem Parameter. Um Befehl von Parameter besser zu trennen hat sich die Konvention

BEFEHL parameter

durchgesetzt, wobei der Befehl durchgehend groß geschrieben wird. Einzelne Zeilen können dabei mit einem # am Anfang auskommentiert werden.

Beim Bauen wird unser Dockerfile Zeile für Zeile abgearbeitet. In jeder Zeile wird der passende Befehl ausgeführt und dabei ein Zwischenimage erzeugt. Dieses Zwischenimage wird dann als Grundlage für die nächste Zeile verwendet. Wird eine Zeile erfolgreich abgeschlossen bleibt das Image auch vorhanden, das heißt solange sich vorher im Dockerfile nichts ändert kann Docker beim nächsten build Aufruf direkt an dieser Stelle weitermachen. Das spart natürlich grade bei aufwendigen Befehlen wie der Installation von mehreren Anwendungen viel Zeit.

 

Befehl: FROM

Der wichtigste Befehl im Dockerfile ist FROM. Dieser muss ganz oben stehen und beschreibt ganz einfach, welches Image die Grundlage für unser neu erzeugtes Image ist. Das können sowohl Basisimages wie beispielsweise ubuntu oder ubuntu:trusty sein aber genauso natürlich von Usern (oder uns selbst) erzeugte Images wie ftiersch/mein-test-server.

FROM ubuntu:trusty

 

Befehl: MAINTAINER

Mit dem MAINTAINER Befehl können wir den Autor des Images setzen.

MAINTAINER Frank Tiersch <mail@ftiersch.de>

 

Befehl: RUN

Einer der wichtigsten Befehle ist sicherlich der Befehl RUN. Dieser führt den Parameter einfach als Shell Befehl aus, gibt uns also die Flexibilität, in unserer virtuellen Maschine quasi alles zu machen. Der Befehl

RUN sudo apt-get install -y apache2

wird also intern übersetzt in

/bin/sh -c sudo apt-get install -y apache2

Eine zweite Möglichkeit, den RUN Befehl auszuführen, ist mit der sogenannten exec Syntax.

RUN [„executable“, „param1“, „param2“]

In vielen Fällen werden unsere Dockerfiles also eine Verkettung vieler RUN Befehle sein. Auch hier nochmal die Erinnerung: Jeder RUN Befehl erzeugt einen neuen Layer im Image – verbraucht also Speicher. Das bedeutet wir sollten die Anzahl möglichst gering halten und mehrere Dinge innerhalb eines Befehles erledigen.

Anstatt also

RUN sudo apt-get install -y apache2

RUN sudo apt-get install -y nodejs

RUN sudo apt-get install -y npm

aufzurufen können wir das in einem Befehl verbinden:

RUN sudo apt-get install -y apache2 nodejs npm

 

 

Befehl: CMD

CMD bietet einem Container, der aus unserem Image erzeugt wird, einen Standardcommand der ausgeführt wird. Dieser kann allerdings natürlich vom Container auch überschrieben werden. Es kann nur einen CMD Befehl pro Dockerimage geben, werden mehrere definiert ist nur der letzte davon gültig. Von der Syntax her funktioniert CMD genau wie RUN.

CMD echo „Hello world“

 

Befehl: EXPOSE

Ein wichtiger Punkt bei Docker ist die Kommunikation inner- und außerhalb der Container. Dafür müssen Ports freigegeben werden und genau darum kümmert sich der EXPOSE Befehl. Möchten wir beispielsweise einen Webserver innerhalb eines Containers laufen lassen können wir mit

EXPOSE 80

dafür sorgen, dass der Port 80 überwacht wird.

 

Befehl: ADD / COPY

ADD und COPY sind zwei sehr ähnliche Befehle – wobei ADD etwas mehr kann als COPY. Aber diese Feinheiten gehen für unser Einsteigertutorial zu weit.

Beide Befehle nehmen eine Datei oder ein Verzeichnis auf dem Hostsystem (oder genauer: dem Kontextverzeichnis das wir weiter oben kennengelernt haben) und fügen diese in das Dateisystem des Images ein. Die Syntax ist dabei sehr simpel:

ADD directory1 /var/www/directory1

Zuerst kommt das Verzeichnis oder die Datei, die kopiert werden soll und als zweites das Ziel. Für COPY sieht die Syntax gleich aus. Beide haben außerdem eine alternative Syntax, die verwendet werden muss, wenn einer der Namen Leerzeichen enthält:

COPY [„directory1“, „/var/www/directory1“]

Diese Befehle ermöglichen es uns zum Beispiel Konfigurationsdateien (wie die Apache2 Konfiguration oder eine php.ini) in unser neu angelegtes Image zu überspielen.

 

Befehl: WORKDIR

Jeder RUN, CMD, ADD und COPY Befehl geht erstmal vom root Verzeichnis aus. Möchten wir das ändern funktioniert das mit dem WORKDIR Befehl ganz einfach. Dieser setzt das neue Referenzverzeichnis für alle folgenden Befehle (oder bis zum nächsten Aufruf von WORKDIR).

WORKDIR /var/www

Dabei kann ich auch einen relativen Pfad übergeben, der sich am letzten WORKDIR Aufruf orientiert.

WORKDIR vhosts/www.meine-seite.de

Der neue Referenzpfad nach den obigen beiden Aufrufen wäre also /var/www/vhosts/www.meine-seite.de

 

 

Das war mal ein Überblick über die wichtigsten Befehle im Dockerfile. Für mehr Informationen solltest du am besten die offizielle Docker Dokumentation anschauen. Wie benutzt du das Dockerfile am liebsten? Hinterlass mir gerne einen Kommentar.

Immer aktuelle Artikel zum Thema Docker, PHP und JavaScript? Dann folge mir einfach bei Twitter unter @AbHeuteCoden.

8 Kommentare zu “Docker – Teil 4: Das Dockerfile

  1. Hallo, erstmal danke für diesen sehr interessanten und hilfreichen Beitrag, ich hätte aber noch eine Frage: welche Hardwareanforderungen/Systemanforderung benötigt Docker? Vielen Dank

    • Hi Verena,

      vielen Dank.

      Das mit den Anforderungen ist eine gute Frage, grundsätzlich sollten diese aber nicht sonderlich hoch sein. Das Besondere an Docker ist, dass es viele Systemressourcen unter seinen Containern aufteilt anstatt für jeden Container wieder neue Ressourcen zu vergeben wie beispielsweise eine normale virtuelle Maschine. Dadurch ist das Ganze wesentlich leistungsschonender als ähnliche Produkte.

      Viele Grüße
      Frank

  2. Hallo Frank,

    auch von mir ein riesen Danke für die gute Zusammenfassung, auch mal in deutscher Sprache. Hat einige offene Fragen geklärt, dennoch bleiben einige weiterhin offen. Eventuell kannst du die Serie ja noch etwas weiterführen, oder mir etwas helfen. Was ich leider noch nicht ganz verstanden habe ist, wie man nun die Verlinkung der einzelnen Container vornimmt und diese miteinander kommunizieren lässt. Ich versuche aktuell eine Kompination aus Apache, PHP und MySQL herzustellen, um am Ende eine Drupal Installation zum laufen zu bekommen. Wenn ich es nun richtig verstanden habe, müsste ich am Ende ja den PHP Container mit dem Apache Container und den PHP Container ebenfalls mit dem MySQL Container linken, damit diese miteinander arbeiten können. Korrekt?

    Dann noch eine Frage zu den Dockerfiles. Für jedes Image muss ja ein eigenes Dockerfile erzeugt werden (ok man kann wohl auch ein großes nehmen, aber eher unschön). Jetzt ist die Frage, ob ich die auch in ein Unterverzeichnis auslagern kann, um mehr Ordnung in das Projekt zu bekommen.

    Beispiel:
    docker/.mysql
    docker/.apache
    docker/.php

    Erstmal das, danah sicher noch mehr. 🙂

    Danke und Gruß
    Marcus

  3. Ok nochmal gelesen und meine Frage mit den Dockerfiles muss ich nochmal etwas ändern 😀
    Unterverzeichnisse gehen. Aber. Können auch alle in ein Verzeichnis, oder müssen die Dockerfiles alle in eigene Verzeichnisse? Ich hoffe ich schmeiß jetzt nicht zu viel zu später Stunde durcheinander. Ich teste parallel einfach mal ein wenig.

    • Hi Marcus,

      danke fürs Kompliment! 🙂

      Genau so einen Artikel wollte ich in naher Zukunft sowieso schreiben (allerdings mit Nginx statt Apache, bin da inzwischen umgestiegen seit ich mich etwas mit Webservern beschäftige). Da es aber noch eine Weile dauert, bis dieser rauskommt, hier schon mal ein paar Stichpunkte damit du hoffentlich fix ein Ergebnis erreichst:

      Ich würde Docker Compose für diesen Zweck empfehlen, damit kannst du ganz einfach mehrere Container starten und verwalten und auch ihre Verlinkung mit einer einzigen YAML Datei konfigurieren.
      Das Prinzip hast du richtig verstanden – der PHP (bzw PHP-FPM in dem Fall) Container muss sowohl mit dem Webserver verlinkt werden (damit der Webserver PHP sagen kann, welche Dateien ausgeführt werden sollen) als auch mit MySQL (damit eine Verbindung hergestellt werden kann). Dabei sind zwei Dinge wichtig:

      1. PHP-FPM muss die richtigen Ports öffnen (z.B. 9000, je nachdem was du in der Webserver Konfiguration einstellst), der Webservercontainer muss die richtigen Ports öffnen (z.B. 80 und 443 für HTTP und HTTPS) und MySQL muss die richtigen Ports öffnen (3306 standardmäßig)
      2. Sowohl der Webserver als auch PHP-FPM brauchen Zugriff auf die PHP Dateien (da der Webserver statische Dateien wie Bilder ja z.B. ausliefern muss und PHP-FPM PHP Dateien auswerten muss). Entweder verlinkst du dafür ein Verzeichnis des Hosts in einen der beiden Container (als Volume) oder – und das würde ich empfehlen – du legst noch einen weiteren Container an, der einfach nur die Aufgabe hat, ein Datencontainer für eben die Dateien zu sein. In diesem Container legst du ein Volume an und gibst im docker-compose.yml dann bei Webserver + PHP-FPM diesen Container unter volumes_from an.
      Damit hättest du die Container wunderschön aufgeteilt und wenn du jetzt plötzlich auf die Idee kommst, z.B. PHP oder den Webserver upzudaten musst du nur diesen einen Container verändern, neu starten und läuft (hoffentlich ;)).

      Zu deiner zweiten Frage:
      Du kannst auch gut vorgefertigte Images benutzen, die meisten Dinge gibt es irgendwo im Dockerhub schon. Beispielsweise habe vor einigen Tagen genau das zum Testen mal gemacht (PHP7, Nginx und eben einen Datencontainer) und hab dafür das fertige Nginx Image und das fertige PHP-FPM7 Image benutzt. Der einzige Grund, dass ich doch ein Dockerfile für Nginx gebraucht habe, war dann, dass ich die Konfigurationsdatei in den Container kopieren musste. Du kannst auch deine eigenen Images „fertig machen“, ins Dockerhub hochladen und dann in der docker-compose.yml referenzieren – räumt auch das Projekt auf.

      Möchtest du lieber alles an einem Ort (und jedes mal neu builden) kannst du aber statt dem Befehl image einfach den Befehl build benutzen. Liegt das Dockerfile im selben Ordner machst du einfach build: . , liegt das Dockerfile in einem Unterverzeichnis gibst du hier das Unterverzeichnis an.

      Ich hoffe, das hat schon mal geholfen 🙂 Wenn du noch Fragen hast, immer raus damit.

      Viele Grüße
      Frank

      PS: Das könnte ich ja schon fast in nen Artikel kopieren so lang wie es jetzt geworden ist 😀

  4. Hi Markus,
    vielen herzlichen Dank für die Docker-Serie.
    Sie ist detailliert und in einfacher Sprache beschrieben, so dass jeder sie verstehen kann, v.a. bei einem Newbie wie ich. 🙂
    Wann planst du den letzten Teil (Teil 5) herauszugeben? Ich glaube einige Leser warten wie ich gespannt darauf.
    Grüße
    Sigit

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*