Als Docker 2013 das Licht der Welt erblickte, war ich von der einfachen Virtualisierungs-Idee sofort fasziniert. Leider konnte ich mich erst fünf Jahre später wirklich damit beschäftigen, als ich anfing, einen neueren Server aufzusetzen. Auf meinem guten alten MacServer bekam ich Docker leider nie zum Laufen.

Allerdings kam schon bald etwas Ernüchterung auf. Erstens ist der Quer-Einstieg recht holperig, da es inzwischen zwei verschiedene Docker-Lösungen gibt: Docker Toolbox (es scheint: nicht mehr ganz so gewollt) und Docker Desktop (gegoogelte Lösungen für Probleme beziehen sich leider meist hierauf).
Die Unterschiede zwischen den beiden und der jeweilige Aufbau werden auf dieser Seite wirklich sehr schön erklärt.
Die Systemvoraussetzungen für Docker Desktop sind allerdings um einiges höher, als für die Docker Toolbox: Aktuell wird mindestens MacOS X 10.12 benötigt (nicht 10.10, wie auf der eben verlinkten Vergleichsseite erwähnt) und noch dazu muss der Prozessor des Macs mit einer "Memory Management Unit" ausgestattet sein, was bei Macs, die so etwa ab 2010 auf den Markt kamen, der Fall sein sollte. Detaillierte Informationen gibt es auf der Docker Desktop-Installationsseite.

Nun ja...der neuere Mac, den ich als Ersatz für den alten Server einsetzen wollte, ist aber leider auch nicht mehr so ganz neu und somit kann ich Docker Desktop nicht einsetzen.
Docker Toolbox funktionierte allerdings auch nicht wirklich: Beim Starten des Programmes Kitematic, mit dem man Docker verwalten kann, kamen nur seltsame Fehlermeldungen.
Docker Toolbox ist an sich nur "ein Paket", geschnürt aus den notwendigen drei Kommandozeilen-Programmen samt Installer und dem GUI Kitematic, welches der Verwaltung der Docker-Container dient.
Die wirklich notwendigen drei Kommandozeilen-Programme lassen sich auch so (am einfachsten mit dem Paketmanager Homebrew) installieren.

Weitere Momente der Ernüchterung: Antworten auf essentielle Fragen, wie "ah, da muss ja ein docker-daemon im Hintergrund laufen...wie starte ich den und wie sorge ich dafür, dass das beim Reboot automatisch geschieht?" sind schwer zu finden und bei den ersten Gehversuchen war es dann doch nicht nur einfach "fertiges Image herunterladen, starten, läuft".

Installation

Ich habe mich hierbei voll und ganz nach diesem Blogeintrag gerichtet.

Vorbereitungen

Hat man es schon mal mit der Docker Toolbox versucht, sollte diese erst mal wieder de-installiert werden.
Der folgende erste Befehl lädt ein Skript von Github herunter, welches die durch die Docker Toolbox installierten Dateien wieder de-installiert. Vor der Ausführung des Skriptes (als root!) ist es natürlich erstmal ratsam, einen Blick in das Skript zu werfen.

curl -o uninstall_dockertoolbox.sh https://raw.githubusercontent.com/docker/toolbox/master/osx/uninstall.sh
chmod +x uninstall_dockertoolbox.sh
sudo ./uninstall_dockertoolbox.sh

Falls noch nicht geschehen, Homebrew installieren, ansonsten updaten.

# Installieren
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
# Updaten
brew update
# Das Cask-Repositoriy hinzufügen (https://github.com/caskroom/cask/)
# Damit kann man anscheinend via Homebrew auch GUI-Programme installieren
# Bin mir aber nicht wirklich sicher, ob wir das überhaupt im Folgenden brauchen
brew tap caskroom/cask

