## Introduction

Un laboratoire local BuildKit vous offre un espace sûr pour tester vos builds Docker avant qu'ils n'atteignent la CI ou la production. Au lieu de résoudre un build défaillant dans un pipeline partagé, vous pouvez le reproduire sur votre propre machine, inspecter le cache, tester des modifications de configuration et vérifier le résultat. Ce guide s'adresse aux développeurs, consultants DevOps et équipes techniques de startups qui ont besoin d'un flux de travail pratique et reproductible.

L'objectif est la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, utiliser des espaces réservés plutôt que des secrets, vérifier le résultat et documenter la reprise si l'état attendu n'est pas atteint. Chaque section inclut des commandes concrètes, les sorties attendues et les signaux d'échec.

## Inventaire des versions et de l'environnement

Commencez par confirmer ce qui est installé et le mode utilisé par Docker. BuildKit peut fonctionner comme builder par défaut ou comme instance de builder distincte. Connaître la version et la topologie vous aide à reproduire le comportement et à éviter les surprises lors des changements de machine.

Vérifiez la version de Docker et l'état de BuildKit :

```bash
docker version --format '{{.Server.Version}}'
```

Exemple de sortie attendue : `24.0.7`. Si la version du serveur est absente ou très ancienne, mettez d'abord Docker à niveau.

Vérifiez si BuildKit est activé pour le builder par défaut :

```bash
docker buildx version
```

Exemple de sortie attendue : `github.com/docker/buildx v0.12.1`. Si vous voyez `docker: 'buildx' is not a docker command`, installez le plugin buildx.

Listez les builders disponibles et identifiez le builder actuel :

```bash
docker buildx ls
```

Cherchez la ligne avec `*` dans la colonne `CURRENT`. Exemple :

```text
NAME/NODE       DRIVER/ENDPOINT             STATUS  BUILDKIT PLATFORMS
default *       docker                                        
  default       default                     running 23.0.6   linux/amd64, linux/arm64
```

Si le builder par défaut n'utilise pas le driver `docker-container`, vous risquez de passer à côté de fonctionnalités avancées comme les builds multi-plateformes. Pour un laboratoire local, le driver `docker` par défaut convient pour un travail mono-plateforme, mais le driver `docker-container` offre plus d'isolation et de contrôle.

**Prérequis :**
- Docker Engine 20.10 ou version ultérieure (recommandé 24.x).
- Plugin Buildx version 0.10 ou ultérieure.
- Au moins 4 Go de RAM pour les builds multi-étapes ; davantage si vous utilisez des montages de cache.

**Observation avant intervention :** Exécutez `docker system df` pour voir l'utilisation du disque par les images, les conteneurs et le cache de build. Notez les valeurs. Cela vous indique si votre cache de build consomme un espace inattendu et fournit une base pour un nettoyage ultérieur.

**Plus petit changement justifié :** Si BuildKit n'est pas actif, définissez la variable d'environnement dans votre session shell plutôt que de modifier la configuration globale :

```bash
export DOCKER_BUILDKIT=1
```

Cela limite le changement à votre session de laboratoire actuelle. Vérifiez avec :

```bash
docker buildx debug info 2>&1 | head -20
```

Vous devriez voir des lignes incluant `BuildKit version` et `Builder: default`.

**Vérification de l'emplacement des données :** Pour tout service qui écrit des données persistantes, confirmez où les fichiers sont stockés avant de modifier les conteneurs. Comparez un volume nommé :

```yaml
# fragment docker-compose.yml
services:
  app:
    image: myapp:latest
    volumes:
      - app_data:/var/lib/app
volumes:
  app_data:
```

avec un montage bind :

```yaml
volumes:
  - ./data:/var/lib/app
```

Un volume nommé est géré par Docker et survit à la recréation du conteneur. Un montage bind mappe directement un répertoire hôte et est pratique pour le développement, mais peut causer des problèmes de permissions ou de portabilité si le même chemin n'existe pas sur une autre machine.

**Test de redémarrage :** Pour vérifier la persistance, créez un conteneur qui écrit un fichier, puis supprimez-le et recréez-le :

```bash
# Premier lancement
docker run -d --name datatest --mount type=volume,source=app_data,target=/var/lib/app alpine sh -c 'echo hello > /var/lib/app/test.txt; sleep 1000'
# Supprimez après quelques secondes
docker rm -f datatest
# Recréez avec le même volume
docker run -d --name datatest2 --mount type=volume,source=app_data,target=/var/lib/app alpine sleep 1000
# Vérifiez que le fichier existe
docker exec datatest2 cat /var/lib/app/test.txt
```

Sortie attendue : `hello`. Si le fichier est absent, le conteneur d'origine écrivait probablement dans sa propre couche inscriptible, pas dans le volume.

