Blog

Erstellen Sie den kleinstmöglichen Docker-Container

Adriaan de Jonge

Adriaan de Jonge

Aktualisiert Oktober 22, 2025
9 Minuten

Wenn Sie mit Docker herumspielen, stellen Sie schnell fest, dass Sie eine große Anzahl von Megabytes herunterladen, wenn Sie vorkonfigurierte Container verwenden. Ein einfacher Ubuntu-Container übersteigt leicht 200 MB und wenn Software darauf installiert wird, nimmt die Größe zu. In manchen Anwendungsfällen brauchen Sie nicht alles, was Ubuntu mitbringt. Wenn Sie z.B. einen einfachen, in Go geschriebenen Webserver betreiben wollen, brauchen Sie überhaupt kein Tool dafür. Ich habe nach dem kleinstmöglichen Container für den Anfang gesucht und diesen gefunden:

docker pull scratch

Das Kratzbild ist perfekt. Buchstäblich perfekt! Es ist elegant, klein und schnell. Es enthält keine Bugs, Sicherheitslücken, langsamen Code oder technische Schulden. Und das liegt daran, dass es im Grunde leer ist. Bis auf ein paar von Docker hinzugefügte Metadaten. Tatsächlich hätten Sie dieses Scratch-Image auch selbst mit diesem Befehl erstellen können , wie in der Docker-Dokumentation beschrieben:

tar cv --files-from /dev/null | docker import - scratch

Das ist es also, das kleinstmögliche Docker-Image. Ende des Blogbeitrags! ... oder gibt es noch etwas, das wir dazu sagen können? Wie verwenden Sie z.B. das Bild der Scratch-Basis? Es stellt sich heraus, dass dies einige eigene Herausforderungen mit sich bringt.

Aktualisierung (11. Mai 2017): Der unten beschriebene Prozess wurde durch die Einführung von Multi-Stage Builds erheblich vereinfacht. Siehe meinen neuen Blog " Vereinfachen Sie das kleinstmögliche Docker-Image"

Inhalt für das Rubbelbild erstellen

Was können wir auf einem leeren Basis-Image ausführen? Eine ausführbare Datei ohne Abhängigkeiten. Haben Sie ausführbare Dateien ohne Abhängigkeiten? Ich habe früher Code in Python, Java und JavaScript geschrieben. Für jede dieser Sprachen/Plattformen muss eine Laufzeitumgebung installiert sein. Vor kurzem habe ich angefangen, mich mit der Go-Plattform (oder GoLang, wenn Sie das bevorzugen) zu beschäftigen. Und es scheint (Spoiler-Alarm), dass Go statisch gelinkt ist. Also habe ich versucht, einen einfachen Webserver zu kompilieren, der Hello World sagt, und ihn innerhalb des Scratch-Containers auszuführen. Hier ist der Code für den Hello World-Webserver:

Paket Haupt
importieren (
    "fmt"
    "net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Anfrage) {
    fmt.Fprintln(w, "Hallo Welt aus Go in minimalem Docker-Container")
}
func Haupt() {
    http.HandleFunc("/", halloHandler)
    fmt.Drucken("Gestartet, serviert auf 8080")
    err := http.ListenAndServe(":8080", null)
    wenn err != null {
        Panik("ListenAndServe: " + err.Fehler())
    }
}

Offensichtlich kann ich meinen Webserver nicht innerhalb des Scratch-Containers kompilieren, da dieser keinen Go-Compiler enthält. Und da ich auf einem Mac arbeite, kann ich auch nicht einfach so eine Linux-Binärdatei kompilieren. (Es ist zwar möglich, GoLang-Quellen für verschiedene Plattformen zu kompilieren, aber das ist Stoff für einen anderen Blogbeitrag) Ich brauche also zunächst einen Docker-Container mit einem Go-Compiler. Fangen wir ganz einfach an:

docker run -ti google/golang /bin/bash

Innerhalb dieses Containers kann ich den Go-Webserver erstellen, den ich in einem GitHub-Repository abgelegt habe:

gehen Sie zu github.com/adriaandejonge/helloworld

Der Befehl go get ist eine Variante des Befehls go build, mit dem Sie entfernte Abhängigkeiten abrufen und erstellen können. Sie können die resultierende ausführbare Datei mit starten:

$GOPATH/bin/helloworld

Das funktioniert. Aber es ist nicht das, was wir wollen. Wir müssen den Hallo-Welt-Container innerhalb des Scratch-Containers laufen lassen. Wir brauchen also eine Dockerdatei, in der steht:

Von Grund auf neu
ADD bin/helloworld /helloworld
CMD  ["/helloworld"]

und starten Sie ihn dann. So wie wir den Google/Golang-Container gestartet haben, gibt es leider keine Möglichkeit, diese Dockerdatei zu erstellen. Wir brauchen also zunächst einen Weg, um aus dem Container heraus auf Docker zuzugreifen.