Diese Art der Docker-Installation benötigt eine installierte Virtualisierungs-Lösung wie VirtualBox, VMWare Fusion oder Parallels Desktop.
Ich verwende im Folgenden die gratis erhältliche VirtualBox. Falls noch nicht geschehen, sollte man diese spätestens jetzt installieren. Es ist nicht notwendig, eine virtuelle Maschine einzurichten. Darum kümmern wir uns später.

Nun die Installation von Docker

brew install docker
brew install docker-machine
brew install docker-compose

Nun noch die virtuelle Maschine einrichten.
Der Name default, der im Folgenden öfters auftaucht, ist von mir selbst vergeben. Es kann genau so gut ein anderer Name verwendet werden. In diesem Fall ist bei allen weiteren Befehlen der abweichende Name zu beachten!
Ich empfehle allerdings, default zu verwenden, da "default" bei allen weiteren Befehlen eigentlich auch weg gelassen werden kann. Wird kein Name angegeben, geht Docker vom Namen "default" aus.

Bei der Verwendung von VMWare Fusion oder Parallels Desktop statt VirtualBox ist statt dem nächsten Befehl eine andere Vorgehensweise zu beachten, die hier beschrieben wird.

# Virtuelle Maschine erstellen
docker-machine create --driver=virtualbox default

# Docker Client und Docker Engine mit der virtuellen Maschine verbinden
# (nur bei Verwendung von VirtualBox??)
docker-machine env default

# Damit die aktuelle Shell die virtuelle Maschine kennt...
# Diesen Befehl sollte man am besten auch dem .bash_profile hinzufügen
eval $(docker-machine env default)

# Die virtuelle Maschine bekommt eine eigene IP.
# Um diese in Erfahrung zu bringen...
docker-machine ip default

Updaten

brew update  # Homebrew updaten
brew upgrade # Alle Homebrew-Packages updaten

Docker Daemon starten

Schön und gut, alles für Docker notwendige ist installiert.
Möchte man jedoch loslegen, erscheint bei jedem Befehl, die Fehlermeldung, dass die Socket-Datei nicht gefunden wird und...

* Are you trying to connect to a TLS-enabled daemon without TLS?
* Is your docker daemon up and running?

Der Docker-Daemon muss im Hintergrund ausgeführt werden!
Das passiert bei der Installation leider nicht von alleine und das macht man nicht auf die Art, wie es bei den ersten 20-30 Google-Treffern erklärt wird, sondern so:

docker-machine start default
docker-machine env default
eval $(docker-machine env)

Docker Daemon automatisch starten

Das ganze läuft wahrscheinlich auf einem Server und wenn man einen Server neu startet, sollte alles einigermaßen automatisiert ablaufen. Auch das Starten des Docker-Daemons.

Dazu folgende plist zu den LaunchDaemons hinzufügen:

~/Library/LaunchDaemons/com.docker.machine.default.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>EnvironmentVariables</key>
        <dict>
            <key>PATH</key>
            <string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin</string>
        </dict>
        <key>Label</key>
        <string>com.docker.machine.default</string>
        <key>ProgramArguments</key>
        <array>
            <string>/usr/local/bin/docker-machine</string>
            <string>start</string>
            <string>default</string>
        </array>
        <key>UserName</key>
        <string>HIER DEN (kurzen) BENUTZERNAMEN EINTRAGEN</string>
        <key>RunAtLoad</key>
        <true/>
    </dict>
</plist>

launchctl load -w ~/Library/LaunchDaemons/com.docker.machine.default.plist

Zuerst hatte ich die .plist-Datei in LaunchAgents und nicht in LaunchDaemons, was einige Zeit problemlos funktioniert hatte, aber dann auf einmal nicht mehr: Docker wurde beim Boot nicht gestartet und PID-Status war 1 (statt 0), was wohl an mangelnden Privilegien liegen könnte.
Nachdem ich die .plist in LaunchDaemons verschoben hatte, funktioniert es dann. Allerdings sind hier noch die beiden Zeilen

        <key>UserName</key>
        <string>HIER DEN (kurzen) BENUTZERNAMEN EINTRAGEN</string>

