{"id":7398,"date":"2021-08-28T11:36:18","date_gmt":"2021-08-28T09:36:18","guid":{"rendered":"https:\/\/u-labs.de\/portal\/?p=7398"},"modified":"2022-12-12T18:31:10","modified_gmt":"2022-12-12T16:31:10","slug":"raspberry-pi-einstieg-in-docker-container-fuer-anfaenger-theorie-praxis","status":"publish","type":"post","link":"https:\/\/u-labs.de\/portal\/raspberry-pi-einstieg-in-docker-container-fuer-anfaenger-theorie-praxis\/","title":{"rendered":"Raspberry Pi: Einstieg in Docker-Container f\u00fcr Anf\u00e4nger (Theorie + Praxis)"},"content":{"rendered":"<p>Du hast noch nie mit Container-Technologie wie Docker gearbeitet? Dann ist dieser Artikel genau richtig f\u00fcr dich: Wir erkl\u00e4ren dir die Vorteile, die auch auf Einplatinencomputer wie dem Raspberry Pi gegen\u00fcber einer klassischen Installation zu tragen kommen. Nach der Theorie geht es in die Praxis: Du lernst, wie du praktisch das richtige Image findest, um damit eine Anwendung als Docker-Container zu starten.<\/p>\n<h2 class=\"wp-block-heading\">Was sind Docker-Container?<\/h2>\n<p>Klassischerweise installiert man alle Programme und deren Abh\u00e4ngigkeiten wie z.B. Bibliotheken direkt auf dem Raspberry Pi. Haupts\u00e4chlich in Form von APT-Paketen \u00fcber die Paketverwaltung des Betriebssystems. Aber auch mit eigenen Paketmanagern verschiedener Programmiersprachen, wie z.B. Python PIP. So lange man nur eine Anwendung bzw. ein Projekt betreibt und dies nicht \u00fcberm\u00e4\u00dfig gro\u00df ist, funktioniert dies zun\u00e4chst meist auch. Sp\u00e4testens wenn der Pi mehrere Anwendungszwecke abdecken soll, wird es komplexer: Die Abh\u00e4ngigkeiten wachsen und damit auch die Gefahr, dass diese sich in die Quere kommen. Vor allem wenn man etwas neues ausprobiert ist dies \u00e4rgerlich &#8211; m\u00f6glicherweise <em>zerschie\u00dft<\/em> man sich damit andere, bislang stabil laufende Programme.<\/p>\n<p>Hier kommen Container-Techniken wie z.B. Docker ins Spiel: Sie isolieren einen Prozess vom Host-System. Der Prozess erh\u00e4lt sein eigenes Dateisystem und eine eigenen Bibliotheken\/Abh\u00e4ngigkeiten &#8211; ohne dabei den Host selbst oder andere Container zu beeinflussen. Dadurch k\u00f6nnen problemlos mehrere Versionen von Abh\u00e4ngigkeiten parallel genutzt werden, ohne sich in die Quere zu kommen.<\/p>\n<h2 class=\"wp-block-heading\">Welche Vorteile bieten Containertechnologien wie Docker auf dem Raspberry Pi?<\/h2>\n<p>Im folgenden betrachten wir die Vorteile aus Sicht eines Raspberry Pi. Diese unterscheiden sich in so fern von x86 Servern und Workstations, dass z.B. Docker und andere Containertechnologien oft mit virtuellen Maschinen verglichen werden. Auf dem Raspberry Pi dagegen lassen sich VMs aufgrund der geringen Leistung kaum sinnvoll einsetzen. Hier installiert man seine Anwendungen direkt auf dem Pi, ggf. mit mehreren Pis. Auch ein paar andere Vorteile sind auf dem Pi nicht oder zumindest weniger relevant.<\/p>\n<h3 class=\"wp-block-heading\">Reproduzierbarkeit &amp; Flexibilit\u00e4t<\/h3>\n<p>Zwei klassische, per Hand konfigurierte Systeme sind selten identisch. Schon alleine weil das eine System vielleicht \u00f6fter geupgraded wurde als das andere, unterscheidet es sich minimal &#8211; m\u00f6glicherweise treten dadurch eines Tages Probleme auf, die nicht reproduzierbar sind. In einem Docker-Image f\u00fcgen wir aber nur die von uns explizit definierten Programme und Abh\u00e4ngigkeiten hinzu &#8211; es ist also klar definiert und reproduzierbar. Jeder Container, der auf Basis unseres Images erzeugt wird, verh\u00e4lt sich gleich. Wir k\u00f6nnen unsere Software also problemlos auf einem anderen Raspberry Pi \u00fcbertragen, sie wird funktionieren und sich gleich verhalten. Au\u00dferdem l\u00e4sst sich ein bestimmter Stand sehr einfach wiederherstellen.<\/p>\n<p>Sogar \u00fcber verschiedene Distributionen und teils Betriebssysteme hinweg. Wobei letzteres eher f\u00fcr den x86 Bereich relevant ist, beispielsweise wenn man unter Linux seine Docker-Container erstellt und jemand anders unter MacOS. Die Reproduzierbarkeit ist dagegen auch auf dem Pi n\u00fctzlich.<\/p>\n<h3 class=\"wp-block-heading\">Dokumentation s\u00e4mtlicher Abh\u00e4ngigkeiten (+ leichte Versionierbarkeit)<\/h3>\n<p>Dadurch haben wir automatisch alles dokumentiert, was wir f\u00fcr die Anwendung sowie deren Konfiguration brauchen. Zumindest im Vergleich zur h\u00e4ndischen Variante. Denn ohne Docker k\u00f6nnten wir nat\u00fcrlich auch Alternativen wie Ansible oder Shell-Skripte nutzen, um dies zu tun. Erfahrungsgem\u00e4\u00df  geschieht dies in der Praxis aber oft nicht, bei Docker machen wir das dagegen automatisch, quasi nebenbei &#8211; und haben zus\u00e4tzliche Vorteile, da weder Ansible noch Shell-Skripte unsere Anwendungen ohne weiteres isolieren.<\/p>\n<p>Legen wir unsere Konfigurationsdateien in eine Versionverwaltung wie Git, sind \u00c4nderungen nachvollziehbar. Sollte es zu Problemen kommen, schauen wir uns den Verlauf an, stellen eine \u00e4ltere Version her. Nach dem Laden des Images bzw. vorherigem bauen entspricht die Umgebung dem jeweiligen \u00e4lteren Stand. Lediglich um externe Programme m\u00fcssen wir uns von Hand k\u00fcmmern, etwa das Schema einer Datenbank, die im Zuge eines Anwendungsupdates ver\u00e4ndert wurde.<\/p>\n<h3 class=\"wp-block-heading\">Aktuelle Software, ohne ein instabiles System zu riskieren<\/h3>\n<p>Klassische Distributionen wie Debian und damit Raspbian aktualisieren die per APT bereitgestellten Pakete oft nur mit Verz\u00f6gerung. Manche Pakete hinken den aktuellen stabilen Versionen der jeweiligen Programme sogar Jahre hinterher. H\u00e4ndisch neuere Pakete zu installieren bringt neue Probleme: M\u00f6glicherweise ist dies nicht mit den globalen Bibliotheken kompatibel, wodurch das System instabil werden kann. Installiert man h\u00e4ndisch ein Paket an der Paketverwaltung vorbei, wird es zudem nicht automatisch aktualisiert &#8211; es drohen auf Dauer Sicherheits- und Kompatibilit\u00e4tsprobleme.<\/p>\n<p>Durch die Isolation erlaubt es uns Docker, einzelne Programme in deutlich aktuelleren Versionen zu installieren. Etwa ein neues PHP-Update. Dies bringt uns neue Funktionen, behebt bekannte Bugs und liefert teils auch sicherheitskritische Updates schneller aus.<\/p>\n<p>Ein konkretes Beispiel ist der <strong>Webserver nginx<\/strong>: Die beim Verfassen des Artikels aktuellste stabile Version ist 1.20. In den Raspberry PI OS Paketquellen erhalten wir  dagegen 1.14.2 aus dem Dezember 2018 &#8211; also etwa 2 3\/4 Jahre alt.<\/p>\n<h3 class=\"wp-block-heading\">Leichte Isolation zum Betrieb mehrerer Programme &amp; Gefahrlose Tests<\/h3>\n<p>Auch Experimente sind dadurch leichter und ungef\u00e4hrlicher: Wir bauen ein Image (falls notwendig), erzeugen einen neuen Container und k\u00f6nnen leicht sowie gefahrlos etwas ausprobieren. Beispiel: PHP-Update von 7 auf 8. Den Tag auf 8 \u00e4ndern, das Image bauen und testen. Dies kann gefahrlos in einer zweiten Instanz (neues Image + neuer Container) ausprobiert werden, ohne unser Hauptsystem oder andere Komponenten zu gef\u00e4hrden. Durch den problemlosen Parallelbetrieb lassen sich auch direkte Vergleiche anstellen. Beispielsweise k\u00f6nnen wir das Hauptsystem einer Anwendung vorerst auf PHP 7 laufen lassen, parallel dazu aber eine Testinstanz mit PHP8 starten &#8211; und etwa die Geschwindigkeit beider Installationen messen.<\/p>\n<p>Werden mehrere Anwendungen betrieben, m\u00f6chte man diese m\u00f6glichst voneinander isolieren &#8211; so hat z.B. eine Sicherheitsl\u00fccke darin nicht die Kompromittierung des gesamten Pis oder gar des Netzwerkes zur Folge. Klassisch ist dies zwar auch mit verschiedenen Techniken wie z.B. einem chroot-Jail m\u00f6glich, jedoch komplexer und aufw\u00e4ndiger als mit Containern &#8211; die ja schon von Grund auf daf\u00fcr konzipiert wurden, sich vor anderen Containern und dem Hostsystem zu isolieren. F\u00fcr maximale Sicherheit sollte man jedoch auch hier Best Practices beachten, wie z.B. m\u00f6glichst keine Prozesse dauerhaft als root laufen zu lassen.<\/p>\n<h2 class=\"wp-block-heading\">Wie funktionieren Docker-Container?<\/h2>\n<p>Als Grundlage dient immer ein sogenanntes <strong>Image<\/strong>. Es besteht aus einem virtuellen Dateisystem, das aus mehreren Schichten aufgebaut wird. Man kann es sich ein wenig wie die Ebenen in Photoshop\/Gimp vorstellen: Auf jeder Ebene befinden sich verschiedene Elemente. \u00dcbereinandergelegt ergeben sie ein komplettes Bild mit dem gew\u00fcnschten Inhalt. Es gibt zwei Arten von Images.<\/p>\n<h3 class=\"wp-block-heading\">Fertige Images (z.B. aus dem Docker-Hub)<\/h3>\n<p>F\u00fcr viele Programme und Anwendungsf\u00e4lle hat bereits jemand (im besten Falle der urspr\u00fcngliche Author\/Entwickler) ein Image erstellt. Dies ist der schnellste und einfachste Weg, ich w\u00fcrde ihn daher bevorzugen, sofern m\u00f6glich.<\/p>\n<h3 class=\"wp-block-heading\">Selbst erstellte Images<\/h3>\n<p>Wir k\u00f6nnen entweder ein fertiges Image erweitern oder ein komplett eigenes Erstellen. Ersteres ist oft einfacher, schneller und nachhaltiger. Dies ist eine der gr\u00f6\u00dften St\u00e4rken von Docker: Ben\u00f6tigen wir beispielsweise PHP mit bestimmten Erweiterungen, m\u00fcssen wir nicht von 0 auf ein Image mit der kompletten Installation\/Konfiguration von PHP erstellen &#8211; sondern erweitern das fertige PHP-Image mit dem, was fehlt bzw. ge\u00e4ndert werden soll. Vergleichbar mit Vererbung in der objektorientierten Programmierung: Statt eine Klasse zu kopieren und zu erweitern, erzeugen wir eine neue Klasse, die von der Basisklasse erbt und damit all deren Funktionen besitzt.<\/p>\n<p>Was davon zum Einsatz kommt, h\u00e4ngt stark vom Anwendungszweck ab. Um Anwendungen von anderen zu Hosten, z.B. Nextcloud oder WordPress), kann man h\u00e4ufig fertige Images verwenden &#8211; zumindest wenn keine gr\u00f6\u00dferen Anpassungen gew\u00fcnscht sind. Das selbst Erstellen wird sp\u00e4testens dann notwendig, wenn eigene Anwendungen (z.B. Python-Skripte) dockerisiert werden sollen. Wobei man auch hier im Regelfall nicht von 0 auf anfangen muss, sondern im reichhaltigen Angebot des Docker-Hubs ein Image als Grundlage findet.<\/p>\n<p><strong>Ein wichtiger Unterschied zwischen fertigen und selbst erstellten Images<\/strong>: Fertige Images m\u00fcssen lediglich heruntergeladen werden, anschlie\u00dfend sind sie sofort einsatzbereit. Ein selbst erstelltes Image muss dagegen einmalig (bis zu entsprechenden \u00c4nderungen) gebaut werden. Dies kostet je nach Inhalt und Leistung\/Infrastruktur etwas Zeit, vor allem auf dem Raspberry Pi.<\/p>\n<h2 class=\"wp-block-heading\">Wie nutze ich Docker-Container auf dem Raspberry Pi?<\/h2>\n<p>Du ben\u00f6tigst einen Raspberry Pi, am besten ein aktueller Pi 4. \u00c4ltere Modelle sind auch m\u00f6glich, aber langsamer und aufgrund der geringeren Ressourcen (haupts\u00e4chlich des Arbeitsspeichers) nur eingeschr\u00e4nkt nutzbar &#8211; wenngleich es prinzipiell auch dort funktioniert. Wie du Docker auf dem Pi installierst, haben wir in folgendem Beitrag bereits erkl\u00e4rt: <a href=\"https:\/\/u-labs.de\/portal\/docker-auf-dem-raspberry-pi-installieren-erste-container-starten-einfach-erklaert\/\" title=\"Docker auf dem Raspberry Pi installieren &amp; erste Container starten einfach erkl\u00e4rt\">Docker einfach auf dem Raspberry Pi installieren (Anleitung f\u00fcr Einsteiger)<\/a>.<\/p>\n<p>Wenn docker ps eine leere Tabelle wie im folgenden Beispiel anzeigt, ist dein Raspberry Pi richtig eingerichtet:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">pi@ul-pi:~ $ docker ps\nCONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES\n<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Beispielszenario: Starten eines Webservers (Nginx)<\/h3>\n<p>Als einfaches Beispiel f\u00fcr den Start m\u00f6chten wir einen einfachen Webserver mit Docker starten. Ich entscheide mich f\u00fcr Nginx, da er aufgrund seiner Leichtgewichtigkeit f\u00fcr den Raspberry Pi gut geeignet ist. Prinzipiell k\u00f6nnten wir aber auch andere Alternativen wie Apache nehmen, dazu an anderer Stelle mehr. <\/p>\n<h4 class=\"wp-block-heading\">Finden des passenden Basis-Images<\/h4>\n<p>Zuerst <a href=\"https:\/\/hub.docker.com\/search?q=nginx&amp;type=image&amp;architecture=arm\" target=\"_blank\" rel=\"nofollow\">suchen wir im Docker-Hub nach einem passenden Image<\/a>. Wichtig ist hierbei, links den Filter bei <strong>Architectures<\/strong> auf <strong>ARM<\/strong> zu setzen. Der Raspberry Pi nutzt n\u00e4mlich eine andere Prozessorarchitektur, als die von den meisten Computern, Laptops und Servern gewohnte x86. Da diese nicht untereinander kompatibel sind, ben\u00f6tigen wir ARM-Kompatible Images. Ohne den Filter werden uns auch x86 Images angezeigt, die mit dem Pi nicht funktionieren.<\/p>\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-41.png\"><img loading=\"lazy\" decoding=\"async\" width=\"152\" height=\"253\" src=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-41.png\" alt=\"\" class=\"wp-image-7399\" srcset=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-41.png 152w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-41-42x70.png 42w\" sizes=\"auto, (max-width: 152px) 100vw, 152px\" \/><\/a><\/figure>\n<p>Die Architektur k\u00f6nnen wir auf der Konsole mit dem Befehl <strong>uname<\/strong> pr\u00fcfen, der Schalter -m (f\u00fcr <strong>M<\/strong>achine) gibt uns die Prozessorarchitektur aus:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">$ uname -m\narmv7l<\/code><\/pre>\n<p>Auf dem Pi wird dies in den meisten F\u00e4llen <strong>armv7l<\/strong> ausgeben, das entspricht der 32-Bit Architektur von ARM. Wie bei x86 gibt es auch hier 32 und 64 Bit. Im Gegensatz dazu ist 32 Bit bei den Pis durch ihren bislang geringen Hauptspeicher noch sehr verbreitet. Das Raspberry Pi OS in 64 Bit ist noch recht jung und macht nur auf den ebenfalls neuen Raspberry Pi 4 mit 8 GB Arbeitsspeicher wirklich Sinn. Dort erhalten wir <strong>aarch64 <\/strong>statt <strong>armv7l<\/strong>. Unser Pi hat nur 4 GB RAM (Limit f\u00fcr 32 Bit Plattformen) und l\u00e4uft daher auf einem 32 Bit Raspberry Pi OS, weswegen wir in der Suche nur <strong>ARM<\/strong> als Filter ankreuzen.<\/p>\n<p>Der erste Treffer ist ein <strong>offizielles Image<\/strong>. Es wurde also vom Entwickler\/Eigent\u00fcmer der jeweiligen Software (hier Nginx) selbst angefertigt und ist damit die vertrauensw\u00fcrdigste Quelle. Unter der Beschreibung sehen wir mit der schlagwort\u00e4hnlichen Ansicht, welche Architekturen unterst\u00fctzt werden. Dieses Image kann also unter einem 64 Bit x86 Computer genau so genutzt werden wie auf dem Raspberry Pi mit ARM 32\/64 Bit.<\/p>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-42.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"369\" src=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-42-1024x369.png\" alt=\"\" class=\"wp-image-7400\" srcset=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-42-1024x369.png 1024w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-42-300x108.png 300w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-42-768x277.png 768w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-42-70x25.png 70w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-42.png 1283w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<p>Weiter unten werden sich noch viele weitere Images finden. Das sind <strong>Community-Images<\/strong> &#8211; jeder kann Images erstellen und diese in den Docker-Hub hochladen. Grunds\u00e4tzlich eine gute Sache. Allerdings sollte man &#8211; wie generell bei fremder Software &#8211; dem Ersteller Vertrauen. Vorsicht ist auch bei \u00e4lteren Images geboten: Wenn diese seit l\u00e4ngerem keine Aktualisierungen erhalten haben, gibt es wahrscheinlich offene Sicherheitsl\u00fccken &#8211; eventuell auch Probleme mit der Software selbst, die in aktuelleren Versionen korrigiert wurden.<\/p>\n<h4 class=\"wp-block-heading\">Brauche ich einen Tag?<\/h4>\n<p>Tags kann man am ehesten mit den Versionen eines Programmes vergleichen. Im Regelfall findest du in der Beschreibung eine Liste aller verf\u00fcgbaren Tags. Diese ist am \u00fcbersichtlichsten, sofern vorhanden. Alternativ kannst du auch oben auf den Reiter <strong>Tags<\/strong> klicken.<\/p>\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-43.png\"><img loading=\"lazy\" decoding=\"async\" width=\"761\" height=\"707\" src=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-43.png\" alt=\"\" class=\"wp-image-7401\" srcset=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-43.png 761w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-43-300x279.png 300w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-43-70x65.png 70w\" sizes=\"auto, (max-width: 761px) 100vw, 761px\" \/><\/a><\/figure>\n<p>Oft haben wir mit den Tags jedoch nicht nur die Wahl zwischen der bestimmten Version eines Programms, sondern noch mehr: <\/p>\n<ul class=\"wp-block-list\">\n<li><strong>Gr\u00f6bere Versionstags<\/strong> wie <strong>1<\/strong> oder <strong>1.20<\/strong> machen es uns einfacher, die aktuellsten Updates einzuspielen &#8211; ohne unerwartetes Verhalten zu erzeugen. Mit dem Tag <strong>1.20<\/strong> erhalten wir die derzeit aktuellste 1.20 Version. H\u00e4lt sich der Entwickler an die semantische Versionierung, ist 1 oder 1.20 eine gute Wahl. Mit 1 erhalten wir nur abw\u00e4rtskompatible \u00c4nderungen (z.B. neue Funktionalit\u00e4ten, die vorhandene nicht beeinflussen) und mit 1.20 lediglich Fehlerkorrekturen, die ebenfalls abw\u00e4rtskompatibel sind (ohne neue Funktionen). Allerdings erhalten wir diese Updates dadurch nicht automatisch, dies ist ein anderes Thema, das wir in einem getrennten Artikel behandeln.<\/li>\n<li><strong>Verschiedene Basis-Images<\/strong>: Wie zuvor bereits erkl\u00e4rt, k\u00f6nnen Images aufeinander aufbauen. Nginx bietet uns hier verschiedene Basis-Images an: Standardm\u00e4\u00dfig ein minimales Debian, aber auch beispielsweise das deutlich schlankere Alpine. <\/li>\n<li><strong>latest<\/strong>: Ein oft vorhandener Standard-Tag, der greift, wenn wir keinen spezifischen Tag angeben. Damit bekommen wir <strong>IMMER <\/strong>die aktuellste Version. Davon w\u00fcrde ich definitiv abraten, weil er die Reproduzierbarkeit deutlich einschr\u00e4nkt bzw. zerst\u00f6rt.<\/li>\n<\/ul>\n<p>F\u00fcr den Anfang brauchst du dir dar\u00fcber keine all zu gro\u00dfen Gedanken machen und solltest lediglich den <strong>latest<\/strong> Tag meiden. Wir nutzen zur Vereinfachung die derzeit aktuellste, exakte Version 1.21 &#8211; damit ist der Vergleich zu einer h\u00e4ndischen Installation am klarsten.<\/p>\n<h4 class=\"wp-block-heading\">Starten eines Containers<\/h4>\n<p>Damit haben wir unser Image <strong>nginx:1.21<\/strong> gefunden. Dies besteht im Folgenden immer aus einem Namen (<strong>nginx<\/strong>) und Tag (<strong>1.21<\/strong>). Mithilfe eines Images kann ein Container gestartet werden. Man kann sich Images \u00e4hnlich wie eine Schablone vorstellen. Es lassen sich also mehrere Container aus einem Image erstellen. Daf\u00fcr kommt im einfachsten Falle der Befehl <strong>docker run<\/strong> zum Einsatz:<\/p>\n<p>So einfach l\u00e4sst sich ein Container starten. Ich empfehle, zus\u00e4tzlich immer einen frei w\u00e4hlbaren Name anzugeben. Ansonsten generiert Docker zuf\u00e4llige Namen. Das erschwert die Zuordnung sp\u00e4testens bei mehreren Containern. <\/p>\n<p>Bei einem Webserver ben\u00f6tigen wir zudem mindestens eine Portweiterleitung. Ansonsten kann man den Webserver nicht erreichen, da Docker nicht nur das Dateisystem, sondern auch das Netzwerk des Containers isoliert. Dies geschieht mit dem Schalter -p 80:80. Dabei leiten wir Port 80 des Hosts (erste Angabe) auf Port 80 innerhalb des Containers.<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">docker run --name webserver -p 80:80 nginx:1.21<\/code><\/pre>\n<p>Der erste Start dauert l\u00e4nger, da zun\u00e4chst das Image aus dem Docker-Hub geladen werden muss. Dies ist jedoch nur einmalig notwendig. Nach dem Start k\u00f6nnen wir den Webserver testen, in dem wir <strong>http:\/\/<\/strong> gefolgt vom DNS-Hostname des Pi oder seine IP-Adresse im Browser eingeben. Am einfachsten ist der Hostname, der im Regelfall vom Router aufgel\u00f6st wird &#8211; also der Name nach <strong>pi@<\/strong>, hier etwa <strong>testpi<\/strong>. Dort sollte nun die Standard-Willkommensseite des Nginx erscheinen:<\/p>\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-44.png\"><img loading=\"lazy\" decoding=\"async\" width=\"616\" height=\"382\" src=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-44.png\" alt=\"\" class=\"wp-image-7402\" srcset=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-44.png 616w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-44-300x186.png 300w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-44-70x43.png 70w\" sizes=\"auto, (max-width: 616px) 100vw, 616px\" \/><\/a><\/figure>\n<h4 class=\"wp-block-heading\">Ordner vom Host durch Volumes mit dem Container teilen<\/h4>\n<p>Wie kann ich nun eigene Seiten \u00fcber den Nginx Webserver ausliefern, z.B. eine HTML Seite? Man k\u00f6nnte sich mit <strong>docker exec<\/strong> in den Container schalten und diese dort erstellen. Davon ist jedoch abzuraten: Container sind kurzlebig, sobald der Container neu erstellt wurde w\u00e4ren diese Daten verloren. Eine andere M\u00f6glichkeit w\u00e4re, ein eigenes Docker-Image zu auf Basis des Nginx zu erstellen. Dies ist ein wenig aufw\u00e4ndiger und wird daher Teil eines eigenen Artikels. An dieser Stelle schauen wir uns die einfachste und flexibelste L\u00f6sung f\u00fcr dieses Problem an: <strong>Volumes<\/strong>.<\/p>\n<p>Ein <strong>Volume <\/strong>speichert persistente Daten, also jene die wir dauerhaft ben\u00f6tigen. Sie liegen au\u00dferhalb des Containers, sodass ein Volume auch dann bestehen bleibt, wenn wir den Container neu starten oder l\u00f6schen. Es gibt verwaltete (Managed) Volumes und sogenannte <strong>Bind Mounts<\/strong>. Letztere sind am einfachsten und f\u00fcr unser einfaches Beispielszenario am besten geeignet: Damit stellen wir einen Ordnerpfad auf dem Host ganz einfach dem Container zur Verf\u00fcgung &#8211; er wird im Container an der gew\u00fcnschten Stelle gemountet. Hierzu gibt es den <strong>-v Schalter:<\/strong><\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">-v Hostpfad:Containerpfad<\/code><\/pre>\n<p>F\u00fcr unseren Webserver erstellen wir einen Ordner, beispielsweise <strong>\/home\/pi\/www<\/strong> und legen darin eine HTML-Seite namens <strong>index.html<\/strong> ab:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">mkdir \/home\/pi\/www\necho &#039;&lt;h1&gt;Eigene HTML-Seite auf dem Pi per Docker bereitgestellt&lt;\/h1&gt;&#039; &gt; \/home\/pi\/www\/index.html<\/code><\/pre>\n<p>Damit dieser Ordner \u00fcber ein Bind Mount Volume dem Container zur Verf\u00fcgung steht, m\u00fcssen wir ihn neu erstellen. Zuvor den alten Container (der eben erstellt wurde) mit <strong>docker rm &lt;Name&gt;<\/strong> f\u00fcr <strong>r<\/strong>e<strong>m<\/strong>ove l\u00f6schen:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">docker rm webserver<\/code><\/pre>\n<p>Und das Volume im <strong>docker run<\/strong> Befehl angeben. Das Ziel lautet <strong>\/usr\/share\/nginx\/html<\/strong>, da das Nginx-Image standardm\u00e4\u00dfig diesen Ordner als Webroot nutzt. Mit folgendem Befehl stellen wir den lokalen Ordner <strong>\/home\/pi\/www<\/strong> im Container unter  <strong>\/usr\/share\/nginx\/html<\/strong> zur Verf\u00fcgung:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">docker run --name webserver -p 80:80 -v \/home\/pi\/www:\/usr\/share\/nginx\/html nginx:1.21<\/code><\/pre>\n<p>Aktualisieren wir den Aufruf unseres Pi im Browser, erscheint statt der Nginx Standard-Seite nun der zuvor in die index.html geschriebene Text:<\/p>\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-46.png\"><img loading=\"lazy\" decoding=\"async\" width=\"616\" height=\"218\" src=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-46.png\" alt=\"\" class=\"wp-image-7404\" srcset=\"https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-46.png 616w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-46-300x106.png 300w, https:\/\/u-labs.de\/portal\/wp-content\/uploads\/2021\/08\/grafik-46-70x25.png 70w\" sizes=\"auto, (max-width: 616px) 100vw, 616px\" \/><\/a><\/figure>\n<h4 class=\"wp-block-heading\">Container dauerhaft im Hintergrund laufen lassen<\/h4>\n<p>M\u00f6chten wir nun weitere Seiten hinzuf\u00fcgen (etwa eine <strong>test.html<\/strong> Seite), wird schnell ein Problem deutlich: Nach dem schlie\u00dfen mit <strong>STRG + C<\/strong> ist die Seite nicht mehr erreichbar &#8211; der Container wurde gestoppt. Dies liegt daran, dass der Container im Vordergrund gestartet wurde. Wie jedes andere Programm auch wird dies beendet, sobald wir entweder das Programm mit STRG + C beenden oder aber die SSH-Sitzung schlie\u00dfen. Klassischerweise w\u00fcrde man dies unter Linux mit einem Dienst (heutzutage haupts\u00e4chlich Systemd) l\u00f6sen, der im Hintergrund l\u00e4uft.<\/p>\n<p>Unter Docker ist dies nicht notwendig: Hierf\u00fcr gibt es den sogenannten <strong>detached<\/strong> Modus. Dies startet den Container im Hintergrund, \u00e4hnlich wie einen klassischen Dienst. Daf\u00fcr ist lediglich der Parameter <strong>-d <\/strong>notwendig:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">docker rm webserver\ndocker run -d --name webserver -p 80:80 -v \/home\/pi\/www:\/usr\/share\/nginx\/html nginx:1.21<\/code><\/pre>\n<p>Der Befehl gibt uns die Container-ID zur\u00fcck. Eine \u00dcbersicht aller laufenden Container liefert <strong>docker ps<\/strong>. Die Spalte Container ID ist eine weniger sperrige Version der 64-Stelligen ID. Einfacher ist jedoch die Verwendung des Namens &#8211; ein Grund, warum ich oben empfohlen habe, stets sinnvolle und selbst festgelegte Namen zu vergeben.<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">$ docker ps\nCONTAINER ID   IMAGE        COMMAND                  CREATED         STATUS         PORTS                               NAMES\n1ab64f6afdf3   nginx:1.21   &quot;\/docker-entrypoint.\u2026&quot;   4 seconds ago   Up 2 seconds   0.0.0.0:80-&gt;80\/tcp, :::80-&gt;80\/tcp   webserver\n<\/code><\/pre>\n<h4 class=\"wp-block-heading\">Konsolenausgabe (Logs, Fehler etc) des Containers einsehen<\/h4>\n<p>Durch den Detached-Modus sehen wir die Ausgabe des Containers nicht mehr &#8211; Im falle von Nginx die Zugriffs- und Fehlerprotokolle, die gerade beim Testen sinnvoll sein k\u00f6nnen. Anhand des Containernamens k\u00f6nnen wir die Logs mit <\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">docker logs webserver -f<\/code><\/pre>\n<p>&#8211;<strong>f<\/strong> <strong>f<\/strong>olgt der Ausgabe, d.H. man sieht alle neuen Eintr\u00e4ge live auf der Konsole &#8211; so wie bei <strong>docker run<\/strong> ohne <strong>-d<\/strong> Parameter. M\u00f6chte man nur die bisher vorhandenen Eintr\u00e4ge ausgeben lassen, gen\u00fcgt <strong>docker logs webserver<\/strong> ohne <strong>-f<\/strong>. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Du hast noch nie mit Container-Technologie wie Docker gearbeitet? Dann ist dieser Artikel genau richtig f\u00fcr dich: Wir erkl\u00e4ren dir die Vorteile, die auch auf Einplatinencomputer wie dem Raspberry Pi gegen\u00fcber einer klassischen Installation zu tragen kommen. Nach der Theorie geht es in die Praxis: Du lernst, wie du praktisch das richtige Image findest, um &#8230;<\/p>\n","protected":false},"author":5,"featured_media":7407,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[671],"tags":[923,912,497],"class_list":["post-7398","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-raspberry-pi","tag-anfaenger-tutorial","tag-container","tag-docker"],"_links":{"self":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/7398","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=7398"}],"version-history":[{"count":4,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/7398\/revisions"}],"predecessor-version":[{"id":9837,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/7398\/revisions\/9837"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media\/7407"}],"wp:attachment":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media?parent=7398"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/categories?post=7398"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/tags?post=7398"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}