Aufrufen von Docker aus Docker selbst

Wenn Sie Docker verwenden, stoßen Sie früher oder später auf die Notwendigkeit, Docker von Docker aus zu steuern. Es gibt mehrere Möglichkeiten, dies zu erreichen. Sie könnten eine Rekursion verwenden und Docker innerhalb von Docker ausführen. Das erscheint jedoch übermäßig komplex und führt wiederum zu großen Containern. Sie können den Zugriff auf den Docker-Server auch außerhalb der Instanz mit ein paar zusätzlichen Befehlszeilenoptionen ermöglichen:

docker run -v /var/run/docker.sock:/var/run/docker.sock -v  $(welcher Docker):$(welcher Docker)  -ti google/golang /bin/bash

Bevor Sie fortfahren, führen Sie bitte den Go-Compiler erneut aus, da Docker beim Neustart unsere vorherige Kompilierung vergessen hat:

gehen Sie zu github.com/adriaandejonge/helloworld

Beim Starten des Containers wird mit dem Flag -v ein Volume innerhalb des Docker-Containers erstellt und Sie können eine Datei vom Docker-Rechner als Eingabe bereitstellen. Der /var/run/docker.sock ist der Unix-Socket, der den Zugriff auf den Docker-Server ermöglicht. Der Teil $(which docker) ist eine clevere Möglichkeit, den Pfad für die ausführbare Datei von Docker innerhalb des Containers anzugeben, ohne ihn hart zu kodieren. Seien Sie jedoch vorsichtig, wenn Sie diesen Befehl auf einem Apple verwenden, wenn Sie boot2docker einsetzen. Wenn die ausführbare Datei von Docker an einem anderen Ort als in der virtuellen Maschine von boot2docker installiert ist, führt dies zu einer Fehlanpassung. Es wird die ausführbare Datei innerhalb des virtuellen Servers von boot2docker in den Container eingefügt. Ersetzen Sie daher $(which docker) durch /usr/local/bin/docker, das fest kodiert ist. Wenn Sie ein anderes System verwenden, besteht die Möglichkeit, dass die Datei /var/run/docker.sock einen anderen Speicherort hat und Sie sie entsprechend anpassen müssen. Jetzt können Sie die Dockerdatei innerhalb des Google/Golang-Containers im Verzeichnis $GOPATH verwenden, das in diesem Beispiel auf /gopath zeigt. Eigentlich habe ich diese Dockerdatei bereits in GitHub eingecheckt. Sie können sie also wie folgt aus dem Go-Build-Verzeichnis an den gewünschten Ort kopieren:

cp  $GOPATH/src/github.com/adriaandejonge/helloworld/Dockerfile
 $GOPATH

Sie müssen dies kopieren, da sich die kompilierte Binärdatei nun in $GOPATH/bin befindet und es nicht möglich ist, Dateien aus übergeordneten Verzeichnissen einzubinden, wenn Sie eine Dockerdatei erstellen. Nach dem Kopieren ist also der nächste Schritt:

docker build -t adejonge/helloworld  $GOPATH

Und wenn alles gut geht, antwortet Docker mit etwas wie:

Erfolgreich gebaut 6ff3fd5a381d

Damit können Sie den Container ausführen:

docker run -ti --name hellobroken adejonge/helloworld

Aber leider antwortet Docker jetzt mit:

2014/07/02 17:06:48 keine solche Datei oder Verzeichnis

Was ist also los? Wir haben eine statisch gelinkte ausführbare Datei innerhalb eines Scratch-Containers. Haben wir einen Fehler gemacht? Wie sich herausstellt, werden in Go keine Bibliotheken statisch gelinkt. Oder zumindest nicht alle Bibliotheken. Unter Linux können wir die dynamisch gelinkten Bibliotheken für eine ausführbare Datei mit dem Befehl ldd anzeigen:

ldd  $GOPATH/bin/helloworld

Der antwortet mit:

linux-vdso.so.1 => (0x00007fff039fe000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f61df30f000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f61def84000) /lib64/ld-linux-x86-64.so.2 (0x00007f61df530000)

Bevor wir also den Hello World-Webserver starten können, müssen wir dem Go-Compiler sagen, dass er statisches Linking durchführen soll.

Statisch gelinkte ausführbare Dateien in Go erstellen

Um statisch gelinkte ausführbare Dateien zu erstellen, müssen wir Go anweisen, den go-Compiler und nicht den cgo-Compiler zu verwenden. Der Befehl dazu lautet:

CGO_ENABLED=0 go get -a -ldflags  '-s'  github.com/adriaandejonge/helloworld

