Einleitung
Mit Docker kann man leichtgewichtige Images mit einer Applikation erstellen um diese schnellst möglich von A nach B zu bringen.
Ein solches Image sollte im Idealfall wenige Megabytes gross sein und schnellst möglich starten. Genau für dies eignet sich Docker.
Doch Vorsichtig, auch mit Docker hat man einiges zu Beachten, den aus einer Applikation welche nicht grösser ist als wenige MB, kann schnell ein Image entstehen mit mehreren hundert MB bis hin zu über 1Gb
Ziele
Am Ende dieses Atickels:
- Verstehst du die Basics für den Einsatz von Multistage builds
- Weisst du wie du das nächste Dockerfile schreibst für deine Anwendung(en)
Voraussetzungen
Als erstes benötigst du:
Du findest den ganzen Code in dem go-multistage-builder Repository
Erstellen des ersten Dockerfiles (Schlechte Variante)
Erstellt einen Projektordner und in diesem eine .Dockerfile
. In meine fall nenne ich diese .Dockerfile_Bad
.
FROM golang:1.14.2 WORKDIR /app COPY . . RUN go mod download RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o main . EXPOSE 8080 ENTRYPOINT ["./main"]
Ich gehe hier nicht auf die einzelnen Commands ein da diese Steps selbsterklärend sind und zum anderen möchte ich nicht das Rad neu erfinden und empfehle denjenigen, die nicht alle ausdrücke verstehen, diese auf docs.Docker selbst nachzuschlagen.
Damit wir dies nutzen können benötigt es auch noch etwas an Code, den wir dann auch builden und starten können. Erstellt dazu eine main.go
und fügt folgendes ein
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") }
Dies ist nur eine Simple Go Anwendung mit dem Einsatz des Gin Frameworks welches für Web-Anwendungen sehr verbreitet ist.
Öffnet nun ein Terminal und gebt folgende commands ein:
go mod init go mod tidy
Diese erstellen euch 2 neue Files go.mod
und go.sum
. dies ist das selbe wie bei npm die package.json
und package.lock
.
Nun habt ihr alles um euer erstes Image zu erstellen. Dies könnt ihr mit folgender Zeile erstellen
docker build -f .Dockerfile_Bad -t bad_go_image .
Heraus kommt dabei ein Dockerimage mit dem Namen bad_go_image
und dem tag latest
. Nun schauen wir und mal die Grösse von diesem Image an.

Wie ihr Seht, das Image ist „nur“ 625MB gross. Würde man dies mit einer VM erstellen und z.b. ein Debian oder Ubuntu Betriebsystem benutzen, so wären es mehrere GB die benötigt werden. Aber, dies darf man auch nicht ganz so vergleichen, da die 2 unterschiedliche Aufbauarten sind. mehr dazu könnt ihr hier nachlesen: Docker vs. VM
Erstellen des zweiten Dockerfiles (bessere Variante)
Erstellt in dem selben Ordner ein zweites .Dockerfile
, bei mir .Dockerfile_Good
mit folgendem Inhalt.
# Obtain certs for final stage FROM alpine:3.11.5 as authority RUN mkdir /user && \ echo 'appuser:x:1000:1000:appuser:/:' > /user/passwd && \ echo 'appgroup:x:1000:' > /user/group RUN apk --no-cache add ca-certificates # Build app binary for final stage FROM golang:1.14.2-alpine3.11 AS builder WORKDIR /app COPY . . RUN go mod download RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o /main . # Final stage FROM scratch COPY --from=authority /user/group /user/passwd /etc/ COPY --from=authority /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /main ./ USER appuser:appgroup EXPOSE 8080 ENTRYPOINT ["./main"]
Hier werden im Gegensatz zum anderen Dockerfile gleich 3 Images beschrieben.
Zum ersten das Image welches Zertifikate erstellt, zum zweiten das builder Image welches euren Code kopiert die GO-Module herunterlädt und die Anwendung erstellt. Als letztes wird ein etwas „spezielleres“ Image genommen FROM scratch
was soviel bedeutet wie „von Grund auf“. Sprich wir nehmen ein Blankes Image beidem noch niemand etwas vorinstalliert hat und es daher sehr klein ist.
Speziel zusehen ist, das wir in diesem Image nichts ausführen wie etwa apt update
oder apt install
etc. Sondern wir Kopieren uns die Daten von den vorhergehenden Images in das Blanke Image hinein, Öffnen den Port 8080 und als Entrypoint starten wir die Applikation.
Nun habt ihr alles um das zweite Image zu erstellen. Dies könnt ihr mit folgender Zeile erstellen
docker build -f .Dockerfile_Good -t good_go_image .
Heraus kommt dabei ein Dockerimage mit dem Namen good_go_image
und dem tag latest
. Nun schauen wir und mal die Grösse von diesem Image an.

Dieses Image ist mit 15.24MB „etwas“ kleiner 98.4% kleiner um genau zu sein. Dies bedeutet, ihr müsst nur noch 15 MB Pullen oder Pushen, wen ihr diese App/Image in einem Cluster Betreibt benötigt ihr Weniger Speicher, Wen ihr diese auf 100 Pods ausrollen wollt, seit ihr wesentlich schneller da ihr nur 1’500MB verschiebt und nicht wie mit dem ersten Image 977’000MB
Hier nochmals Beide Images zusammen

Diese Art des Builden, kann man auch für andere Programmiersprachen benutzen wie z.b. Node C C++ uvm.
Den gesamten Code Welche ich hier Gepostet habe, findet ihr auch im Git
Dieser Beitrag hat 2 Kommentare
Pretty! This has been an extremely wonderful post. Thank you for supplying this info. Wayne Comiso
Pingback: Multistage Dockerbuild with Docker-Compose » Grolimund Solutions