Cache obsolète de binaires Rust dans Docker Buildx
Problème
Docker buildx peut servir des binaires Rust compilés obsolètes depuis le cache de couche même quand les fichiers source ont changé. L'image reçoit une nouvelle balise et pousse avec succès, mais contient le binaire ancien. C'est particulièrement courant avec les builds multi-plateforme (--platform linux/amd64 sur des Macs ARM).
Contexte / Conditions de déclenchement
- Dockerfile utilise un motif multi-étapes : copier Cargo.toml → build des dépendances → copier source → build
- Utilisation de
docker buildx build --pushavec--platform linux/amd64 - Les changements de code sont vérifiés localement (tests passent) mais la production affiche l'ancien comportement
- Le digest d'image du build en cache diffère du build
--no-cache - L'ajout d'un simple header de réponse dans le code n'apparaît pas en production
Solution
Toujours utiliser --no-cache lors du déploiement de changements de code :
# MAUVAIS — peut utiliser la couche de compilation en cache avec l'ancien code
docker buildx build --platform linux/amd64 --target api \
-t registry/image:tag --push .
# BON — force la recompilation complète
docker buildx build --platform linux/amd64 --target api \
--no-cache -t registry/image:tag --push .
Vérification
Comparer les digests d'image entre les builds avec et sans cache :
# Build avec cache
docker buildx build --platform linux/amd64 --target api \
-t registry/image:cached --push . 2>&1 | grep "pushing manifest"
# Noter le digest sha256
# Build sans cache
docker buildx build --platform linux/amd64 --target api \
--no-cache -t registry/image:nocache --push . 2>&1 | grep "pushing manifest"
# Noter le digest sha256
# Si les digests diffèrent, le build en cache contenait du code obsolète
Exemple
Motif Dockerfile typique vulnérable à ce problème :
# Couche 1 : Copier les manifests (en cache si Cargo.toml inchangé)
COPY Cargo.toml Cargo.lock ./
COPY crates/*/Cargo.toml crates/
# Couche 2 : Build des dépendances (en cache — c'est le bon cache)
RUN cargo build --release && rm -rf src crates
# Couche 3 : Copier le vrai code source (DOIT invalider au changement de source)
COPY crates crates
COPY bin bin
# Couche 4 : Rebuild avec le vrai code source
RUN touch crates/*/src/lib.rs && cargo build --release
Le problème : le cache content-addressable de buildx peut correspondre à la sortie de la Couche 4 depuis un build précédent si les représentations intermédiaires se heurtent par hasard, surtout lors de la cross-compilation où l'état de l'environnement de build est plus complexe.
Notes
- Ceci affecte principalement les builds cross-compilation
--platform(ARM → x86) - Les builds sur plateforme native sont moins susceptibles de rencontrer ce problème
- Le flag
--no-cacheajoute ~5-10 minutes aux builds Rust mais garantit une compilation fraîche - Alternative : utiliser
--cache-fromavec un scoping de cache explicite pour éviter les couches obsolètes - Envisager d'ajouter une variable d'environnement au build (comme un hash de commit) qui force l'invalidation de couche :
ARG BUILD_HASH RUN echo $BUILD_HASH && cargo build --release