IT-Knowledgebase
de Programmieren Sprachen Golang

Golang - Einfaches Programm in docker packen

Um ein einfaches Programm, welches in Golang geschrieben wurde in einen Docker Container zu verpacken brauchen wir natürlich zuerst ein Docker-File. Einfach wird hier verbunden mit Programm die nur Golang-Code oder die Standard Bibliothek benutzen und kein CGO eingebunden haben. Hier kann man sich überlegen ob man ein vorher gebautes Binary in ein Container verpackt oder ob man das auch schon innerhalb von Docker tun möchte.

Unser Programm

Um ein Beispiel für ein Programm zu haben, benutzen wir hier folgendes Beispiel:

1package main
2
3import "fmt"
4
5func main() {
6	fmt.Println("HELLO WORLD")
7}

die main.go Datei. ist natürlich auch auf alles andere Anwendbar

1module github.com/dmnkrobert
2
3go 1.23.3

unsere go.mod Datei

In dem gleichen Ordner packen wir nun eine Datei, die wir Dockerfile nennen.

Vorher gebautes Binary

Ein vorher gebautes Binary muss vorher ohne CGO gebaut werden. Wir bauen also das Binary wie folgt:

1CGO_ENABLED=0 GOOS=linux go build  . 

Danach fangen wir mit einem Image an, welches von scratch ist. Scratch ist dabei ein besonderes leeres Image. Vorteil dabei ist, dass unser Image sehr klein und keine Angriffsfläche bietet. Nachteil ist, dass keine Programme zum debuggen vorhanden sind, da es sich nicht um eine Distribution handelt und somit kein ls, dir oder andere Programme in der Art zur Verfügung stehen.

1FROM scratch

Danach setze ich gerne das Verzeichnis vom Image. Da es sich hier um Golang handelt bietet es sich an /app zu nehmen (ist das Verzeichnis vom Golang-Image)

1FROM scratch
2
3WORKDIR /app

Nun kopieren wir unser fertiges Binary in das Image und setzen den Entrypoint auf unser Verzeichnis und dem Namen unseres Binary:

1FROM scratch
2
3WORKDIR /app
4COPY ./example ./
5
6ENTRYPOINT [ "./example" ]

Damit wären wir fertig und kriegen mit folgendem Build Befehl nachfolgenden Output

 1➜  docker build -t test . 
 2[+] Building 0.2s (6/6) FINISHED                                                                     docker:desktop-linux
 3 => [internal] load build definition from Dockerfile                                                                 0.0s
 4 => => transferring dockerfile: 112B                                                                                 0.0s
 5 => [internal] load .dockerignore                                                                                    0.0s
 6 => => transferring context: 2B                                                                                      0.0s
 7 => [internal] load build context                                                                                    0.0s
 8 => => transferring context: 2.16MB                                                                                  0.0s
 9 => CACHED [1/2] WORKDIR /app                                                                                        0.0s
10 => [2/2] COPY ./example ./                                                                                          0.0s
11 => exporting to image                                                                                               0.1s
12 => => exporting layers                                                                                              0.1s
13 => => exporting manifest sha256:b7f9d4a1faacd398c0c404d08305f5da164d214fc3784ca8978a15b3d750e95b                    0.0s
14 => => exporting config sha256:f21a16320b5531ee0637175733603e4ac1475503fed281e34b8802be16ac2724                      0.0s
15 => => exporting attestation manifest sha256:f92bb369864e7e204d6709625b1b6e2df07324d5c594a8cba07cf8d89e5051b1        0.0s
16 => => exporting manifest list sha256:59308fde0fabf4183a8463d049c0c92b360759eb165aa9f099867cddab140e03               0.0s
17 => => naming to docker.io/library/test:latest                                                                       0.0s
18 => => unpacking to docker.io/library/test:latest                                                                    0.0s
19
20What's next:
21    View a summary of image vulnerabilities and recommendations → docker scout quickview 

binary ebenfalls im docker bauen

Einfacher und für ein ordentliches bauen sorgt das zur Verfügung stellen in docker. Hierbei bedienen wir uns ein multi-stage build, welches in neueren Docker-Versionen mit dem build kit zur Verfügung steht.

Dafür definieren wir uns zuerst eine Stage, die unser Binary baut.

1FROM golang:1.23 AS builder

wir fangen mit einer Stage an, die golang beinhaltet und vergeben einen Alias Namens builder

Hier fungiert der Alias als Name, den wir verwenden können um die Stage nachher zu referenzieren. Danach kopieren wir alle Abhängigkeiten und unser go.mod und go.sum Datei in den Container hinein.

 1FROM golang:1.23 AS builder
 2
 3WORKDIR /app
 4COPY go.mod go.sum ./
 5
 6# kopiert alle Abhängigkeiten
 7COPY ./ ./
 8
 9# kopiert alle Abhängigkeiten
10# Alternativ auch direkt mit dem Verzeichnisnamen
11# COPY models ./models
12# COPY internal ./internal

Nun downloaden wir uns die Abhängigkeiten, die in der go.mod Datei definiert wurden und fangen danach einfach an unser Binary zu bauen.

 1FROM golang:1.23 AS builder
 2
 3WORKDIR /app
 4COPY go.mod go.sum ./
 5
 6# kopiert alle Abhängigkeiten
 7COPY ./ ./
 8
 9RUN go mod download
10
11RUN CGO_ENABLED=0 GOOS=linux go build -o example .

Nun setzen wir unsere finale Stage darunter, welche wiederum von Scratch ist und kopieren uns von unserer ersten Stage die Daten rein, die wir brauchen.

1FROM scratch
2
3WORKDIR /app
4COPY --from=builder /app/example /app/example
5
6ENTRYPOINT [ "./example" ]

Demnach sieht unser gesamtes Dockerfile so aus:

 1FROM golang:1.23 AS builder
 2
 3WORKDIR /app
 4COPY go.mod go.sum ./
 5
 6# kopiert alle Abhängigkeiten
 7COPY ./ ./
 8
 9RUN go mod download
10
11RUN CGO_ENABLED=0 GOOS=linux go build -o example .
12
13
14FROM scratch
15
16WORKDIR /app
17COPY --from=builder /app/example /app/example
18
19ENTRYPOINT [ "./example" ]

bedenke, dass dieser Build in unserem Beispiel ggf. fehlscghlägt, da wir keine go.sum Datei haben. Wenn wir keine Abhängigkeiten haben können wir diese Zeile einfach komplett löschen.

Der output

Startet man nun den Container sieht man folgenden Output unserer Binary:

1➜ docker run -it test
2HELLO WORLD