vonnöten, die bei den LaunchAgents nicht notwendig waren.
Als Benutzername ist der eigene kurze , unter dem man normalerweise arbeitet, nötig, ansonsten läuft Docker mit Root-Rechten, was dann auch wieder Probleme bereitet.

Docker-Container von anderen Computern aus nutzen

In Docker laufende Web-Apps sind eigentlich nur am lokalen Server mit einer speziellen IP, die nicht in der Subnetzmaske des lokalen Netzwerkes liegt, aufrufbar. Um von einem anderen Rechner im Netzwerk Zugriff zu haben, muss in VirtualBox noch ein Portforward eingerichtet werden.
Dann kann man den gewünschten Container über die IP des Servers, auf dem Docker läuft, und dem gewählten Port erreichen.

Beispiel Port 8888 "publizieren":

Per Kommandozeile:
VBoxManage controlvm "default" natpf1 "tcp-port9000,tcp,,9000,,9000"

Wobei die kommagetrennten Werte folgenden Feldern des VirtualBox-GUIs (zweiter Screenshot) entsprechen:
Name,Protokoll,Host-IP,Host-Port,Gast-IP,Gast-Port
 
Ãœber die GUI von VirtualBox:
Einstellungen der virtuellen Maschine default öffnen und unter "Netzwerk" den Button "Port-Weiterleitung" anklicken.
 
Neuen Eintrag hinzufügen, bei dem Host-IP und Gast-IP leer sind (man kann auch 127.0.0.1 bei Host-IP eintragen, dann ist der Container allerdings nur auf dem Host selbst aufrufbar), der Port, auf dem der Container lauscht (Gast-Port) und der Port, über welchen man den Container aus dem restlichen Netzwerk erreichen möchte (Host-Port) eintragen.
 

Docker verwalten

Das GUI-Programm Kitematic ist Teil der Docker Toolbox, lässt sich allerdings auch separat herunterladen und installieren.

Eine gute Übungsmöglichkeit für das Hinzufügen von Docker-Containern ist allerdings, auf das Web-Interface Portainer zurückzugreifen, welches selbst als Docker-Container zur Verfügung steht:

docker volume create portainer_data
docker run --name portainer -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data --restart=always portainer/portainer

Mit diesen beiden Befehlen wird ein virtuelles Laufwerk portainer_data erstellt, das Portainer-Image aus dem GitHub-Repository portainer/portainer heruntergeladen und die Web-Oberfläche auf dem Port 9000 zugänglich gemacht.
Damit man auf Portainer nicht nur vom Server aus zugreifen kann, auf dem Docker läuft und auch nicht immer nachschauen muss, welche seltsame IP die Docker-Maschine hat, sollte in VirtualBox noch ein Port-Forwarding eingerichtet werden. Ich richte dies im folgenden Beispiel so ein, damit man sehen kann, dass der "Docker-Port" und der "reale Port" nicht zwingend derselbe sein müssen:
VBoxManage controlvm "default" natpf1 "Portainer,tcp,,8001,,9000"
Portainer wäre nun direkt am Server unter der Adresse http://ip-der-virtuellen-maschine:9000 (Port 9000 ist obligatorisch, da wir diesen vorhin beim docker run-Kommando gewählt hatten) und von jedem anderen Gerät im lokalen Netzwerk (natürlich auch vom Server selbst) unter der Adresse http://ip-des-servers:8001 erreichbar.

Beim ersten Öffnen der Seite kann man Benutzernamen und Passwort für den Admin-Zugang eingeben.

Mit --restart=always geben wir übrigens mit, dass der Container auch nach einem Neustart gleich wieder mit Docker mitgestartet wird.

Portainer updaten


