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

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

Skills similaires