Introduction au Docker multi-stage build

Docker multistage build

Pour développer des microservices en Golang ou tous autres langages, je vous propose de partager mon expérience pour profiter d’une fonctionnalité introduite par les équipes de Docker dans la version 17.05 : le multistage build, cette fontion offre la possibilité dans un seul et même fichier Dockerfile d’avoir 2 phases, le BUILD et le RUN !

Un exemple concret avec Golang

Pour illustrer le Docker multi-stage build, je vais utiliser un petit programme en Go, composé de 2 modules, qui affiche à l’écran du texte en couleur.

Code source de l’application Golang

L’application en Go est composée de 2 fichiers :

  • example.go (main)
  • frame/myframe.go (package)

Elle fait appel à une bibliothèque sur Github.

Voici le début du fichier example.go :

import (
        "fmt"

        "github.com/raphamorim/go-rainbow"
        . "github.com/user/app/frame"
)

Pour construire l’exécutable à partir des sources, dans mon Dockerfile, je place tous mes fichiers dans le répertoire github.com/user/app.

Dans un premier temps, je vais créer un fichier Dockerfile pour compiler cette petite application :

# Docker builder for Golang
FROM golang
LABEL maintainer "Vincent RABAH <vincent.rabah@gmail.com>"


WORKDIR /go/src/github.com/user/app
COPY . .
RUN set -x && \
    go get -d -v . && \
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

Avec la commande je vais compiler l’application :

docker build -t itwars/gobuilder .

Maintenant comment récupérer mon binaire ? Comment l’inclure dans une image Docker ? Docker multistage build permet d’enchainer directement les différentes étapes, depuis un seul et même fichier Dockerfile. Dans ce nouveau Dockerfile, j’ai 2 définitions, une pour le BUILD, une pour le RUN. La première étape construit le binaire et s’appelle : builder . La seconde étape est construite depuis : scratch (la plus petite image possible) et dans cette étape à l’avant dernière ligne on copie l’application est copiée depuis builder.

# Docker builder for Golang
FROM golang as builder
LABEL maintainer "Vincent RABAH <vincent.rabah@gmail.com>"


WORKDIR /go/src/github.com/user/app
COPY . .
RUN set -x && \
    go get -d -v . && \
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .


# Docker run Golang app
FROM scratch
LABEL maintainer "Vincent RABAH <vincent.rabah@gmail.com>"

WORKDIR /root/
COPY --from=builder /go/src/github.com/user/app .
CMD ["./app"]

Pour construire l’image depuis ce nouveau Dockerfile, j’utilise la commande :

docker build -t itwars/mygo .

Une fois le création de l’image Docker terminée, on va regarder les images avec la commande :

docker image ls

Le résultat :

REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
itwars/mygo             latest              281dd82b86a8        38 seconds ago      2.01MB
<none>                  <none>              95a40189be5e        40 seconds ago      731MB

On constate 2 choses :

  • l’image docker générée est très petite (~2Mo)
  • il y a un image résiduelle qui a servi à compiler le binaire, il faudra la supprimer manuellement

Maintenant le démarre mon *container Docker* :

docker run --rm itwars/mygo

Et voici le résultat :

docker multistage build Dockerfile

Si vous avez des questions, laissez-moi un commentaire !