Docker Buildx Stale Rust Binary Cache
Problem
Docker buildx peut servir des binaires Rust compilés obsolètes à partir du cache des couches, même si les fichiers source ont changé. L'image reçoit une nouvelle étiquette et se pousse avec succès, mais contient le vieux binaire compilé. Cela est particulièrement courant avec les builds multi-plateforme (--platform linux/amd64 sur les Macs ARM).
Context / Trigger Conditions
- Dockerfile utilise le pattern multi-stage : copier Cargo.toml → builder les dépendances → copier la source → builder
- Utilisation de
docker buildx build --pushavec--platform linux/amd64 - Changements de code vérifiés localement (les tests passent) mais la production affiche l'ancien comportement
- Le digest de l'image du build mis en cache diffère du build
--no-cache - Ajout d'un simple en-tête de réponse dans le code et il n'apparaît pas en production
Solution
Utilisez toujours --no-cache lors du déploiement de changements de code :
# WRONG — peut utiliser la couche de compilation mise en cache avec l'ancien code
docker buildx build --platform linux/amd64 --target api \
-t registry/image:tag --push .
# CORRECT — force la recompilation complète
docker buildx build --platform linux/amd64 --target api \
--no-cache -t registry/image:tag --push .
Verification
Comparez les digests d'image entre les builds avec cache et sans cache :
# Build avec cache
docker buildx build --platform linux/amd64 --target api \
-t registry/image:cached --push . 2>&1 | grep "pushing manifest"
# Notez 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"
# Notez le digest sha256
# Si les digests diffèrent, le build mis en cache contenait du code obsolète
Example
Pattern Dockerfile typique vulnérable à ce problème :
# Layer 1: Copy manifests (cached if Cargo.toml unchanged)
COPY Cargo.toml Cargo.lock ./
COPY crates/*/Cargo.toml crates/
# Layer 2: Build dependencies (cached — this is the good cache)
RUN cargo build --release && rm -rf src crates
# Layer 3: Copy actual source (SHOULD invalidate on source change)
COPY crates crates
COPY bin bin
# Layer 4: Rebuild with actual source
RUN touch crates/*/src/lib.rs && cargo build --release
Le problème : le cache addressable par contenu de buildx peut correspondre à la sortie de la Layer 4 d'un build précédent si les représentations intermédiaires entrent en collision, particulièrement lors de la cross-compilation où l'état de l'environnement de build est plus complexe.
Notes
- Cela affecte principalement les builds de cross-compilation
--platform(ARM → x86) - Les builds de plateforme native sont moins susceptibles de rencontrer ce problème
- Le flag
--no-cacheajoute environ 5-10 minutes aux builds Rust mais garantit une compilation fraîche - Alternative : utilisez
--cache-fromavec un scoping de cache explicite pour éviter les couches obsolètes - Envisagez d'ajouter une variable d'environnement de build-time (comme un commit hash) qui force l'invalidation de couche :
ARG BUILD_HASH RUN echo $BUILD_HASH && cargo build --release