Nach einiger Zeit fragte ich mich, wie ich Portainer updaten kann.
Kurze Antwort: Garnicht. Aber dank dem Docker-System und den auf portainer_data ausgelagerten Daten ist das Docker-Image schnell gelöscht, schnell neu heruntergeladen und wieder in Betrieb gesetzt:

docker pull portainer/portainer
docker stop portainer
docker rm portainer
docker run --name portainer -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data --restart=always portainer/portainer

Wenn ich das mit docker-machine und docker richtig verstanden habe...

Der Befehl docker-machine ist nur zur Verwaltung der virtuellen Maschine. Zum Verwalten der darin enthaltenen Container benötigt man den Befehl docker.

Wieso es diese beiden unterschiedlichen Befehle gibt, wo (der Befehl) docker sowieso schon so eine unüberblickbare Anzahl an verschiedenen Optionen hat, ist mir allerdings nie so richtig klar geworden.

Um (den Befehl) docker verwenden zu können, muss man der aktuellen Shell-Session mitgeben, um welche Maschine es sich denn eigentlich handelt, die man hier bedienen möchte.
Wir hatten vorhin die Maschine default angelegt, doch es lassen sich x-beliebig viele virtuelle Maschinen für Docker anlegen.

Beim Öffnen einer neuen Shell-Session muss man folgende beiden Befehle eingeben, welche man natürlich auch im .bash_profile hinterlegen kann.
Die Angabe des Namens default kann auch entfallen, wenn es sich tatsächlich um die Maschine namens "default" handelt.

docker-machine env default
eval $(docker-machine env default)

Docker Volumes

Einem Docker-Container kann man mit der Option -v ein Volume zuweisen. Meistens sieht das so aus:

docker run -v my_data:/data ...

Hierbei wird ein virtuelles Volume namens my_data angelegt, sogesehen ein Volume-Image, welches im laufenden Docker-Container unter /data eingehängt ist.
Ich habe es bisher noch nicht hinbekommen, außerhalb von Docker auf dieses Volume zugreifen zu können. Man kann es nicht irgendwie im System mounten, höchstens einen weiteren Docker-Container starten, der vi oder pico enthält und dort das Volume ebenfalls einbinden. Nicht gerade sehr praktisch.
Vielleicht gibt es eine einfachere Lösung, aber die ist mir Google bisher schuldig geblieben.

Es besteht aber auch die Möglichkeit, ein Verzeichnis des "lokalen Dateisystems" (so liest sich das jedenfalls immer) im Docker-Container einzuhängen. Das sieht dann in etwa so aus:

docker run -v /pfad/zu/ordner:/data

Das hatte ich immer so verstanden, dass dann innerhalb des Docker-Containers im Verzeichnis /data die Dateien zu sehen sein sollten, die ich auf dem Server, auf welchem Docker läuft, im Verzeichnis /pfad/zu/ordner liegen habe. Aber Fehlanzeige.

Irgendwann bin ich mal dahintergekommen, dass dieses "lokale Dateisystem" innerhalb der Docker-Maschine zu finden ist, welche sich mit dem Befehl docker-machine ssh öffnen lässt.

Um nun auch noch von einer Instanz darüber (meinem Mac) zugreifen zu können, muss das Verzeichnis /pfad/zu/ordner in der VirtualBox als gemeinsamer Ordner angelegt sein.
Im rechts gezeigten Beispiel habe ich den Ordner /server/etc/docker, welcher auf meinem Mac-Server liegt, als gemeinsamen Ordner eingerichtet, welcher dann in der Docker-Umgebung unter /docker eingebunden ist.

Starte ich nun einen Container mit folgender Option

-v /docker/container1:/data

Dann sind die Daten innerhalb des Containers in /data zu finden und auf meinem Mac-Server im Verzeichnis /server/etc/docker/container1

Docker hinter Proxy

Wie bei fast so allem, was mit Internetverbindung zu tun hat, renne ich in der Firma gegen eine Wand, wenn ich Docker hinter dem Firmen-Proxy verwenden will. Da Docker ja schön einfach alle Images aus nem Repositoriy zieht, geht bei mir so gar nix.
Löst man ein Problem der Proxy-Verbindung, so taucht das nächste auf...

