docker-buildx-stale-rust-binary

Par divinevideo · divine-mobile

Corriger les binaires Rust déployés qui ne reflètent pas les modifications du code lors de l'utilisation de docker buildx. À utiliser lorsque : (1) les modifications du code sont vérifiées localement (les tests passent) mais le comportement en production ne change pas après le déploiement, (2) l'image Docker a un nouveau tag mais contient un ancien binaire compilé, (3) le Dockerfile utilise un build multi-étapes avec une couche de pré-compilation des dépendances et une couche de copie des sources. Le cache de couches de docker buildx peut servir des sorties de compilation obsolètes même lorsque les fichiers sources changent, en particulier avec la cross-compilation (`--platform linux/amd64`).

npx skills add https://github.com/divinevideo/divine-mobile --skill docker-buildx-stale-rust-binary

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 --push avec --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-cache ajoute ~5-10 minutes aux builds Rust mais garantit une compilation fraîche
  • Alternative : utiliser --cache-from avec 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

Skills similaires