Einführung in Docker – von 0 auf 50%
Vorwort
Docker ist eine Container Plattform, mit der du Applikationen in isolierten Umgebungen starten kannst.
Dies hilft dir und deinem Team bei alltäglichen Herausforderungen wie lokalen Entwicklungsumgebung sowie auch bei Deployments.
Was ist Docker technisch gesehen?
Wie funktioniert das mit dem Kernel?
Wie ist der Vergleich zu herkömmlicher Virtualisierung?
Das alles interessiert uns in diesem Artikel nicht 🙂
Nachfolgend wird beschrieben, wie Docker auf einem lokalen System aufgesetzt und wie schnell Erfolge mit Docker erzielt werden können.
Wer tiefere Einblicke in den technischen Unterbau von Docker erhalten möchte, dem empfehle ich diese Docker-Übersicht.
Lerne jetzt, wie du als Docker-Beginner mit Docker arbeitest.
Installation
Docker zu installieren ist ziemlich einfach.
Wie es dir je nach Betriebssystem gelingt, erfährst du im Folgenden:
Ubuntu / Linux
Es ist wichtig, nicht die Docker Engine aus den normalen Sources zu installieren (outdated).
Folge einfach dieser Anleitung: https://docs.docker.com/install/linux/docker-ce/ubuntu/
Mac
Hier findest du die Anleitung für Mac: https://docs.docker.com/docker-for-mac/install/
Kollegen, die einen Mac benutzen, hatten jedoch Probleme mit der Docker-Toolbox und sind daher auf diese Applikation umgestiegen: https://download.docker.com/mac/stable/Docker.dmg
Windows
Wie Docker auf Windows installiert wird, erfährst du hier: https://docs.docker.com/docker-for-windows/install/
Erste Schritte – Dein Erster Container
Nachdem du Docker erfolgreich installiert hast, geht es darum, den ersten Container zu starten. Stelle dir vor, ein „Container“ sei ein „Ding“, das gestartet wird, ähnlich wie ein Programm. Dieses „Ding“ läuft dauerhaft oder beendet sich nach erledigter Aufgabe.
Wenn du der Installations-Anleitung (siehe oben) gefolgt bist, hast du bereits den hello-world Container gestartet. Ansonsten: Starte ihn 🙂
docker run hello-world
Das ist natürlich langweilig!
Deshalb möchten wir nun andere „Dinge“ (Container) starten, die Aufgaben für uns erledigen.
Images sind fertig gebaute Container die aufgrund einer Bauanleitung (einer Art Rezept) angefertigt wurden. Diese Bauanleitung ist das Dockerfile.
Eine Liste von existierenden öffentlichen Images findest du in Docker Hub. Mithilfe der Filterung werden dir nur offizielle Images angezeigt.
Dein erster Anlaufpunkt sollten offizielle Images sein.
Offizielle Images sind Images, die von großen Communities gepflegt werden, gut dokumentiert sind und Sicherheitsrelevant geprüft werden.
Nehmen wir an, du möchtest einen PHP-Befehl ausführen – ohne PHP lokal installiert zu haben.
Führe folgendes Kommando in deinem Terminal aus.
docker run php:cli php -r 'echo date("c");'
Super! Du hast einen Container mit einem PHP Image gestartet und einen Befehl übergeben. Der Befehl wurde ausgeführt und der Container im Anschluss beendet.
Führe das Kommando erneut aus – der zweite Durchlauf ist schneller.
Lasse uns nun das Kommando Stück für Stück aufschlüsseln.
docker
Der Befehl um Docker auszuführen.
run
Das Kommando, das ausgeführt werden soll. Wir wollen aktuell einen Container „rennen“ lassen.
php
Das Image, das mit dem der Container gestartet werden soll. Siehe https://hub.docker.com/_/php.
cli
Der Tag, der gestartet werden soll.
Ein Tag ist eine Art Version / Ausprägung / Unterkategorie des PHP-Images.
Siehe https://hub.docker.com/_/php?tab=tags
php -r 'echo date("c");'
Dies ist ein Parameter, den wir an den Container übergeben.
In unserem Fall führen die Parameter die PHP Executable aus und übergeben wiederum ein Kommando, das ausgeführt werden soll.
Cool, nicht wahr?
Was hat das dir jetzt gebracht?
Du hast einen PHP-Befehl ausgeführt ohne PHP lokal installiert zu haben!
„Aber ich kann PHP/Java/Node/Python/… doch einfach lokal installieren“
Ja – aber das ist nicht immer gewünscht, praktikabel oder einfach.
Lasse uns nun einen PHP-Befehl in verschiedenen PHP-Versionen ausführen.
docker run php:5.5-cli php -r 'echo PHP_VERSION;'
Und jetzt nochmal mit Python
Und mit Nginx …
… um nur einige Beispiele zu zeigen.
Es ist sehr einfach zwischen verschiedenen Versionen zu wechseln!
Docker Layer
Jedes Mal, wenn du eines dieser Kommandos ausführst, werden Docker-Layer heruntergeladen. Lasse dich davon nicht verunsichern. Dies ist lediglich beim ersten Starten von einem Image notwendig, bis die Layer gecached sind.
Mehr zu Docker Layern, was Docker Layer sind und warum du sie benötigst, lernst du im nächsten Abschnitt.
Ein kurzer Ausflug zum Dockerfile
Über die zuvor genannten Layer möchte ich nun einiges erklären.
Ein Dockerfile (du erinnerst dich, Image = fertiges Gebäude, Dockerfile = Bauanleitung) besteht aus einzelnen Befehlen.
Ein Image besteht aus einem oder mehreren Layern – ein Schichtensystem. Ein Layer enthält Daten, ähnlich wie eine Festplatte. Wird eine Datei im Bauprozess geändert, so wird dies in einem neuen Layer festgehalten.
Befehle im Dockerfile, die eine Änderung hervorrufen, generieren alle einen neuen Layer.
Ein Befehl (innerhalb des Dockerfiles) ist z.B. RUN.
Jedes Mal, wenn RUN im Dockerfile verwendet wird, wird ein neuer Layer erzeugt. Du möchtest darauf achten, möglichst wenige Layer zu erzeugen, d.h. du solltest Befehle zusammenfassen. Dies reduziert die Dateigröße des Images und hat viele weitere Vorteile.
Als Gegenspieler erscheint der Layer-Cache. Du solltest darauf achten, dein Dockerfile so aufzubauen, dass du von dem Layer-Cache profitierst.
# mehrere Kommandos zusammengefasst RUN set -x \ && addgroup -g 101 -S nginx \ && adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \ && apkArch="$(cat /etc/apk/arch)" \ && nginxPackages=" \
Aber solche technischen Feinheiten musst du jetzt noch nicht beachten 😉
Hinweis: Daten persistieren
Sobald ein Docker Container beendet wird, verliert der Container alle seine Daten.
Falls du etwas dauerhaft speichern möchtest, dann musst du das außerhalb des Containers tun.
Mehr Container – etwas Sinnvolles bitte
Die zuvor gestarteten Container waren zwar beeindruckend, jedoch nicht gerade nützlich.
Nun möchten wir Container starten, konfigurieren und benutzen.
Für den Anfang einen MySQL-Server.
Schritt 1: Finde das gewünschte Image in Docker Hub
https://hub.docker.com/search?q=mysql&type=image
Schritt 2: Benutze das „Official Image“ https://hub.docker.com/_/mysql
Schritt 3: Lies die Doku für das Image und verwende es.
Ziel: Es soll ein MySQL-Server in Docker laufen. Der Server soll vom Host-Rechner (deinem PC) erreichbar sein. Wenn der Container beendet wird und neu gestartet wird, sollen die Datenbanken noch vorhanden sein. Zugangsdaten für den MySQL Server werden vorgegeben.
docker run --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=test -e MYSQL_USER=user1 -e MYSQL_PASSWORD=password1 -v /var/tmp/test-mysql-data:/var/lib/mysql mysql:8
Das aufgeschlüsselte Kommando:
docker
Du führst Docker aus.
run
Du startest einen Container.
--rm
Entferne den Container wenn er gestoppt wird.
-p 3306:3306
Route den Port von innerhalb des Containers (3306) auf außerhalb des Containers 3306.
-e MYSQL_ROOT_PASSWORD=root
Setze das root Passwort auf „root“.
-e MYSQL_DATABASE=test
Erstelle die Datenbank „test“.
-e MYSQL_USER=user1
Erstelle einen Benutzer mit login „user1„.
-e MYSQL_PASSWORD=password1
Setze das Passwort „password1“ für user1.
-v /var/tmp/test-mysql-data:/var/lib/mysql
Mounte den Ordner „/var/lib/mysql“ in den lokalen Ordner “/var/tmp/test-mysql-data”.
mysql:8
Image + Tag. MySQL in der Version 8.
Du gibst kein Kommando an, das ausgeführt werden soll. Damit verwendest du das Standard-Kommando des Images (das wiederum den Server startet).
Falls du eine Meldung erhältst, die ungefähr so aussieht,
„Bind for 0.0.0.0:3306 failed: port is already allocated.“,
dann ist bereits der Port 3306 lokal belegt.
Verwende einfach einen anderen Port z.B. „-p 3355:3306“
Toll! MySQL läuft.
Schau dir die Daten an, die der Server generiert hat.
ls -l /var/tmp/test-mysql-data
Verbinde dich zum Server.
mysql -h 127.0.0.1 -P3306 -uroot -proot test
Erstelle eine Tabelle.
Stoppe den Server (stoppe den laufenden MySQL Prozess im Terminel mit STRG+4).
Starte den Server neu mit docker run ...
(siehe Kommando von oben).
Prüfe, ob die erstellte Tabelle noch da ist (ob sie persistiert wurde, trotz Container-Neustart).
mysql -h 127.0.0.1 -P3306 -uroot -proot test -e "show tables;"
bzw.
mysql -h 127.0.0.1 -P3306 -uuser1 -ppassword1 test -e "show tables;"
Du möchtest wissen, welche weiteren Optionen es für „docker run“ gibt? Dann befrage das Hilfe-Kommando.
docker help run
Anwendungsfall: nano Editor in Docker
Aufgabe: Du möchtest den Konsolen-Editor „nano“ verwenden – ihn jedoch nicht lokal installieren.
Als Grundlage nimmst du ein kleines und schnelles Betriebssystem: Alpine.
Natürlich möchtest du die Möglichkeit haben, Dateien permanent auf deinem eigenen PC zu speichern.
docker run --rm -it -v /tmp/mydata:/app alpine sh -c "apk add nano; nano /app/myfile.txt"
Viele der Optionen und Parameter kommen dir bestimmt bekannt vor.
Neu sind -it
aka -i -t
: Interactive Terminal
Als Kommando benutzt du „sh“. Dieses Kommando bekommt wiederum „-c“ als Argument und die Befehle, die ausgeführt werden sollen. In diesem Fall installierst du nano und öffnest eine Datei in nano.
Die Datei wird bei dir lokal als /tmp/mydata/myfile.txt
gespeichert.
Führe das Kommando mehrfach aus.
Dir fällt bestimmt auf, dass nano bei jedem Start neu installiert werden muss. Wie das behoben wird, zeige ich dir im nächsten Kapitel.
Anwendungsfall: Linux testen mit Docker
Aufgabe: Du möchtest eine Linux Version testen/benutzen/ausführen ohne sie lokal bei dir zu installieren.
Hier die Kommandos dafür.
docker run --rm -it debian:10-slim bash
docker run --rm -it alpine sh
docker run --rm -it fedora bash
Und schon bist du in einem Terminal in Debian/Alpine/Fedora – egal, ob du gerade mit Linux, Windows oder MacOS arbeitest.
Großartig!
Anwendungsfall: SASS compile mit Docker
Aufgabe: Du möchtest mit SASS deine scss-Dateien kompilieren (scss -> css).
1. Erstelle die Datei file.scss
/* file.scss */ #hui { .pfui { color: #ff0000; } }
2. Öffne ein Terminel und wechsle in den Ordner, in dem die Datei liegt.
3. Führe dieses Kommando aus
docker run --rm -it -v "$PWD:/app" -w /app node:slim bash -c "npm install -g sass; sass ./file.scss > ./file.css"
4. Prüfe den Inhalt
cat file.css
npm install dauert zu lange?
Lösung: Siehe Nächstes Kapitel.
Weiteres
Ich hoffe, es wird klar, wie flexibel Docker ist.
Fix mal einen Container starten, nur das notwendige persistieren und danach den Container stoppen (wegwerfen). Toll!
Offizielle Images sind meist gut dokumentiert und flexibel konfigurierbar (z.B. MySQL Credentials per Environment Variablen übergeben).
Als nächstes geht es darum, dir dein eigenes Image zu bauen.
Eigenes Image bauen – dein erstes Dockerfile
Nun lernst du, wie du dein eigenes Image baust, welches mithilfe deiner Bauanleitung erstellt wird. Wenn ein Image einmal gebaut ist, kann es schnell gestartet werden ohne die Bauanleitung erneut (bei jedem Start) ausführen zu müssen.
Als Beispiel verwendest du das nano-Szenario aus dem vorherigen Kapitel und verbesserst es.
Erstelle eine Datei „Dockerfile“ (am besten in einem neuen Ordner). Nun befülle diese Datei.
# Dockerfile FROM alpine RUN apk update \ && apk add nano
Dann führe die Bauanleitung aus (in dem Ordner in dem die Dockerfile-Datei liegt).
docker build -t my-local-nano .
Starte den Container
docker run --rm -it -v /tmp/mydata:/app -w /app my-local-nano nano testfile.txt
Schreibe nun etwas mit nano, speichere die Datei und prüfe die erstellte Datei auf deinem Rechner
cat /tmp/mydata/testfile.txt
Du hast jetzt
- ein wiederverwendbares Image mit Namen
my-local-nano
erstellt - einen Container damit gestartet
- eine Datei gespeichert und auf deinem lokalen Rechner persistiert
Du kannst jetzt
- den
docker run ...
Befehl erneut ausführen, ohne das Image erneut zu bauen
Dockerfile’s helfen dir dabei ausgeführte Kommandos in Layern zu cachen. Wenn du das Dockerfile nicht änderst, dann muss das Image nicht erneut gebaut werden. Das beschleunigt die Ausführung von docker run
.
Was kann ein Dockerfile noch?
Stelle dir vor, du verbindest dich mit SSH auf einen Server. Du konfigurierst Services, legst Dateien an, veränderst Parameter usw.
Damit du das Ganze nicht andauernd wiederholen musst, schreibst du ein Skript dafür.
Ähnlich wie dieses Skript (Kommandos sequentiell abarbeiten) funktioniert das Dockerfile.
Gängige Inhalte für ein Dockerfile sind
- System updaten (
apt-get update && apt-get upgrade -y
) - Pakete installieren (
apt-get install -y php-cli
) - Konfigurationen / Source-Code Dateien in den Container kopieren (
COPY
) - Den ausführenden Systemuser setzen (
USER
)
Eine vollständige Übersicht findest du in der Dockerfile reference und in den Best Practices für Dockerfiles.
Es hilft dir, wenn du dir Dockerfiles von Open-Source-Projekten ansiehst, um zu lernen, welche Best Practices angewendet werden sollten. Docker Best Practices, Guide Best Practices.
Lass mich rein Container – docker exec
Es ist durchaus üblich, Container zu starten und diese später zu „betreten“ (per Terminal in den Container springen).
Das tust du jetzt mit folgenden Kommandos.
Schritt 1: Container starten.
docker run php:apache
Schritt 2: Container ID (oder Name) rausfinden (zweites Terminal).
docker ps
Schritt 3: Mit Container verbinden.
docker exec -it bdd5c095c543 sh
Damit kannst du jederzeit in bereits gestartete Container springen und Aktionen ausführen.
Beispiel Aktionen:
- Konfigurationen ändern
- Service neustarten
- Dateien editieren
- Logfiles anschauen
Helfer-Snippets – docker aliases
Shell aliases helfen dir dabei, häufig benutzte Befehle komfortabel zwischenzuspeichern und wiederverwendbar zu machen.
Ein beliebtes Beispiel (quasi Standard), um sich den Inhalt eines Verzeichnisses anzeigen zu lassen, ist ll
(kurz für z.B. ls -l
).
Alias für Composer in Docker
alias composer='docker run --rm -it -u "$(id -u):$(id -g)" -w /app -v $PWD:/app composer'
Mit diesem Alias wird das Kommando composer
bereitgestellt. Du kannst es gerne anders benennen.
Das Kommando startet einen Container mit dem offiziellen Composer-Image, benutzt deine lokale User-ID und mountet deinen aktuellen Ordner als /app
innerhalb des Containers.
Prüfe, ob dein erstellter Alias funktioniert.
Tipp: Öffne dein Terminal erneut, damit der Alias wirksam wird.
Alias für PHP in Docker
alias php72='docker run --rm -it -u "$(id -u):$(id -g)" -w /app -v $PWD:/app --entrypoint=php php:7.2-cli'
Mit diesem Alias wird der Befehl php72
definiert.
Du kannst diesen Befehl wie den Standard-PHP-Befehl benutzen.
Da du dich mittlerweile schon gut mit Docker auskennst, hast du sicherlich bemerkt, dass du im Alias einfach 7.2
mit 7.3
austauschen kannst, um die Version zu ändern.
Verschiedene PHP-Versionen auf einem System installieren – das war einfach!
Worauf kommt es bei Docker Images an?
Damit du effektiv und schnell arbeiten kannst, solltest du bei Docker Images Folgendes beachten:
- Best-Practices befolgen.
- Anzahl der Image-Layer minimieren.
- Keine unnötigen (dev) Tools in Images installieren die deployed werden.
- Image-Dateigröße beachten (lieber slim anstatt full, lieber Alpine anstatt Debian).
- Wiederverwendbarkeit (Environment Variablen zur Konfiguration benutzen).
Hier ein kleiner Vergleich zur Dateigröße von Images:
REPOSITORY | TAG | SIZE |
---|---|---|
node | slim | 178MB |
python | latest | 932MB |
mysql | latest | 456MB |
php | cli | 398MB |
php | apache | 414MB |
composer | latest | 165MB |
debian | 10-slim | 69MB |
debian | latest | 114MB |
fedora | latest | 194MB |
ubuntu | latest | 64MB |
alpine | latest | 6MB |
Docker Compose
docker-compose ist ein Tool, mit dem du mehrere Container verknüpfen kannst. Das hilft dir, Applikationen zu starten, die mehrere Services benötigen z.B. einen LAMP Stack (Linux, Apache, MySQL, PHP).
Docker Compose installieren
docker-compose ist einfach zu installieren. Bitte folge dieser Anleitung.
Prüfe mit docker-compose -v
, ob die Installation funktioniert hat.
docker-compose.yml Datei
Docker Compose benötigt eine Konfigurationsdatei, die beschreibt, welche Container gestartet werden sollen und wie diese miteinander kommunizieren. Dies ist die docker-compose.yml Datei.
Diese Konfiguration verknüpft mehrere Container – egal, ob fertige Images oder selbst gebaute Images (mit deinem eigenen Dockerfile).
Docker LAMP Stack
Nun startest du mehrere Container die dem LAMP Stack entsprechen.
L(inux) wird indirekt verwendet.
A(pache) und P(HP) laufen innerhalb von einem Container.
M(ySQL) läuft in einem separaten Container, kann jedoch von PHP verwendet werden.
docker-compose.yml File
version: "3.7" services: php: container_name: app-php image: php:apache volumes: - "./src:/var/www/html/" ports: - "8081:80" environment: MYSQL_USER: user1 MYSQL_PASSWORD: password1 links: - mysql depends_on: - mysql networks: app: mysql: container_name: app-mysql command: --default-authentication-plugin=mysql_native_password --sql-mode="" image: mysql:8 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: app MYSQL_USER: user1 MYSQL_PASSWORD: password1 volumes: - "/var/tmp/app-mysql:/var/lib/mysql" ports: - "3307:3306" networks: app: networks: app:
Starte die Container
docker-compose up
Springe in den MySQL Container
docker-compose exec mysql mysql -u root -proot
Springe in den PHP Container und prüfe, ob die MySQL Zugangsdaten in den Umgebungsvariablen ankommen.
docker-compose exec php bash # zu Container connecten apt-get update && apt-get install -y default-mysql-client # MySQL Client installieren env | grep -i mysql # Environment Variablen checken
Wenn all das geklappt hat, kannst du dich von innerhalb des PHP-Containers zum MySQL-Container verbinden.
mysql -h mysql -u ${MYSQL_USER} -p${MYSQL_PASSWORD} app
Was fehlt noch zur vollständigen Applikation?
Wie du sicher bemerkt hast, werden in der docker-compose.yml Datei nur offizielle Images verwendet. Du wirst aber sehr schnell eigene Extensions, Konfigurationen, Behaviours usw. in deinen Images benötigen.
Hierzu kannst du, wie gewohnt, deine eigenen Dockerfile’s schreiben und diese in Docker Compose verwenden. Siehe docker-compose file build Referenz.
Wie geht es weiter? – Der Weg zu 100%
Super, jetzt kennst du die Grundlagen von Docker. Doch der Weg endet hier noch nicht, es gibt noch viel zu lernen.
Nächste möglichen Themen sind:
- Docker Compose Datei vollständig verstehen.
- Dockerfile Entrypoints verstehen und anwenden.
- Docker in PHPStorm verwenden.
- Open Source Dockerfiles lesen, verstehen und davon lernen.
- Separate Container pro Prozess benutzen (z.B. PHP-FPM separat + NGINX, 2 Container).
- Docker benutzen, um Grafische UI-Applikationen laufen zu lassen (eine Spielerei).
- Viele eigene Images zur Übung bauen.
- Deploy-Methoden testen z.B. Kubernetes, Heroku + Docker, Google Cloud + Docker.
- Über Docker Container vs Virtualisierung lesen.
- Welche Technologien werden durch Docker ersetzt (bzw. stehen in Konkurrenz), z.B. VirtualBox, Vagrant, KVM.
Wer sind wir? Warum verwenden wir Docker?
Als Online-Marktplatz für hochwertigen Luxus-Schmuck bringt FineJewels24 Händler und Verkäufer direkt zusammen. Egal ob Ringe, Armbänder, Halsketten oder Ohrringe, Markenschmuck bekannter Designer oder seltenes Einzelstück – FineJewels24 bietet eine außergewöhnliche Schmuck-Vielfalt von dezent bis ausgefallen. Lass dich von unseren atemberaubenden Angeboten verzaubern!
Das FineJewels24-Core Team besteht aus 10 Personen.
Unsere 3 Entwickler (2x Backend, 1x Frontend) verwenden Docker für verschiedenste Zwecke, z.B. für
- lokale Entwicklung,
- Container für Deployment Skripte,
- GitLab CI/CD Pipelines,
- Komprimierung von CSS, Javascript und Typescript Dateien,
- Aliase / Snippets, um produktiver zu sein.
Uns hat die lokale Entwicklung mit Docker geholfen, um den Onboarding-Aufwand für neue Entwickler zu reduzieren. Außerdem gewährleistet Docker, dass alle Entwickler in der gleichen Umgebung arbeiten. Um eine bestmögliche Developer-Experience zu gewährleisten, wurden viele Prozesse mit Scripten abgedeckt. Einige Probleme der Kompatibilität zwischen Mac und Linux haben etwas mehr Aufmerksamkeit benötigt 😉
Wir sind zufrieden mit Docker und werden die Nutzung zukünftig ausbauen.