Bake
đŹ Introduction
Bake est une commande qui permet d'aider Ă la gestion des configurations de nos builds.
LĂ oĂč buildx exposait les options de buildkit, bake nous permet de les orchestrer.
On a pu le voir avec la section sur buildx, on est vite amené à passer beaucoup d'instructions dans nos lignes de commande.
Pour limiter ces commandes à rallonge et garantir une homogénéité dans les builds réalisés, bake propose un fichier pour transcrire ces informations.
Tout comme buildx, bake sâinscrit lui aussi dans l'approche GitOps. Le fichier dĂ©crit le / les build(s), est versionnĂ© et rend les builds reproductibles.
Parmi les informations, on peut retrouver le nom du Dockerfile, le tag, si vous souhaitez utiliser le cache ou bien encore la liste des architectures cibles.
đ ContrĂŽle
Bake utilise buildx. Pour vérifier que la commande est disponible sur votre poste, faites :
Docker buildx est disponible
âčïž La version courante est la v0.30.1.
đ C'est parti
Documentation
NâhĂ©sitez pas Ă aller faire un tour sur la documentation : Documentation bake
đŁ Un premier fichier bake
Plusieurs formats de fichier peuvent ĂȘtre choisis dont HCL, JSON, et le YAML.
Le HCL est le format présent dans la documentation docker il est mis en avant car plus expressif et flexible.
La transcription est assez simple chaque élément de la d'une ligne de commande est repris et placé dans le fichier .hcl.
un exemple simple
Lors de la crĂ©ation, vous pouvez organiser votre fichier en blocs avec une cible plus ou moins large : des sections target et des sections group. Les target permettent de dĂ©crire des variables et des informations de build propres Ă un type dâimage. Les group permettent de regrouper plusieurs target.
group "default" {
targets = ["image1", "image2"]
}
target "image1" {
context = "./image1"
dockerfile = "image1.Dockerfile"
tags = ["myapp/image1:latest"]
}
target "image2" {
context = "./image2"
dockerfile = "image2.Dockerfile"
tags = ["myapp/image2:latest"]
}
Attention
Nous allons faire appel au registry local créé dans la partie buildx.
Question
Notre projet a une partie frontend et une partie backend. On va pouvoir construire un fichier pour bake.
- Créer à la racine du workspace un fichier
docker-bake.hcl - Transcrire les commandes
docker build -f frontend/Dockerfile -t localhost:5000/frontend:bake ./frontenddocker build -f backend/Dockerfile -t localhost:5000/backend:bake ./backend
- Lancer le build avec la commande
docker buildx bake
Le build est bien réalisé
[+] Building 29.2s (36/36) FINISHED docker-container:mybuilder
=> [internal] load local bake definitions 0.0s
=> => reading docker-bake.hcl 311B / 311B 0.0s
=> [frontend internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 618B 0.0s
=> WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 2) 0.0s
=> [backend internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 824B 0.0s
=> [backend internal] load metadata for docker.io/library/golang:1.25.0-alpine 0.9s
=> [backend internal] load metadata for docker.io/library/alpine:3 0.9s
=> [frontend internal] load metadata for docker.io/library/nginx:alpine 0.9s
=> [frontend internal] load metadata for docker.io/library/node:22-alpine 1.5s
=> [auth] library/node:pull token for registry-1.docker.io 0.0s
=> [auth] library/golang:pull token for registry-1.docker.io 0.0s
=> [auth] library/nginx:pull token for registry-1.docker.io 0.0s
=> [auth] library/alpine:pull token for registry-1.docker.io 0.0s
=> [backend internal] load .dockerignore 0.0s
=> => transferring context: 132B 0.0s
=> [backend build 1/7] FROM docker.io/library/golang:1.25.0-alpine@sha256:f18a072054848d87a8077455f0ac8a25886f23 0.1s
=> => resolve docker.io/library/golang:1.25.0-alpine@sha256:f18a072054848d87a8077455f0ac8a25886f2397f88bfdd222d6 0.1s
=> [backend stage-1 1/6] FROM docker.io/library/alpine:3@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed 0.1s
=> => resolve docker.io/library/alpine:3@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 0.1s
=> [backend internal] load build context 0.3s
=> => transferring context: 114.06kB 0.3s
=> CACHED [backend stage-1 2/6] WORKDIR /app 0.0s
=> CACHED [backend stage-1 3/6] RUN apk --no-cache add ca-certificates 0.0s
=> CACHED [backend build 2/7] WORKDIR /app 0.0s
=> CACHED [backend build 3/7] COPY go.mod go.sum ./ 0.0s
=> CACHED [backend build 4/7] RUN go mod download 0.0s
=> CACHED [backend build 5/7] COPY . . 0.0s
=> CACHED [backend build 6/7] RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/server 0.0s
=> CACHED [backend build 7/7] RUN mkdir -p /app/uploads 0.0s
=> CACHED [backend stage-1 4/6] COPY --from=build /app/server /app/ 0.0s
=> CACHED [backend stage-1 5/6] COPY --from=build /app/uploads /app/uploads 0.0s
=> CACHED [backend stage-1 6/6] RUN mkdir -p /app/uploads && chmod -R 755 /app/uploads 0.0s
=> [frontend internal] load .dockerignore 0.1s
=> => transferring context: 249B 0.0s
=> [frontend build 1/6] FROM docker.io/library/node:22-alpine@sha256:0340fa682d72068edf603c305bfbc10e23219fb0e40 7.1s
=> => resolve docker.io/library/node:22-alpine@sha256:0340fa682d72068edf603c305bfbc10e23219fb0e40df58d9ea4d6f33a 0.1s
=> => sha256:df42fde706147d8465782dcb26b2297f24a734079eadfcd035ceb21ee351e36e 448B / 448B 0.3s
=> => sha256:1e51518bad62c492c11e17439908faa5137221cf82279cc330086334b7d93c21 1.26MB / 1.26MB 0.5s
=> => sha256:d53378de7b14aa8ba0119f9adbe3097551dcc406ae16048777b5c24be77371e4 51.60MB / 51.60MB 4.9s
=> => extracting sha256:d53378de7b14aa8ba0119f9adbe3097551dcc406ae16048777b5c24be77371e4 1.7s
=> => extracting sha256:1e51518bad62c492c11e17439908faa5137221cf82279cc330086334b7d93c21 0.1s
=> => extracting sha256:df42fde706147d8465782dcb26b2297f24a734079eadfcd035ceb21ee351e36e 0.0s
=> CACHED [frontend stage-1 1/2] FROM docker.io/library/nginx:alpine@sha256:8491795299c8e739b7fcc6285d531d9812ce 0.1s
=> => resolve docker.io/library/nginx:alpine@sha256:8491795299c8e739b7fcc6285d531d9812ce2666e07bd3dd8db00020ad13 0.1s
=> [frontend internal] load build context 0.2s
=> => transferring context: 325.74kB 0.0s
=> [frontend build 2/6] WORKDIR /app 0.4s
=> [frontend build 3/6] COPY package*.json ./ 0.1s
=> [frontend build 4/6] RUN npm ci 11.5s
=> [frontend build 5/6] COPY . . 0.4s
=> [frontend build 6/6] RUN echo "VITE_API_URL=http://localhost:8080" >> .env && npm run build 7.0s
=> [frontend stage-1 2/2] COPY --from=build /app/build /usr/share/nginx/html 0.1s
La solution est lĂ ... —ïž
Comme bake utilise buildx, il n'y a pas d'image créé dans le contexte docker ni dans la registry.
Modifier le fichier hcl pour pousser les images dans la registry locale
Les images sont exportées
[+] Building 0.7s (34/34) FINISHED docker-container:mybuilder
=> [internal] load local bake definitions 0.0s
=> => reading docker-bake.hcl 377B / 377B 0.0s
=> [frontend internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 618B 0.0s
=> WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 2) 0.0s
=> [backend internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 909B 0.0s
=> [frontend internal] load metadata for docker.io/library/nginx:alpine 0.2s
=> [frontend internal] load metadata for docker.io/library/node:22-alpine 0.1s
=> [backend internal] load metadata for docker.io/library/alpine:3 0.2s
=> [backend internal] load metadata for docker.io/library/golang:1.25.0-alpine 0.2s
=> [frontend internal] load .dockerignore 0.0s
=> => transferring context: 249B 0.0s
=> [backend internal] load .dockerignore 0.0s
=> => transferring context: 132B 0.0s
=> [frontend stage-1 1/2] FROM docker.io/library/nginx:alpine@sha256:8491795299c8e739b7fcc6285d531d9812ce2666e07bd3dd8db00020ad132295 0.0s
=> => resolve docker.io/library/nginx:alpine@sha256:8491795299c8e739b7fcc6285d531d9812ce2666e07bd3dd8db00020ad132295 0.0s
=> [frontend build 1/6] FROM docker.io/library/node:22-alpine@sha256:0340fa682d72068edf603c305bfbc10e23219fb0e40df58d9ea4d6f33a9798bf 0.0s
=> => resolve docker.io/library/node:22-alpine@sha256:0340fa682d72068edf603c305bfbc10e23219fb0e40df58d9ea4d6f33a9798bf 0.0s
=> [frontend internal] load build context 0.0s
=> => transferring context: 1.18kB 0.0s
=> [backend build 1/7] FROM docker.io/library/golang:1.25.0-alpine@sha256:f18a072054848d87a8077455f0ac8a25886f2397f88bfdd222d6fafbb5bba440 0.0s
=> => resolve docker.io/library/golang:1.25.0-alpine@sha256:f18a072054848d87a8077455f0ac8a25886f2397f88bfdd222d6fafbb5bba440 0.0s
=> [backend stage-1 1/6] FROM docker.io/library/alpine:3@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 0.0s
=> => resolve docker.io/library/alpine:3@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 0.0s
=> [backend internal] load build context 0.1s
=> => transferring context: 77.28kB 0.1s
=> CACHED [frontend build 2/6] WORKDIR /app 0.0s
=> CACHED [frontend build 3/6] COPY package*.json ./ 0.0s
=> CACHED [frontend build 4/6] RUN npm ci 0.0s
=> CACHED [frontend build 5/6] COPY . . 0.0s
=> CACHED [frontend build 6/6] RUN echo "VITE_API_URL=http://localhost:8080" >> .env && npm run build 0.0s
=> CACHED [frontend stage-1 2/2] COPY --from=build /app/build /usr/share/nginx/html 0.0s
=> [frontend] exporting to image 0.3s
=> => exporting layers 0.1s
=> => exporting manifest sha256:7092fc32c7d0bc44f082bb672d48881fe9f8886ef353fb6c6619b352464a52f5 0.0s
=> => exporting config sha256:2604bd42fc2fb3d6fec9e360b34b86108d686fd049fbe745b1e6da1b75df05c3 0.0s
=> => exporting attestation manifest sha256:05a962f30b0c8e92032374d2756d2382d2bf8c21a467f025481a34999ebf7dc1 0.0s
=> => exporting manifest list sha256:39203ae7d685d38729cc69d094dd870d179f87b747e466f05bc26413e49d7cee 0.0s
=> => pushing layers 0.1s
=> => pushing manifest for localhost:5000/frontend:bake@sha256:39203ae7d685d38729cc69d094dd870d179f87b747e466f05bc26413e49d7cee 0.0s
=> CACHED [backend stage-1 2/6] WORKDIR /app 0.0s
=> CACHED [backend stage-1 3/6] RUN apk --no-cache add ca-certificates 0.0s
=> CACHED [backend build 2/7] WORKDIR /app 0.0s
=> CACHED [backend build 3/7] COPY go.mod go.sum ./ 0.0s
=> CACHED [backend build 4/7] RUN go mod download 0.0s
=> CACHED [backend build 5/7] COPY . . 0.0s
=> CACHED [backend build 6/7] RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o server ./cmd/server 0.0s
=> CACHED [backend build 7/7] RUN mkdir -p /app/uploads 0.0s
=> CACHED [backend stage-1 4/6] COPY --from=build /app/server /app/ 0.0s
=> CACHED [backend stage-1 5/6] COPY --from=build /app/uploads /app/uploads 0.0s
=> CACHED [backend stage-1 6/6] RUN mkdir -p /app/uploads && chmod -R 755 /app/uploads 0.0s
=> [backend] exporting to image 0.1s
=> => exporting layers 0.0s
=> => exporting manifest sha256:fdd8dd3fe59abd9c85218a40f7c7b0ee8afb9471a9ac73ef1bf7eb66fd4c98e6 0.0s
=> => exporting config sha256:2cfc5cbd878c4a93879e92069e8793738f11d64d629c722719efafd4244973a2 0.0s
=> => exporting attestation manifest sha256:a751b9d34fb2990abfe49802ca8ca042fecf5859ec1cd5d124ac465c461f81ae 0.0s
=> => exporting manifest list sha256:1aad1a3ee5032d5b73df3f67f2322c0ba68390fa1b4799a9d5e1b8136870315c 0.0s
=> => pushing layers 0.1s
=> => pushing manifest for localhost:5000/backend:bake@sha256:1aad1a3ee5032d5b73df3f67f2322c0ba68390fa1b4799a9d5e1b8136870315c 0.0s
La solution est lĂ ... —ïž
group "default" {
targets = ["frontend", "backend"]
}
target "frontend" {
context = "./frontend"
dockerfile = "Dockerfile"
tags = ["localhost:5000/frontend:bake"]
output = [{"type"="registry"}]
}
target "backend" {
context = "./backend"
dockerfile = "Dockerfile"
tags = ["localhost:5000/backend:bake"]
output = [{"type"="registry"}]
}
Info
Il est aussi possible d'utiliser l'option --push avec la commande docker buildx bake si on ne veut pas le mettre dans le fichier .hcl.
Documentation
NâhĂ©sitez pas Ă aller faire un tour sur la documentation : Documentation hĂ©ritage
Il est possible pour une target dâhĂ©riter des capacitĂ©s dâune autre target. Pour ce faire, vous devez dĂ©clarer inherits = ["nom-de-la-target"] dans la target oĂč vous souhaitez hĂ©riter des informations. Dans le cas oĂč vous avez plusieurs paramĂštres communs, vous pouvez regrouper ces informations dans une target commune, puis les utiliser via inherits.
target "_common" {
dockerfile = "Dockerfile"
}
target "target_1" {
inherits = ["_common"]
tags = ["target1:latest"]
}
Il est Ă©galement possible de choisir la / les target(s) que lâon souhaite exĂ©cuter.
đ Gestion des variables
Documentation
NâhĂ©sitez pas Ă aller faire un tour sur la documentation : Documentation bake variables
Les variables de Bake sont injectĂ©es au moment du build (par exemple lors de la crĂ©ation dâune image) et sont figĂ©es dans lâimage finale. Elles ne peuvent plus ĂȘtre modifiĂ©es au moment du dĂ©ploiement, ce qui favorise lâimmutabilitĂ© mais rĂ©duit la flexibilitĂ©.
Ajouter une section variable dans notre fichier
- Créer une section variable dans
docker-bake.hcl - Remplacer la version de l'image par une variable
VERSIONqui a la valeurbake-style - Utiliser
--printpour visualiser le fichier
Le build est bien réalisé
[+] Building 0.0s (1/1) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading docker-bake.hcl 438B / 438B 0.0s
{
"group": {
"default": {
"targets": [
"backend",
"frontend"
]
}
},
"target": {
"backend": {
"context": "backend",
"dockerfile": "Dockerfile",
"tags": [
"localhost:5000/backend:bake-style"
],
"output": [
{
"type": "registry"
}
]
},
"frontend": {
"context": "frontend",
"dockerfile": "Dockerfile",
"tags": [
"localhost:5000/frontend:bake-style"
],
"output": [
{
"type": "registry"
}
]
}
}
}
La solution est lĂ ... —ïž
group "default" {
targets = ["frontend", "backend"]
}
variable "VERSION" {
default = "bake-style"
}
target "frontend" {
context = "./frontend"
dockerfile = "Dockerfile"
tags = ["localhost:5000/frontend:${VERSION}"]
}
target "backend" {
context = "./backend"
dockerfile = "Dockerfile"
tags = ["localhost:5000/backend:${VERSION}"]
}
Imaginez maintenant que l'on souhaite externaliser la variable VERSION et lui donner une autre valeur que celle renseignée dans votre fichier bake ?
Question
- Ne modifier pas le fichier
docker-bake.hcl - Réaliser un export VERSION="v1"
- Utilisé --print pour visualiser le fichier
Le build est bien réalisé
[+] Building 0.0s (1/1) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading docker-bake.hcl 356B / 356B 0.0s
{
"group": {
"default": {
"targets": [
"backend",
"frontend"
]
}
},
"target": {
"backend": {
"context": "backend",
"dockerfile": "Dockerfile",
"tags": [
"localhost:5000/backend:v1"
]
},
"frontend": {
"context": "frontend",
"dockerfile": "Dockerfile",
"tags": [
"localhost:5000/frontend:v1"
]
}
}
}
Documentation
NâhĂ©sitez pas Ă aller faire un tour sur la documentation :
Mais bake propose d'autres fonctionnalités utiles :
- la validation des variables
- les fonctions pour construire dynamiquement des valeurs
Construire dynamique le nom de l'image
Modifier le fichier docker-bake.hcl pour que :
- il y ait une validation que la variable VERSION soit au format
vX.Y - l'URL de la registry soit une variable
- il y ait une fonction qui construise le nom de l'image avec :
- le nom de la registry et la version via des variables
- le nom de l'image via un paramĂštre de la fonction
export VERSION=1.0
$ docker buildx bake --print
[+] Building 0.0s (1/1) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading docker-bake.hcl 649B / 649B 0.0s
docker-bake.hcl:9
--------------------
7 | validation {
8 | # VAR and the regex match must be identical:
9 | >>> condition = VERSION == regex("v[0-9]\\.[0-9]", VERSION)
10 | error_message = "The variable 'VERSION' can only contain letters and numbers."
11 | }
--------------------
ERROR: docker-bake.hcl:9,28-34: Error in function call; Call to function "regex" failed: pattern did not match any part of the given string.
export VERSION=v1.1
{
"group": {
"default": {
"targets": [
"backend",
"frontend"
]
}
},
"target": {
"backend": {
"context": "backend",
"dockerfile": "Dockerfile",
"tags": [
"localhost:5000/backend:v1.1"
],
"output": [
{
"type": "registry"
}
]
},
"frontend": {
"context": "frontend",
"dockerfile": "Dockerfile",
"tags": [
"localhost:5000/frontend:v1.1"
],
"output": [
{
"type": "registry"
}
]
}
}
}
La solution est lĂ ... —ïž
group "default" {
targets = ["frontend", "backend"]
}
variable "REPO" {
default = "localhost:5000"
}
variable "VERSION" {
default = "bake-style"
validation {
# VAR and the regex match must be identical:
condition = VERSION == regex("v[0-9]\\.[0-9]", VERSION)
error_message = "The variable 'VERSION' can only contain letters and numbers."
}
}
function "ImageCompleteName" {
params = [name]
result = ["${REPO}/${name}:${VERSION}"]
}
target "frontend" {
context = "./frontend"
dockerfile = "Dockerfile"
tags = ImageCompleteName("frontend")
output = [{ type = "registry" }]
}
target "backend" {
context = "./backend"
dockerfile = "Dockerfile"
tags = ImageCompleteName("backend")
output = [{ type = "registry" }]
}
đ° Gestion des donnĂ©es sensibles
Docker Buildx permet de transmettre des secrets au build sans les inclure dans lâimage finale. Pour cela, on utilise --secret avec Buildx, mais il est Ă©galement possible de passer ces informations via Bake. Cela renforce la sĂ©curitĂ© en Ă©vitant que des donnĂ©es sensibles soient exposĂ©es dans les couches Docker ou dans le registre des images.
Question
Dans un dossier test-secret et en utilisant le Dockerfile ci-dessous, créez un fichier .hcl et un secret.
- Créez le fichier
mon_secret.txt - Créez le fichier
docker-bake.hcl - RĂ©alisez le build de lâimage avec bake.
- Ajoutez lâoption
--progress=plainqui permet de numéroter les étapes du build et la progression du build.
Le build est bien réalisé
Dans l'étape #8, on voit bien que le fichier et son contenu sont "montés" lors du build
#0 building with "mybuilder" instance using docker-container driver
#1 [internal] load local bake definitions
#1 reading docker-bake.hcl 256B / 256B done
#1 DONE 0.0s
#2 [internal] load build definition from Dockerfile
#2 transferring dockerfile: 223B done
#2 DONE 0.0s
#3 resolve image config for docker-image://docker.io/docker/dockerfile:1.4
#3 DONE 0.2s
#4 docker-image://docker.io/docker/dockerfile:1.4@sha256:9ba7531bd80fb0a858632727cf7a112fbfd19b17e94c4e84ced81e24ef1a0dbc
#4 resolve docker.io/docker/dockerfile:1.4@sha256:9ba7531bd80fb0a858632727cf7a112fbfd19b17e94c4e84ced81e24ef1a0dbc 0.0s done
#4 CACHED
#5 [internal] load .dockerignore
#5 transferring context: 2B done
#5 DONE 0.0s
#6 [internal] load metadata for docker.io/library/alpine:3.18
#6 DONE 0.2s
#7 [stage-0 1/2] FROM docker.io/library/alpine:3.18@sha256:de0eb0b3f2a47ba1eb89389859a9bd88b28e82f5826b6969ad604979713c2d4f
#7 resolve docker.io/library/alpine:3.18@sha256:de0eb0b3f2a47ba1eb89389859a9bd88b28e82f5826b6969ad604979713c2d4f 0.0s done
#7 CACHED
#8 [stage-0 2/2] RUN --mount=type=secret,id=MY_SECRET sh -c 'echo "SECRET PRESENT:" && ls /run/secrets && cat /run/secrets/MY_SECRET'
#8 0.109 SECRET PRESENT:
#8 0.110 MY_SECRET
#8 0.110 TEST_SECRET_OK
#8 DONE 0.1s
#9 exporting to docker image format
#9 exporting layers
#9 exporting layers 0.2s done
#9 exporting manifest sha256:8254c8f996db660add9118cb2fee6753eb446b658f32af73e45bcd1660adac8b 0.0s done
#9 exporting config sha256:12d77a4f07f51b91fa8a86cd647eef0afa796e73d91f74a97536261fc73f91ea 0.0s done
#9 sending tarball
#9 sending tarball 0.1s done
#9 DONE 0.3s
#10 importing to docker
#10 loading layer ac93d62670d8 113B / 113B 0.1s done
#10 DONE 0.1s
La solution est lĂ ... —ïž
Vérifier que vous ne voyez pas la valeur du secret
Aucune fois le secret n'est révélé
Pour SSH
Buildx permet Ă©galement dâutiliser des clĂ©s SSH pour accĂ©der Ă des dĂ©pĂŽts privĂ©s ou Ă des serveurs pendant le build des images.
Bake sâinscrit dans lâorientation CI/CD et GitOps proposĂ©e par Docker avec Buildx. Il la complĂšte en offrant un moyen de garantir la reproductibilitĂ© des builds grĂące Ă un systĂšme de configuration. Il apporte Ă©galement des avancĂ©es en matiĂšre de sĂ©curitĂ©, notamment via la gestion des secrets, du SBOM et des attestations de provenance.