## Chemin de configuration sûr

Les modifications de configuration doivent être délimitées, documentées et réversibles. Pour un laboratoire BuildKit, les ajustements les plus courants sont les options du driver du builder, les miroirs de registre et les options `cache-to`/`cache-from` pour la mise en cache distante.

**Observation en lecture seule d'abord :** Vérifiez la configuration actuelle du builder :

```bash
docker buildx inspect --bootstrap
```

Exemple de sortie :

```text
Name:   default
Driver: docker
Nodes:
Name:      default
Endpoint:  docker
Status:    running
Buildkit:  v0.12.3
Platforms: linux/amd64, linux/arm64
```

Notez le driver et la version de BuildKit. Si le driver est `docker`, vous ne pouvez pas utiliser directement `--cache-to=type=registry` avec le builder par défaut ; vous avez besoin d'un builder `docker-container`.

**Exemple de changement délimité :** Créez un nouveau builder utilisant le driver `docker-container` pour tester la mise en cache distante :

```bash
docker buildx create --name labbuilder --driver docker-container --use
```

Vérifiez que le builder actuel a changé :

```bash
docker buildx ls
```

Recherchez `*` à côté de `labbuilder`. Si vous faites une erreur, revenez en arrière avec :

```bash
docker buildx use default
```

**Configuration du miroir de registre :** Si vous avez besoin de tirer des images de base plus rapidement, ajoutez un miroir dans la configuration du démon Docker. Sur Linux, éditez `/etc/docker/daemon.json` :

```json
{
  "registry-mirrors": ["https://mirror.example.com"]
}
```

Puis redémarrez Docker :

```bash
sudo systemctl restart docker
```

**Règle de sécurité :** Ne mettez jamais d'identifiants dans un Dockerfile ou un argument de build. Utilisez les secrets BuildKit :

```dockerfile
# Dockerfile
# syntax=docker/dockerfile:1
FROM alpine
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
```

Passez le secret au moment du build :

```bash
echo "my-api-key" > secret.txt
docker build --secret id=mysecret,src=secret.txt -t myapp:secrettest .
```

Le secret n'est disponible que pendant l'instruction `RUN` et n'est pas stocké dans les couches de l'image. Après le build, supprimez le fichier local `secret.txt`.

**Propriétaire de la configuration et cadence de revue :** Attribuez à une personne la responsabilité des modifications de configuration de build. Par exemple, « Priya Shah, responsable ingénierie » examine et approuve les modifications chaque lundi lors de la réunion d'équipe. Chaque modification doit inclure un plan de retour en arrière.

## Vérification et diagnostics

Après toute modification, vérifiez que BuildKit fonctionne comme prévu. Utilisez un petit projet d'exemple pour tester les fonctionnalités clés : builds multi-étapes, montages de cache et arguments de build.

**Structure du projet d'exemple :**

```text
lab/
├── Dockerfile
├── app.py
└── requirements.txt
```

`app.py` :

```python
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello BuildKit'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
```

`requirements.txt` :

```text
flask==2.3.2
```

`Dockerfile` :

```dockerfile
# syntax=docker/dockerfile:1
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
```

**Build avec BuildKit :**

```bash
docker build -t labapp:test .
```

Observez la sortie. Avec BuildKit, vous voyez des étapes comme `#1 [internal] load build definition`, `#2 [internal] load .dockerignore`, et `#3 [builder 1/3] FROM docker.io/library/python:3.11-slim`. Le build se termine par `writing image sha256:...` et `naming to docker.io/library/labapp:test`.

**Vérifiez le cache de build :** Relancez le build immédiatement :

```bash
docker build -t labapp:test .
```

La sortie attendue inclut `CACHED` pour chaque étape. Si une étape affiche `RUN` au lieu de `CACHED`, le cache a été invalidé. Examinez pourquoi : soit un fichier a changé, soit une commande utilise une valeur non déterministe comme `RUN date`.

**Exécutez et testez le conteneur :**

```bash
docker run -d -p 5000:5000 --name labapp_container labapp:test
curl http://localhost:5000
```

Attendu : `Hello BuildKit`. Vérifiez les journaux :

```bash
docker logs labapp_container --tail 20
```

Recherchez la ligne de démarrage de Flask `Running on http://0.0.0.0:5000`. Si le conteneur se termine immédiatement, diagnostiquez avec `docker inspect labapp_container --format '{{.State.ExitCode}} {{.State.Error}}'`.

**Équivalent Compose :** Si vous utilisez Docker Compose, exécutez :

```bash
docker compose ps
docker compose logs -f app
```

Pour déboguer à l'intérieur du conteneur sans modifier l'image :

