{"id":7897,"date":"2021-11-14T02:46:43","date_gmt":"2021-11-14T00:46:43","guid":{"rendered":"https:\/\/u-labs.de\/portal\/?p=7897"},"modified":"2022-12-12T18:25:24","modified_gmt":"2022-12-12T16:25:24","slug":"dockerfile-einstieg-fuer-anfaenger-was-ist-ein-dockerfile-und-ein-image-wie-kann-ich-images-anpassen","status":"publish","type":"post","link":"https:\/\/u-labs.de\/portal\/dockerfile-einstieg-fuer-anfaenger-was-ist-ein-dockerfile-und-ein-image-wie-kann-ich-images-anpassen\/","title":{"rendered":"Dockerfile Einstieg f\u00fcr Anf\u00e4nger: Was ist ein Dockerfile und ein Image? Wie kann ich Images anpassen?"},"content":{"rendered":"<p>In den bisherigen Beitr\u00e4gen haben wir ausschlie\u00dflich fertige Images aus dem Docker-Hub genutzt, beispielsweise f\u00fcr die Webserver Nginx und Apache. F\u00fcr einfachere Anwendungsf\u00e4lle reicht das. Wer sich tiefergehend mit Docker besch\u00e4ftigt, wird jedoch irgendwann an die Grenzen sto\u00dfen und m\u00f6chte ein Image anpassen oder gar ein komplett eigenes erstellen. Dies m\u00f6chten wir im heutigen Beitrag genauer anschauen.<\/p>\n<h2 class=\"wp-block-heading\">Was ist ein Dockerfile?<\/h2>\n<p>Die Antwort in der Frage liegt in den Images. Was ist ein Image? Es besteht aus einer Reihe von Anweisungen, um eine bestimmte Software zu installieren und einzurichten. Dies kann beispielsweise der Apache-Webserver sein, eben so wie nahezu jede andere Software. Werden diese Anweisungen ausgef\u00fchrt, entsteht im sogenannten <em>Build-Prozess<\/em> ein Image. Diese Anweisungen finden sich im Dockerfile. Zus\u00e4tzlich noch einige weitere Befehle, mit denen sich ein so erstelltes Image von au\u00dfen beeinflussen l\u00e4sst &#8211; beispielsweise mit Umgebungsvariablen.<\/p>\n<h2 class=\"wp-block-heading\">Warum sollte ich mit einem Dockerfile ein eigenes Image erstellen?<\/h2>\n<p>Haupts\u00e4chlich gibt es zwei Gr\u00fcnde:<\/p>\n<ol class=\"wp-block-list\">\n<li>Um ein vorhandenes Image umfangreicher anzupassen<\/li>\n<li>Zum &#8222;Containerisieren&#8220; von Programmen, f\u00fcr die noch kein bzw. kein (offizielles) Image existiert<\/li>\n<\/ol>\n<h2 class=\"wp-block-heading\">Wie sieht ein Dockerfile genau aus?<\/h2>\n<p>Der grunds\u00e4tzliche Aufbau eines Dockerfiles ist leicht erkl\u00e4rt: Kommentare werden mit einer Raute # eingeleitet. Alle Befehle weisen das Format <strong>ANWEISUNG &lt;Argumente&gt;<\/strong> auf:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-makefile\" data-line=\"\"># Diese Zeile wird ignoriert\nANWEISUNG &lt;Argumente&gt;<\/code><\/pre>\n<p>Da sich Docker durch Modularit\u00e4t auszeichnet, gibt es immer ein <strong>Basis-Image<\/strong>. Es wird mit der Anweisung <strong>FROM<\/strong> definiert, das Argument ist der Name des Basis-Images. Alles was dort drin ist, wird auch in unserem Image enthalten sein &#8211; es sei denn, wir \u00e4ndern und entfernen etwas davon. In der Praxis ist das <strong>Dockerfile <\/strong>eine Textdatei mit gleichem Name, ohne .txt oder anderen Erweiterungen. Ich empfehle, pro Dockerfile einen eigenen Ordner zu erstellen.<\/p>\n<h2 class=\"wp-block-heading\">Unser erstes Dockerfile: Wir f\u00fcgen eine Datei hinzu<\/h2>\n<p>Beginnen wir mit dem ersten Grund, er ist am einfachsten: Wir haben ein Image, dass grunds\u00e4tzlich unseren Vorstellungen entspricht. Aber es gibt Dinge, die angepasst werden sollen. Ein m\u00f6gliches Szenario ist der Apache2 Webserver mit mod_php aus dem vorherigen Beitrag. Hier wird ein PHP-Modul ben\u00f6tigt, etwa MySQLi um sich zu einer MySQL-Datenbank verbinden zu k\u00f6nnen. Dies ist standardm\u00e4\u00dfig nicht enthalten bzw. nicht aktiviert.<\/p>\n<p>Um das zu \u00e4ndern, legen wir uns einen Ordner an, erzeugen eine Datei namens <strong>Dockerfile<\/strong> ohne Erweiterung und \u00f6ffnen sie. Es macht Sinn, mit der <strong>FROM <\/strong>Anweisung zu beginnen. Nach einem Leerzeichen geben wir das Apache-PHP-Image mit dem gew\u00fcnschten Tag an, etwa<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-makefile\" data-line=\"\">FROM php:8-apache-buster<\/code><\/pre>\n<div style=\"color: orange;font-weight:bold; margin-bottom: 15px\">\nAn dieser Stelle wird das Buster-Image (Version 10, statt dem zum Erstellzeitpunkt aktuellsten Bullseye 11) image genutzt. Hintergrund ist <a href=\"https:\/\/stackoverflow.com\/questions\/68843094\/docker-php-ext-install-gets-stuck-on-raspberry-pi-arm32v6\" rel=\"nofollow\" target=\"_blank\">dieser Bug im PHP-Image<\/a>, wodurch der sp\u00e4ter verwendete Befehl <\/p>\n<pre style=\"display:inline\">docker-php-ext-install<\/pre>\n<p> ohne Ausgabe nie fertiggestellt wird. Bitte den Status im verlinkten Bugticket pr\u00fcfen, falls dieser Fehler behoben ist, kann man auch aktuellere Images nutzen! Buster erh\u00e4lt noch bis Ende 2022 Sicherheitsupdates.\n<\/p><\/div>\n<p>Die wohl wichtigste Anweisung nach <strong>FROM <\/strong>hei\u00dft <strong>RUN<\/strong>: Damit f\u00fchrt man einen beliebigen Linux-Befehl aus. Wir nutzen im Folgenden die <em>Shell-Form<\/em>, das hei\u00dft: Im Hintergrund wird eine <strong>SH-Shell<\/strong> gestartet und unser Befehl darin ausgef\u00fchrt. Ein einfaches Beispiel ist ein echo-Befehl:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-makefile\" data-line=\"\">RUN echo Hallo Docker!<\/code><\/pre>\n<p>Wichtig zu verstehen ist: Ein Image ist eine Vorlage f\u00fcr einen Container. Es besteht aus mehreren Schichten eines Dateisystemes. Wie bei einer Schablone k\u00f6nnen wir mithilfe eines Images also beliebig viele identische Container erstellen. Mit diesem Verst\u00e4ndnis wird deutlich, dass obiger Befehl wenig Sinn ergibt: Beim ersten Bauen des Images w\u00fcrde der Echo-Befehl ausgef\u00fchrt. F\u00fcr das Image hat er keine Bedeutung, da er nichts ins Dateisystem schreibt. Ein sinnvolleres Beispiel w\u00e4re, wenn wir etwa eine PHP-Datei f\u00fcr den Webserver ablegen w\u00fcrden:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-makefile\" data-line=\"\">RUN echo &#039;&lt;?php echo &quot;Hallo vom Dockerfile&quot;;&#039; &gt; \/var\/www\/html\/hallo.php<\/code><\/pre>\n<p>Damit sind wir an dieser Stelle f\u00fcr die erste Demo fertig und k\u00f6nnen die Datei speichern.<\/p>\n<h3 class=\"wp-block-heading\">Anlegen einer docker-compose.yml, die unser Image baut und einen Container startet<\/h3>\n<p>Aus dem <strong>Dockerfile <\/strong>l\u00e4sst sich ein Image bauen, welches f\u00fcr einen Container genutzt werden kann. Diese Schritte lassen sich h\u00e4ndisch und getrennt ausf\u00fchren. In der Praxis ist das m\u00fchsam. Wenn man Images f\u00fcr sich selbst entwickelt, die nicht \u00fcber z.B. den Docker-Hub mit anderen geteilt werden sollen, ist Docker-Compose auch hier der einfachste und sinnvollste Weg. Wir k\u00f6nnen dort n\u00e4mlich automatisch ein Image bauen und einen Container erstellen lassen.<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\" data-line=\"\">version: &quot;2.4&quot;\n\nservices:\n    mein-webserver:\n        build: .\n        restart: always\n        ports:\n            - 80:80<\/code><\/pre>\n<p>Die Besonderheit liegt darin, dass wir kein <strong>image<\/strong> angeben &#8211; sondern <strong>build<\/strong>. Das legt den sogenannten Buildkontext fest, also den Pfad, in dem ein Dockerfile gebaut wird. Der Punkt steht unter Linux f\u00fcr das aktuelle Verzeichnis. Dort sucht Compose nach einem Dockerfile. Sofern wir dies nicht anders benennen, m\u00fcssen wir keinen Dateiname angeben. Mit <strong>docker-compose up -d &#8211;build<\/strong> l\u00f6sen wir nun zwei Vorg\u00e4nge aus:<\/p>\n<ol class=\"wp-block-list\">\n<li>Anhand des Dockerfiles wird ein Image gebaut<\/li>\n<li>Das Starten eines Containers mit dem zuvor erstellten Image<\/li>\n<\/ol>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\" data-line=\"\">$ docker-compose up -d --build\nSending build context to Docker daemon     335B\nStep 1\/3 : FROM php:8-apache-buster\n ---&gt; e2070fb1b17d\nStep 2\/3 : RUN echo Hallo Docker!\n ---&gt; Running in d51571677206\nHallo Docker!\n ---&gt; 356e1b361b67\nStep 3\/3 : RUN echo &#039;&lt;?php echo &quot;Hallo vom Dockerfile&quot;;&#039; &gt; \/var\/www\/hallo.php\n ---&gt; Running in 05bb1b73251e\n ---&gt; 3376002c07d3\nSuccessfully built 3376002c07d3\nSuccessfully tagged erstes-dockerfile_mein-webserver:latest\n[+] Running 1\/1\n \u283f Container erstes-dockerfile-mein-webserver-1  Started                                                                                                                                1.2s<\/code><\/pre>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\" data-line=\"\">$ docker-compose up -d --build\nSending build context to Docker daemon     335B\nStep 1\/3 : FROM php:8-apache-buster\n ---&gt; e2070fb1b17d\nStep 2\/3 : RUN echo Hallo Docker!\n ---&gt; Running in d51571677206\nHallo Docker!\n ---&gt; 356e1b361b67\nStep 3\/3 : RUN echo &#039;&lt;?php echo &quot;Hallo vom Dockerfile&quot;;&#039; &gt; \/var\/www\/hallo.php\n ---&gt; Running in 05bb1b73251e\n ---&gt; 3376002c07d3\nSuccessfully built 3376002c07d3\nSuccessfully tagged erstes-dockerfile_mein-webserver:latest\n[+] Running 1\/1\n \u283f Container erstes-dockerfile-mein-webserver-1  Started                                                                                                                                1.2s<\/code><\/pre>\n<p>Hier sieht man, dass der erste echo-Befehl ausgef\u00fchrt wird &#8211; aber eben mangels Schreiboperation keine Auswirkung auf das Image hat. Der Zweite schreibt dagegen in eine PHP-Datei. Nach dem Start des Webservers k\u00f6nnen wir diese \u00fcber den Hostname oder die IP-Adresse des Pi aufrufen, wo das Skript ausgef\u00fchrt und der Text ausgegeben wird:<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-3.png\"><img loading=\"lazy\" decoding=\"async\" width=\"454\" height=\"215\" src=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-3.png\" alt=\"\" class=\"wp-image-7900\" srcset=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-3.png 454w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-3-300x142.png 300w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-3-70x33.png 70w\" sizes=\"auto, (max-width: 454px) 100vw, 454px\" \/><\/a><\/figure>\n<\/div>\n<h2 class=\"wp-block-heading\">Realer Anwendungsfall: Installation von PHP-Modulen<\/h2>\n<p>Kommen wir zum anfangs erw\u00e4hnten realistischeren Anwendungsfall: Wir m\u00f6chten ein PHP-Modul installieren. Zun\u00e4chst f\u00fcgen wir im Dockerfile einen weiteren RUN-Befehl ein, um die PHP-Info auszugeben. Sie zeigt, welche Module installiert sind.<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\" data-line=\"\">RUN echo &#039;&lt;?php phpinfo();&#039; &gt; \/var\/www\/html\/info.php<\/code><\/pre>\n<p>Anschlie\u00dfend das Image neu bauen und den Container ebenfalls neu erstellen. Damit ein neu erstelltes Image Anwendung findet, m\u00fcssen alle Container, die es verwenden neu erstellt werden. Docker-Compose k\u00fcmmert sich bei allen Containern der aktuellen docker-compose.yml Datei automatisch darum:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">docker-compose up -d --build<\/code><\/pre>\n<p>Durch den Aufruf unseres erstellten Skriptes <strong>info.php<\/strong> k\u00f6nnen wir sehen, dass die MySQLi-Erweiterung nicht installiert ist. Auf der gesamten Seite gibt es nur einen Treffer in den Credits:<\/p>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-4.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"721\" src=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-4-1024x721.png\" alt=\"\" class=\"wp-image-7901\" srcset=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-4-1024x721.png 1024w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-4-300x211.png 300w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-4-768x540.png 768w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-4-70x49.png 70w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-4.png 1053w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<p>W\u00e4re die Erweiterung installiert, m\u00fcsste ein eigener Bereich mit ihrer Konfiguration weiter oben auftauchen. PHP bietet uns in den offiziellen Docker-Images zwei Werkzeuge f\u00fcr Erweiterungen: <strong>docker-php-ext-install <\/strong>zur Installation von Erweiterungen und <strong>docker-php-ext-configure<\/strong> f\u00fcr zus\u00e4tzliche Konfigurationsparameter. Letzteres ist nicht bei allen Erweiterungen n\u00f6tig.<\/p>\n<p>Um die MySQLi-Erweiterung zu installieren, ben\u00f6tigen wir einen RUN-Befehl, der docker-php-ext-install aufruft und als Parameter den Name der Erweiterung \u00fcbergibt:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\" data-line=\"\">RUN docker-php-ext-install mysqli<\/code><\/pre>\n<p>Wie schon zuvor gezeigt, m\u00fcssen wir nun <strong>docker-compose up -d &#8211;build<\/strong> aufrufen, um das neue Image zu erstellen. Da die Erweiterung kompiliert werden muss, kann dies &#8211; vor allem auf dem Raspberry Pi &#8211; ein paar Minuten dauern. Wird anschlie\u00dfend die PHPInfo im Browser aktualisiert, sehen wir einen eigenen Bereich zu MySQLi:<\/p>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-5.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"721\" src=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-5-1024x721.png\" alt=\"\" class=\"wp-image-7906\" srcset=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-5-1024x721.png 1024w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-5-300x211.png 300w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-5-768x540.png 768w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-5-70x49.png 70w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/11\/grafik-5.png 1053w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<p>Die Erweiterung wurde also erfolgreich installiert, sie kann nun im PHP-Code genutzt werden, um auf eine MySQL-Datenbank zuzugreifen. <\/p>\n<p>Auf \u00e4hnlichem Wege kann man auch ein eigenes Image erstellen, um etwa eine Anwendung zu dockerisieren. Dies schauen wir uns in einem eigenen Teil an.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In den bisherigen Beitr\u00e4gen haben wir ausschlie\u00dflich fertige Images aus dem Docker-Hub genutzt, beispielsweise f\u00fcr die Webserver Nginx und Apache. F\u00fcr einfachere Anwendungsf\u00e4lle reicht das. Wer sich tiefergehend mit Docker besch\u00e4ftigt, wird jedoch irgendwann an die Grenzen sto\u00dfen und m\u00f6chte ein Image anpassen oder gar ein komplett eigenes erstellen. Dies m\u00f6chten wir im heutigen Beitrag &#8230;<\/p>\n","protected":false},"author":5,"featured_media":7914,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[671],"tags":[497,771,983],"class_list":["post-7897","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-raspberry-pi","tag-docker","tag-docker-compose","tag-dockerfile"],"_links":{"self":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/7897","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/comments?post=7897"}],"version-history":[{"count":11,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/7897\/revisions"}],"predecessor-version":[{"id":9813,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/7897\/revisions\/9813"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media\/7914"}],"wp:attachment":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media?parent=7897"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/categories?post=7897"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/tags?post=7897"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}