Die Umgebungsvariable CGO_ENABLED weist Go an, den go-Compiler und nicht den cgo-Compiler zu verwenden. Die Flagge -a weist Go an, alle Abhängigkeiten neu zu erstellen. Andernfalls haben Sie immer noch dynamisch gelinkte Abhängigkeiten. Und schließlich ist die Flagge -ldflags '-s' ein nettes Extra. Er reduziert die Dateigröße der resultierenden ausführbaren Datei um etwa 50%. Sie können dies auch ohne den Go-Compiler tun. Die Größenreduzierung ergibt sich aus der Entfernung der Debug-Informationen. Um sicher zu gehen, führen Sie den Befehl ldd erneut aus.

ldd  $GOPATH/bin/helloworld

Es sollte nun mit antworten:

keine dynamische ausführbare Datei

Sie können auch die Schritte zur Erstellung des Docker-Containers um die ausführbare Datei von Grund auf neu ausführen:

docker build -t adejonge/helloworld  $GOPATH

Und wenn alles gut geht, antwortet Docker mit etwas wie:

Erfolgreich gebaut 6ff3fd5a381d

Damit können Sie den Container ausführen:

docker run -ti --name helloworld adejonge/helloworld

Und dieses Mal sollte es mit antworten:

Angefangen, serviert mit 8080

Bis hierhin waren es viele manuelle Schritte und es gibt viel Raum für Fehler. Verlassen wir den Google/Golang-Container und machen wir auf dem umgebenden Rechner weiter:

<Drücken Sie Strg-C>
Ausgang

Sie können das Vorhandensein oder Nichtvorhandensein von Containern und Bildern mit überprüfen:

docker ps -a
docker images -a

Und Sie können Docker mit aufräumen:

docker rm -f helloworld
docker rmi -f adejonge/helloworld

 

Erstellen eines Docker-Containers, der einen Docker-Container erstellt

Die Schritte, die wir bisher durchgeführt haben, können wir auch in einer Dockerdatei aufzeichnen und Docker die Arbeit für uns erledigen lassen:

VON google/golang
LAUFEN  CGO_ENABLED=0 go get -a -ldflags  '-s'  github.com/adriaandejonge/helloworld
RUN cp /gopath/src/github.com/adriaandejonge/helloworld/Dockerfile /gopath
CMD docker build -t adejonge/helloworld gopath

Ich habe diese Dockerdatei in ein separates GitHub-Repository namens adriaandejonge/hellobuild eingecheckt. Sie kann mit diesem Befehl erstellt werden:

docker build -t adejonge/hellobuild github.com/adriaandejonge/hellobuild

Wenn Sie die Option -t angeben, wird das Bild als adejonge/hellobuild benannt und implizit als latest gekennzeichnet. Diese Namen machen es Ihnen leichter, das Abbild später wieder zu entfernen. Als Nächstes können Sie einen Container aus diesem Abbild erstellen und dabei die Flags angeben, die Sie bereits in diesem Beitrag gesehen haben:

docker run -v /var/run/docker.sock:/var/run/docker.sock -v  $(welcher Docker):$(welcher Docker)  -ti --name hellobuild adejonge/hellobuild

Die Angabe des Flags --name hellobuild erleichtert das Entfernen des Containers nach der Ausführung. Sie können dies sogar sofort tun, denn nach der Ausführung dieses Befehls haben Sie bereits das Abbild adejonge/helloworld erstellt:

docker rm -f hellobuild
docker rmi -f adejonge/hellobuild

Und nun können Sie einen neuen Container mit dem Namen helloworld auf der Grundlage des Abbilds adejonge/helloworld starten, wie Sie es zuvor getan haben:

docker run -ti --name helloworld adejonge/helloworld

Da alle diese Schritte von derselben Befehlszeile aus ausgeführt werden, ohne dass eine Bash-Shell innerhalb eines Docker-Containers geöffnet werden muss, können Sie diese Schritte zu einem Bash-Skript hinzufügen und automatisch ausführen lassen. Zu Ihrer Erleichterung habe ich diese Bash-Skripte dem GitHub-Repository hellobuild hinzugefügt. Wenn Sie den kleinstmöglichen Docker-Container ausprobieren möchten, auf dem ein Hello World-Webserver läuft, ohne alle in diesem Blogbeitrag beschriebenen Schritte auszuführen, können Sie auch das vorgefertigte Image verwenden, das ich in das Docker Hub-Repository eingecheckt habe:

docker pull adejonge/helloworld

Mit docker images -a können Sie sehen, dass die Größe 3,6 MB beträgt. Natürlich können Sie es noch kleiner machen, wenn Sie es schaffen, eine ausführbare Datei zu erstellen, die kleiner ist als der Webserver in Go, den ich geschrieben habe. In C oder Assembler können Sie das vielleicht auch schaffen. Allerdings können Sie es niemals kleiner machen als das Scratch-Image. Postscript 16. Juni 2015: Wenn Sie einen anderen Container verkleinern müssen, lesen Sie den Blog How to create the smallest possible Docker container of any image.

Verfasst von

Adriaan de Jonge

Contact

Let’s discuss how we can support your journey.