```bash
docker compose exec app sh
```

## Modes d'échec et reprise

BuildKit introduit de nouveaux modes d'échec différents de l'ancien builder. Reconnaissez les plus courants et ayez une procédure de reprise.

### Échec 1 : Le build échoue avec « failed to solve: failed to read dockerfile »

**Pourquoi cela arrive :** Le Dockerfile n'est pas à l'emplacement attendu, ou vous construisez à partir d'un contexte différent de celui prévu.

**Comment éviter :** Exécutez toujours `docker build -f <chemin-vers-dockerfile> <contexte>` explicitement. Par exemple :

```bash
docker build -f ./docker/Dockerfile .
```

**Reprise :** Vérifiez le chemin du fichier et le contexte. Exécutez `ls -la` pour confirmer que le Dockerfile existe. Si l'erreur mentionne `failed to read dockerfile: open /var/lib/docker/tmp/buildkit-mount.../Dockerfile: no such file or directory`, le contexte peut manquer le fichier. Ajustez le chemin.

### Échec 2 : Le build se bloque ou est extrêmement lent pendant l'installation des paquets

**Pourquoi cela arrive :** Pas de cache de build pour l'étape d'installation des paquets, ou vous utilisez un miroir réseau lent.

**Comment éviter :** Utilisez des montages de cache pour les gestionnaires de paquets. Pour Python `pip`, modifiez le Dockerfile :

```dockerfile
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
```

**Reprise :** Si le build est bloqué, appuyez sur `Ctrl+C` et ajoutez le montage de cache. Vérifiez également votre réseau et la configuration du miroir de registre.

### Échec 3 : « ERROR: BuildKit is enabled but the buildx component is missing or broken »

**Pourquoi cela arrive :** Le plugin buildx n'est pas installé ou n'est pas dans le PATH.

**Comment éviter :** Installez buildx selon la documentation officielle de Docker. Vérifiez avec `docker buildx version`.

**Reprise :** Réinstallez buildx ou utilisez temporairement l'ancien builder en désactivant `DOCKER_BUILDKIT` :

```bash
unset DOCKER_BUILDKIT
```

Mais cela désactive les fonctionnalités de BuildKit.

### Échec 4 : L'import/export du cache distant échoue

**Pourquoi cela arrive :** Vous utilisez le driver `docker` avec `--cache-to=type=registry`, ce qui nécessite un driver `docker-container`.

**Comment éviter :** Créez et utilisez un builder dédié :

```bash
docker buildx create --name labbuilder --driver docker-container --use
```

**Reprise :** Passez au bon builder ou supprimez les options de cache si non nécessaires.

### Échec 5 : Fuite de secrets dans les couches de l'image

**Pourquoi cela arrive :** Vous avez utilisé des arguments de build (`ARG`) pour les secrets, ou écrit des secrets dans un fichier pendant le build sans nettoyage.

**Comment éviter :** Utilisez toujours `--mount=type=secret` et ne stockez jamais de secrets dans le Dockerfile ou les variables d'environnement de l'image finale. Analysez votre image à la recherche de secrets après le build :

```bash
docker scan labapp:test
```

Ou utilisez un outil comme `trivy` :

```bash
trivy image labapp:test
```

**Reprise :** Si un secret a fui, faites pivoter le secret immédiatement et reconstruisez l'image avec une gestion appropriée des secrets. Ne poussez pas l'image compromise vers un registre.

**Propriétaire de la reprise :** Le propriétaire de la configuration de build (par exemple, Priya Shah) est responsable d'approuver toute solution de contournement et de planifier une correction permanente dans un délai d'une semaine.

## Liste de contrôle opérationnelle

Utilisez cette liste de contrôle avant et après chaque modification du laboratoire de build. Chaque élément a une commande concrète et un résultat attendu.

| Vérification | Commande | Résultat attendu | Propriétaire | Fréquence de revue |
|---|---|---|---|---|
| Version de Docker | `docker version --format '{{.Server.Version}}'` | Sortie d'une version >= 20.10 | Responsable DevOps | Mensuelle |
| BuildKit actif | `docker buildx debug info` | Affiche la version de BuildKit, aucune erreur | Responsable DevOps | Hebdomadaire |
| Driver du builder | `docker buildx ls` | Builder actuel marqué avec `*`, driver `docker` ou `docker-container` | Responsable ingénierie | Hebdomadaire |
| Utilisation du disque | `docker system df` | Cache de build inférieur à 10 Go pour le laboratoire | Responsable DevOps | Hebdomadaire |
| Santé des conteneurs | `docker ps` | Tous les conteneurs attendus en cours d'exécution, pas de boucle de redémarrage | Propriétaire de l'application | Quotidienne |
| Erreurs de journaux | `docker logs <container> --tail 100` | Aucune erreur fatale dans les 100 dernières lignes | Propriétaire de l'application | Quotidienne |
| Persistance du volume | Test de redémarrage comme décrit | Les données survivent à la recréation du conteneur | Propriétaire de l'application | Mensuelle |
| Taux de succès du cache de build | `docker build -t labapp:test .` | Les étapes affichent `CACHED` | Responsable ingénierie | À chaque build |
| Hygiène des secrets | `docker history labapp:test --no-trunc` | Aucune valeur de secret visible | Propriétaire de la sécurité | Hebdomadaire |
| Plan de retour en arrière | Documenté dans le runbook | Étapes de retour en arrière testées et valides | Responsable ingénierie | À chaque changement |