Soweit meine Schritte, die bisher noch nicht zum Erfolg geführt hatten:

Erkenntnis 1: Die Environment-Variablen http_proxy und https_proxy meiner Shell reichen Docker wohl nicht aus. Am besten man hinterlegt die Proxy-Informationen in einer Config-Datei je Maschine:

~/.docker/machine/machines/default/config.json
"HostOptions": {
        "Driver": "",
        ...
        "EngineOptions": {
           ...
            "Env": [
              "HTTP_PROXY=http://user:pass@172.17.60.9:80",
              "HTTPS_PROXY=http://user:pass@172.17.60.9:80",
              "NO_PROXY=172.17.60.8",
              "ALL_PROXY=''"
            ],

Erkenntnis 2: Ist allerdings die Environment-Variablen all_proxy in meiner Shell gesetzt, führt das bei jedemdocker run-Befehl zur Fehlermeldung proxy: unknown scheme: http (so lange der all_proxy kein SOCKS5-Proxy ist)

Abhilfe schafft hier, ALL_PROXY wie in meiner zuvor bekannt gemachten Erkenntnis #1 auf '' zu setzen.

Erkenntnis 3: Ein docker run führte zur Fehlermeldung get https://registry-1.docker.io/v2/: dial tcp 34.228.211.243:443: connect: connection refused

Hier gibt's laut Internetrecherchen DNS-Probleme und den Google-DNS in /etc/resolv.conf hinzuzufügen, schafft Abhilfe:

sudo pico /etc/resolv.conf

# Folgende Zeile hinzufügen und andere nameserver-Zeilen
# mit einem # auskommentieren

nameserver 8.8.8.8

# Speichern und Docker Daemon neu starten
docker-machine restart

Erkenntnis 4: Nun haben wir die Fehlermeldung docker: Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).

Innerhalb der Docker-Machine kann man (auch nochmals) den Proxy konfigurieren. Dazu per ssh in die Maschine wechseln und...

docker-machine ssh default

# Ab hier sind wir in der virtuellen Maschine!

# Leider gibt's als Texteditor nur den gewöhnungsbedürftigen vi
sudo vi /var/lib/boot2docker/profile

# Mit dem Cursor ans Ende des Dokumentes gehen und Taste i zum Text einfügen drücken
# Folgende beiden Zeilen mit den Proxy-Zugangsdaten hinzufügen
export HTTP_PROXY='http://<Proxy-Adresse>'
export HTTPS_PROXY='http<s>://<Proxy-Adresse>'
# Mit <esc> aus dem Bearbeitungsmodus springen und mit :wq<enter> speichern und schließen
# Bei Fehleingabe mit :q!<enter> ohne Speichern schließen

# Docker Daemon neu starten
sudo /etc/init.d/docker restart

# Zurück aus der virtuellen Maschine in die echte Shell
exit

Erkenntnis 5: Unser Firmen-Proxy liegt im selben Subnetzbereich (172.17.x.x) wie der virtuelle Docker-Netzwerkanschluss.

Dadurch bekomme ich nun als nächste Fehlermeldung proxyconnect tcp: dial tcp <Proxy-Adresse>: connect: no route to host

docker-machine ssh default

# Ab hier sind wir in der virtuellen Maschine!

# Sind wir hier im selben Netzwerkbereich wie der Proxy?
# (Netzwerkadresse docker0)
ifconfig

# Falls ja auf 172.18.x.x ändern
sudo ifconfig docker0 172.18.0.1 netmask 255.255.0.0

# Achtung: Nach einem sudo /etc/init.d/docker restart
# ändert sich der Netzwerkbereich wieder zurück!

# Zurück aus der virtuellen Maschine in die echte Shell
exit

Läuft auf dem Rechner, auf dem Docker läuft, ein lokaler Proxy á la squid, dann sollte man statt der Proxyadresse 127.0.0.1 die Adresse verwenden, die der Host von Docker aus gesehen hat.
Die Adresse ist mit einem ifconfig am Host auszulesen. In meinem Fall hat vboxnet1 die Adresse 192.168.99.1

Erkenntnis 6: Jetzt brauchen wir noch Zertifikate, weil der Firmen-Proxy die Zertifikate ändert und ich nun folgenden Fehler bekomme docker: Error response from daemon: Get https://registry-1.docker.io/v2/: remote error: tls: handshake failure.

So kopiere ich die beiden Dateien, die ich von der IT bekommen habe, erstmal in die virtuelle Maschine:

docker-machine scp proxy.crt default:~/
docker-machine scp proxy.pem  default:~/

Und in der virtuellen Maschine verschiebe ich sie an verschiedene Stellen (die wohl nix bringen).

docker-machine ssh default

# Ab hier sind wir in der virtuellen Maschine!
# Ich stopfe alle möglichen Zertifikatesdateien irgendwo rein,
# wo ich mal gelesen haben könnte, es würde was bringen
sudo mv proxy.crt /etc/ssl/certs/
sudo cp /etc/ssl/certs/proxy.crt /usr/local/share/ca-certificates/
sudo mv proxy.pem usr/local/share/ca-certificates/proxy.crt
sudo update-ca-certificates
# Updating certificates in /usr/local/etc/ssl/certs...
# 1 added, 0 removed; done.
# (bringt aber trotzdem nix)

# Docker neu starten und im Zweifelsfall nochmals die Erkenntnis 4 wiederholen
sudo /etc/init.d/docker restart

# Zurück aus der virtuellen Maschine in die echte Shell
exit

Trotz alldem bekomme ich weiterhin den handshake failure ...

Probleme

Zugegebenermaßen stellt sich bei mir ein gewisser Stress ein, wenn ich den Server mal neu starten muss.
Es gab schon einige Momente, wo sich Docker nicht mehr starten ließ.

Error setting up host only network on machine start: VirtualBox is configured with multiple host-only adapters with the same IP "192.168.99.1". Please remove one

Es kann sein, dass bereits eine Docker-Instanz läuft. Kann aber sein, dass nicht.

Mit Hilfe des Befehls
VBoxManage list -l hostonlyifs
lassen sich alle "Host Only Netzwerke" der VirtualBox anzeigen. Hier wurden tatsächlich zwei mit derselben IP-Adresse angezeigt.

Name:            vboxnet0
GUID:            786f6276-656e-4074-8000-0a0027000000
DHCP:            Disabled
IPAddress:       192.168.99.1
NetworkMask:     255.255.255.0
IPV6Address:    
IPV6NetworkMaskPrefixLength: 0
HardwareAddress: 0a:00:27:00:00:00
MediumType:      Ethernet
Wireless:        No
Status:          Down
VBoxNetworkName: HostInterfaceNetworking-vboxnet0

[...]

Name:            vboxnet3
GUID:            786f6276-656e-4374-8000-0a0027000003
DHCP:            Disabled
IPAddress:       192.168.99.1
NetworkMask:     255.255.255.0
IPV6Address:    
IPV6NetworkMaskPrefixLength: 0
HardwareAddress: 0a:00:27:00:00:03
MediumType:      Ethernet
Wireless:        No
Status:          Up
VBoxNetworkName: HostInterfaceNetworking-vboxnet3

Den ersten namens vboxnet0 mit Status Down habe ich gelöscht. Jener war auch in den VirtualBox-Einstellungen der virtuellen Maschine unter Netzwerk > Adapter 2 eingerichtet, so habe ich dort vorsorglich auf vboxnet3 umgestellt.

vboxnet0 löschen:
VBoxManage hostonlyif remove vboxnet0

Danach ließ sich Docker starten.