Retour au cours

infra / docker

BuildKit avancé

Leçon 181 exercice

Explication

Ce que vous allez apprendre

  • Comprendre ce qu'apporte BuildKit par rapport au moteur de build historique de Docker
  • Utiliser un cache mount pour persister le cache d'un gestionnaire de paquets entre plusieurs builds
  • Comprendre pourquoi la ligne # syntax=docker/dockerfile:1 est nécessaire pour les fonctionnalités avancées
  • Utiliser buildx pour construire pour plusieurs architectures ou déléguer à une machine distante
  • Partager un cache de build entre plusieurs runners CI via un registre distant

Dans quel contexte ?

Une équipe remarque que chaque build CI réinstalle intégralement toutes les dépendances Python, même quand seul un fichier de code source a changé — chaque exécution démarre sur un runner "propre", sans aucun historique de build précédent. En ajoutant un cache mount (--mount=type=cache,target=/root/.cache/pip) combiné à un cache distant exporté vers le registre, l'installation des dépendances redevient quasi instantanée d'un build à l'autre, même sur des machines différentes.

Un moteur de build repensé

Le système de build historique de Docker exécutait les instructions d'un Dockerfile de façon séquentielle, une par une. BuildKit est un moteur de build de nouvelle génération, activé par défaut depuis plusieurs versions de Docker, qui peut analyser les dépendances entre les étapes et en paralléliser certaines, tout en apportant des fonctionnalités que l'ancien système ne permettait simplement pas.

Type de mountRôlePersiste dans l'image finale ?
type=cacheCache de gestionnaire de paquets entre buildsNon
type=bindMonte un fichier/dossier du contexteNon
type=secretInjecte un secret au buildNon

Prérequis

Cette leçon suppose que tu es à l'aise avec les instructions de base d'un Dockerfile et le principe du cache de build (leçon optimisation d'images).

Le cache mount : un problème concret enfin résolu

On a vu dans la leçon sur l'optimisation qu'une installation de dépendances (comme pip install) recrée entièrement son travail dès que le cache de couche est invalidé. Le cache mount de BuildKit va plus loin : il conserve un espace de cache persistant (par exemple le cache interne de pip) ENTRE plusieurs builds différents, sans jamais l'inclure dans l'image finale. Concrètement, même après un changement de code qui invalide le cache de couche classique, l'installation des dépendances peut rester rapide car les paquets déjà téléchargés sont réutilisés depuis ce cache persistant.

Pourquoi la ligne "syntax" en haut du Dockerfile

Cette ligne particulière indique explicitement à Docker quelle version du langage de Dockerfile utiliser. Sans elle, certaines fonctionnalités avancées de BuildKit (comme les montages --mount ou les heredocs) ne sont tout simplement pas reconnues, même si BuildKit est bien actif — un piège fréquent pour qui copie un Dockerfile avancé sans cette ligne.

buildx : piloter BuildKit au-delà d'une seule machine

buildx est l'extension en ligne de commande qui permet d'exploiter pleinement BuildKit : construire pour plusieurs architectures processeur en une seule commande, déléguer un build à une machine distante plus puissante, ou partager un cache de build entre plusieurs machines via un registre distant — un point crucial en intégration continue, où chaque exécution démarre en général sur une machine sans aucun historique local.

Commandes & code

BuildKit avancé

BuildKit est le moteur de build moderne de Docker : parallélisation, cache distant, montages au build, frontends alternatifs.

dockerfile
# syntax=docker/dockerfile:1
# La ligne "syntax" ci-dessus active la dernière version du frontend BuildKit — sans elle,
# certaines fonctionnalités (--mount, heredocs...) ne sont pas reconnues.

FROM python:3.12-slim AS base
WORKDIR /app

# --- Cache mount : garde un cache de gestionnaire de paquets ENTRE les builds, sans l'embarquer ---
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt
# Contrairement à un simple COPY+RUN, ce cache persiste même après un "docker build --no-cache"
# sur le reste de l'image, et n'ajoute RIEN au poids final de la couche.

# --- Bind mount : monte un fichier/dossier du contexte SANS le copier dans la couche ---
RUN --mount=type=bind,source=./scripts,target=/scripts \
    /scripts/verifier-licences.sh

# --- Secret mount : déjà vu dans la leçon variables/secrets, rappel de la syntaxe complète ---
RUN --mount=type=secret,id=npm_token,target=/run/secrets/npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) npm install --silent

# --- Heredoc natif BuildKit : plus lisible qu'enchaîner des RUN avec && ---
RUN <<EOF
set -e
apt-get update
apt-get install -y --no-install-recommends curl git
rm -rf /var/lib/apt/lists/*
EOF

COPY <<EOF /app/config.json
{"env": "production", "debug": false}
EOF
bash
# --- Construire avec BuildKit explicitement (activé par défaut depuis Docker 23+) ---
DOCKER_BUILDKIT=1 docker build -t mon-api:1.0 .
docker build --secret id=npm_token,src=./token.txt -t mon-api:1.0 .

# --- buildx : CLI plugin qui pilote BuildKit, gère les builders multiples ---
docker buildx ls                             # builders disponibles (driver docker, docker-container...)
docker buildx create --name multi --driver docker-container --use
docker buildx inspect --bootstrap              # démarre le builder et affiche ses capacités

# --- Cache distant : partagé entre machines/CI, contrairement au cache local par défaut ---
docker buildx build \
  --cache-to type=registry,ref=registry.exemple.com/mon-api:cache,mode=max \
  --cache-from type=registry,ref=registry.exemple.com/mon-api:cache \
  -t mon-api:1.0 --push .
# mode=max exporte TOUTES les couches intermédiaires (pas seulement finales) -> cache plus efficace

# --- Cache local exporté en dossier (utile en CI sans registre de cache dédié) ---
docker buildx build \
  --cache-to type=local,dest=/tmp/buildx-cache,mode=max \
  --cache-from type=local,src=/tmp/buildx-cache \
  -t mon-api:1.0 .

# --- Debug d'un build BuildKit ---
docker build --progress=plain --no-cache -t mon-api:1.0 .    # logs complets, sans troncature
docker buildx build --load -t mon-api:debug .                 # --load = charge le résultat dans le daemon local

# --- Builder distant (build sur une machine plus puissante, ex: ARM natif) ---
docker buildx create --name arm-builder --driver docker-container \
  ssh://builder@build-arm.exemple.com
docker buildx build --builder arm-builder --platform linux/arm64 -t mon-api:arm .

Résumé

  • --mount=type=cache persiste un cache de gestionnaire de paquets entre builds sans alourdir l'image, contrairement à un COPY+RUN classique.
  • Le cache distant (--cache-to/from type=registry, mode=max) est indispensable dès que les builds tournent sur des runners CI éphémères.
  • docker buildx create --driver docker-container permet un builder dédié (multi-arch, machine distante), séparé du démon Docker local.

Exercices pratiques

1 disponible
1

Mission : accélérer un build qui réinstalle tout à chaque exécution CI

Objectif : Mettre en place un cache mount BuildKit persistant et corriger un Dockerfile qui n'active pas les fonctionnalités avancées.

Contexte

Chaque build CI de mon-api réinstalle intégralement toutes les dépendances Python, même quand seul le code source a changé. Un développeur a copié un Dockerfile avec RUN --mount=type=cache depuis un exemple, mais le build échoue avec une erreur de syntaxe inconnue.

Résoudre l’exercice →