**Avant tout changement :**
- Exécutez `docker system df` et notez les chiffres.
- Exécutez `docker buildx ls` et notez le builder actuel.
- Assurez-vous qu'aucun conteneur de type production n'utilise actuellement les mêmes volumes que vous prévoyez de modifier.

**Après tout changement :**
- Relancez le build et vérifiez les étapes `CACHED`.
- Exécutez le conteneur et testez avec `curl` ou équivalent.
- Vérifiez `docker logs` pour des avertissements inattendus.
- Mettez à jour le runbook avec le changement et le retour en arrière.

## Pièges courants et comment les éviter

**Piège 1 : Utiliser `COPY . .` sans fichier `.dockerignore`.**

Pourquoi : Le contexte de build devient énorme, ralentit le build et peut inclure des fichiers sensibles comme `.env` ou des identifiants.

Comment éviter : Créez un fichier `.dockerignore` à la racine du projet :

```text
.git
.env
*.log
node_modules
__pycache__
```

Vérifiez la taille du contexte avec :

```bash
docker build --progress=plain -t labapp:test . 2>&1 | grep -i "context"
```

Recherchez une ligne comme `#1 transferring context: 1.2MB`. Si elle fait des dizaines de Mo, revoyez le fichier d'ignore.

**Piège 2 : Ne pas épingler les versions des images de base.**

Pourquoi : Utiliser `latest` conduit à des builds non reproductibles et des échecs surprises lorsque l'image de base est mise à jour.

Comment éviter : Épinglez à la fois l'image et le condensat :

```dockerfile
FROM python:3.11.3-slim@sha256:...
```

Ou au moins une version mineure spécifique.

**Piège 3 : Ignorer les avertissements de build sur les fonctionnalités dépréciées.**

Pourquoi : Des avertissements comme `DEPRECATED: The legacy builder is deprecated` indiquent que vous utilisez peut-être une syntaxe obsolète ou que vous manquez des fonctionnalités BuildKit.

Comment éviter : Lisez toujours la sortie complète du build. Utilisez `--progress=plain` pour voir tous les avertissements. Corrigez-les avant qu'ils ne deviennent des erreurs.

**Piège 4 : Exécuter les builds en tant que root dans le conteneur.**

Pourquoi : L'image finale s'exécute en root par défaut, ce qui est un risque de sécurité en production.

Comment éviter : Ajoutez un utilisateur non-root dans le Dockerfile :

```dockerfile
RUN useradd -m appuser
USER appuser
```

Vérifiez avec `docker run --rm labapp:test id -u` qui devrait afficher `1000` ou l'UID que vous avez défini.

**Piège 5 : Ne pas nettoyer les anciens builders et instances.**

Pourquoi : Les builders inutilisés et le cache gonflent votre système et créent de la confusion.

Comment éviter : Listez et supprimez périodiquement :

```bash
docker buildx ls
docker buildx rm labbuilder
```

Élaguez aussi le cache de build :

```bash
docker builder prune
```

Confirmez l'espace libéré avec `docker system df`.

## Conclusion

Un laboratoire local Docker BuildKit n'a de valeur que si chaque recommandation est limitée à une version, observable et réversible. Copier des commandes sans vérifier les prérequis et la sortie attendue n'est pas une procédure opérationnelle. Ce guide vous donne une manière structurée d'observer, de modifier, de vérifier et de récupérer.

Commencez par une vérification à faible risque : vérifiez votre builder actuel avec `docker buildx ls`, enregistrez l'état, exécutez un petit build et comparez le résultat avec la sortie `CACHED` attendue. Passez ensuite à des fonctionnalités plus avancées comme la mise en cache distante et les secrets seulement après que les bases fonctionnent de manière fiable.

Rendez les échecs visibles, protégez les valeurs sensibles, limitez les modifications à la ressource prévue et définissez la vérification de reprise avant qu'un incident ne force la décision.