Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Cache BuildKit en CI et promotion d'image entre registres

27 min de lecture
Améliorer cette page

Un runner de CI neuf, c'est-à-dire la machine qui exécute votre pipeline, n'a aucun cache : chaque build refait toutes les étapes, y compris l'installation des dépendances. Le cache distant de BuildKit règle ce problème : le premier build range son cache dans un registre, et le runner suivant le récupère avec --cache-from. Le réglage --cache-to a deux modes : min n'exporte que les couches de l'image finale, max exporte aussi celles des étapes intermédiaires. Cette page mesure ce que coûte et ce que rapporte chacun.

Elle traite ensuite la promotion : envoyer vers le registre de production l'image qui a été testée, sans la reconstruire, avec docker buildx imagetools create, skopeo copy --all ou crane copy. Elle s'adresse à qui écrit ou maintient un pipeline de construction d'images, et connaît déjà docker build et le principe d'un registre.

Quelques pages complètent celle-ci. La leçon Automatiser build, tests et publication d'une image montre un script ci.sh qui construit, teste puis publie une archive accompagnée de sa provenance, mais elle ne mesure ni le cache distant ni la promotion entre registres. Les outils de copie ont leurs guides, Crane et Skopeo. Le format des tags, des digests et des index est expliqué dans Comprendre OCI, leur usage dans Publier et récupérer une image de façon maîtrisée et dans Épingler un socle, et livrer hors ligne. Les images multi-plateformes sont dans Construire pour plusieurs architectures.

  • Exporter le cache de build vers un registre et le réimporter sur une machine vide.
  • Corriger l'échec connection refused d'un builder docker-container qui vise un registre local.
  • Comparer mode=min et mode=max en taille de cache, temps d'export et temps de rebuild.
  • Copier une image d'un registre à l'autre en conservant le digest de son index, puis le vérifier.
  • Repérer les deux copies qui perdent des plateformes tout en réussissant : skopeo copy sans --all, et l'enchaînement docker pull, docker tag, docker push.
  • Choisir entre imagetools, Skopeo et crane selon l'outil déjà présent dans le pipeline.

Dans quel environnement ces mesures ont-elles été faites ?

Section intitulée « Dans quel environnement ces mesures ont-elles été faites ? »

Toutes les sorties viennent d'une machine virtuelle Debian 13 avec Docker Engine 29.9.2, Buildx 0.38.1 et le magasin d'images containerd, qui est le magasin des nouvelles installations de Docker 29. Les registres de test sont registry:3.1.2@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3, joints en HTTP sur 127.0.0.1.

Deux mots reviennent dans toute la page. Un builder est l'instance de BuildKit qui exécute vos builds, et son pilote (driver) décide où elle tourne. Le builder default utilise le pilote docker, avec BuildKit v0.34.0. Le builder docker-container, créé pour les mesures, lance BuildKit v0.34.2 dans un conteneur dédié, à partir de l'image moby/buildkit:v0.34.2@sha256:c5f8862f52499773af85bc07695b2379ef08e8002352b0df2c4864c91d78dabe. Le @sha256:… est le digest de l'image, l'empreinte de son contenu : l'épingler garantit que vous obtenez exactement cette image, même si le tag est déplacé. v0.34.2 est la dernière version publiée de moby/buildkit sur Docker Hub au 2026-11-04, et son digest est aussi celui des tags latest et buildx-stable-1 à cette date.

Si votre version diffère, ce qui est mesuré ici ne vaut que pour cette combinaison. Les durées et les tailles sont des mesures uniques, à lire comme des ordres de grandeur : relevez les vôtres sur vos runners et votre registre. L'ancien magasin d'images de Docker n'a pas été testé, et rien n'est affirmé sur son comportement.

Comment partager le cache de build entre deux exécutions de CI ?

Section intitulée « Comment partager le cache de build entre deux exécutions de CI ? »

Deux options de docker buildx build suffisent : --cache-to type=registry,ref=<image de cache> écrit le cache dans un registre à la fin du build, et --cache-from type=registry,ref=<image de cache> le relit au début du suivant. Le cache est publié comme une image ordinaire, dans un dépôt dédié, par exemple cache/app. Les mesures de cette page s'appuient sur ce Dockerfile, dont les sleep rendent les durées lisibles sans changer ce qui est mis en cache :

FROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a AS deps
RUN apk add --no-cache build-base python3 && sleep 3
FROM deps AS build
COPY app.c /src/app.c
RUN sleep 3 && gcc -o /app /src/app.c
FROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
COPY --from=build /app /app
RUN sleep 2 && echo final > /etc/final
CMD ["/app"]

Il a trois étapes : deps installe un compilateur, build compile app.c, l'image finale ne garde que le binaire. Seule l'étape build dépend du code source, ce qui permet de mesurer ce qu'un changement de code relance.

Pour reproduire, travaillez dans un dossier vide, ~/refcache/app, où vous enregistrez le Dockerfile ci-dessus. Créez ensuite le fichier app.c à côté, puis démarrez le registre qui recevra le cache, publié sur l'adresse locale de la machine :

Fenêtre de terminal
mkdir -p ~/refcache/app && cd ~/refcache/app
printf '#include <stdio.h>\nint main(){puts("hello");return 0;}\n' > app.c
docker run -d --name reg-cache -p 127.0.0.1:5080:5000 \
registry:3.1.2@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3

D'après la documentation de Docker Build, le pilote docker ne prend en charge le backend registry que si le magasin d'images containerd est activé. Avec ce magasin sur Docker 29.9.2, le builder default exporte le cache vers un registre sans erreur.

Le premier export ci-dessous utilise mode=min, la valeur par défaut d'après la documentation des backends de cache. La section sur min et max plus bas compare les deux modes :

Fenêtre de terminal
docker buildx build \
--cache-to type=registry,ref=127.0.0.1:5080/cache/app:y,mode=min \
-t app:t .

La commande rend le code 0, et le dernier bloc de la sortie annonce writing cache image manifest. L'import par --cache-from avec ce pilote est mesuré dans le guide Docker Bake, section « Comment réutiliser le cache entre deux builds ? », avec le builder default. Les comparaisons ci-dessous utilisent un builder docker-container, parce que son cache est isolé : docker buildx prune --builder le vide sans toucher au builder default.

Pourquoi un builder docker-container échoue-t-il avec connection refused ?

Section intitulée « Pourquoi un builder docker-container échoue-t-il avec connection refused ? »

Parce que BuildKit s'exécute alors dans son propre conteneur, et que l'adresse 127.0.0.1 y désigne ce conteneur, pas votre machine. Un builder créé sans option exporte les couches puis échoue au moment d'écrire dans le registre local :

Fenêtre de terminal
docker buildx create --driver docker-container \
--driver-opt image=moby/buildkit:v0.34.2@sha256:c5f8862f52499773af85bc07695b2379ef08e8002352b0df2c4864c91d78dabe \
--name bk1
docker buildx build --builder bk1 --progress=plain \
--cache-to type=registry,ref=127.0.0.1:5080/cache/app:min,mode=min \
-o type=cacheonly .
ERROR: failed to build: failed to solve: error writing layer blob: failed to do request: Head "http://127.0.0.1:5080/v2/cache/app/blobs/sha256:7b229517ed222597277036d94892173207d01a731ab3a88eeb1de2e963a2c788": dial tcp 127.0.0.1:5080: connect: connection refused

Regardez la fin du message : dial tcp 127.0.0.1:5080: connect: connection refused dit que la connexion est refusée sur l'adresse 127.0.0.1 vue depuis le builder. Le message ne distingue pas un registre arrêté d'un builder qui ne voit pas le réseau de la machine.

Pour les séparer, lancez curl -sS -i http://127.0.0.1:5080/v2/ depuis la machine elle-même. Si le registre répond, il tourne bien et c'est le réseau du builder qui est en cause. S'il ne répond pas, démarrez ou réparez d'abord le registre. Voici les deux réponses mesurées, en-têtes abrégés :

# registre vivant
HTTP/1.1 200 OK
Content-Type: application/json
Docker-Distribution-Api-Version: registry/2.0
{}
# registre arrêté (code de retour 7)
curl: (7) Failed to connect to 127.0.0.1 port 5080 after 0 ms: Could not connect to server

Dans notre cas le registre tournait, et l'option --driver-opt network=host fait partager à ce conteneur le réseau de la machine, ce qui corrige l'échec :

Fenêtre de terminal
docker buildx create --driver docker-container \
--driver-opt network=host \
--driver-opt image=moby/buildkit:v0.34.2@sha256:c5f8862f52499773af85bc07695b2379ef08e8002352b0df2c4864c91d78dabe \
--name bk2
docker buildx inspect bk2 --bootstrap

inspect doit afficher Status: running, BuildKit version: v0.34.2 et Driver Options avec les valeurs image="moby/buildkit:v0.34.2@sha256:c5f8862f52499773af85bc07695b2379ef08e8002352b0df2c4864c91d78dabe" et network="host". La mesure porte uniquement sur un registre local. Face à un registre distant, joignable par un nom, ce contournement n'a pas été testé.

network=host est un contournement de banc d'essai local : il expose le réseau de la machine à un conteneur BuildKit privilégié. Ne le mettez pas par défaut sur un runner de CI partagé.

Le mode min exporte seulement les couches de l'image finale, le mode max exporte aussi celles des étapes intermédiaires. Le cache d'une image est décrit par un manifeste de cache, la liste des couches qu'il contient. Sur le Dockerfile ci-dessus, le manifeste min contient 3 couches pour 3,9 Mo, et le manifeste max en contient 6 pour 117,5 Mo (la même couche de 3 267 octets y figure deux fois).

Le schéma suit les trois étapes du Dockerfile. Les flèches pleines montrent l'ordre des étapes ; la flèche épaisse part de l'image finale vers ce que min exporte, et les flèches pointillées montrent que max exporte les trois étapes.

Les étapes deps, build et final s'enchaînent. Le mode min exporte seulement les couches de l'image finale, 3 couches pour 3,9 Mo. Le mode max exporte les couches des trois étapes, 6 couches pour 117,5 Mo.Étape depsinstalle build-baseÉtape buildcompile app.cÉtape finalecopie le binairemode=min3 couches, 3,9 Momode=max6 couches, 117,5 Mo
Le mode min exporte l'image finale, le mode max exporte aussi les étapes intermédiaires. Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

La couche de 113,6 Mo, qui n'existe qu'en max, correspond à l'étape deps, là où l'installation de build-base pèse le plus. C'est ce qui fait la différence de taille entre les deux modes.

Le tableau résume quatre mesures. Avant chaque relecture, le cache local a été vidé avec docker buildx prune -af --builder bk2. L'export se fait avec --cache-to type=registry,ref=127.0.0.1:5080/cache/app:<mode>,mode=<mode> -o type=cacheonly, et la relecture avec --cache-from type=registry,ref=127.0.0.1:5080/cache/app:<mode>.

Pour la dernière ligne, le code source est modifié par echo '/* v2 */' >> app.c, qui ajoute un commentaire à la fin de app.c. Avant de passer à l'autre mode, app.c est restauré avec la commande printf de départ (printf '#include <stdio.h>\nint main(){puts("hello");return 0;}\n' > app.c), pour que les deux modes partent de la même source.

Les durées et la taille de min viennent d'un builder créé avec l'image moby/buildkit:buildx-stable-1, qui porte le même digest c5f8862f… que v0.34.2. Celles de max viennent de --builder bk2, épinglé sur v0.34.2.

Situationminmax
Build à froid avec export (durée totale)18,3 s28,0 s
Étape d'export du cache seule0,2 s9,4 s
Cache local vidé, source identique955 ms, tout en CACHED942 ms, tout en CACHED
Cache local vidé, app.c modifié16,0 s, étape deps rejouée (11,3 s)6,6 s, étape deps en CACHED

Les deux lignes de durée ne mesurent pas la même chose. Pour min, 0,2 s est l'étape d'export entière. Pour max, 9,4 s est la sous-étape preparing build cache for export, qui prépare le cache avant son écriture dans le registre.

La ligne importante est la dernière. Tant que le code ne change pas, les deux modes donnent le même résultat. Dès qu'une étape en amont de l'image finale doit être rejouée, min n'a rien à offrir pour elle et le runner réinstalle le compilateur, alors que max la reprend du registre. Dans la sortie de max, la ligne de l'étape deps affiche CACHED puis une durée de 2,0 s. Il s'agit probablement du temps de récupération de la couche, mais ce point n'a pas été vérifié séparément.

Le prix de max se lit dans les deux premières lignes : il écrit 117,5 Mo au lieu de 3,9 Mo et ajoute environ 9 s à chaque export. Sur ce Dockerfile, ces 9 s sont du même ordre que ce que max fait gagner à la relecture (16,0 s contre 6,6 s), et le registre est local.

max se justifie quand une étape lourde change rarement : plus elle est longue à rejouer, plus le cache de max rapporte. Si presque toutes les étapes changent à chaque commit, min peut suffire. Le critère est donc le poids des étapes intermédiaires que vos commits ne touchent pas, que vous mesurez sur vos runners.

Comment promouvoir une image d'un registre à l'autre sans la reconstruire ?

Section intitulée « Comment promouvoir une image d'un registre à l'autre sans la reconstruire ? »

Il faut copier l'index de l'image tel quel, avec toutes ses plateformes. Un manifeste décrit une image pour une plateforme : ses couches et sa configuration. Un index est la liste des manifestes d'une image multi-plateforme, avec ses attestations, des métadonnées sur la construction de l'image. Il est identifié par un digest, son empreinte SHA-256.

Si la copie conserve l'index, l'image en production a le même digest que celle qui a passé les tests, et une vérification faite sur ce digest reste valable. Cette garantie suppose que la copie parte du bon index. Un tag comme 1.0 est mobile : si quelqu'un le repousse entre les tests et la promotion, la copie envoie une autre image que celle qui a été testée. Relevez donc le digest au moment des tests, avec docker buildx imagetools inspect <image> --format '{{.Manifest.Digest}}', et promouvez à partir de ce digest plutôt que du tag, comme le montre la section « Promotion par digest en CI/CD » de Comprendre OCI.

Une signature externe (par exemple celle de cosign) reste aussi valable pour ce digest, mais elle est stockée à part, comme un référent (en anglais referrer), un objet rattaché à l'image par son digest. Il faut donc le copier lui aussi, ou re-signer l'image : ce cas n'a pas été mesuré.

Une promotion réelle se fait avec deux registres authentifiés, en lecture sur la source et en écriture sur la cible. Les mesures ci-dessous utilisent des registres locaux sans identifiants : l'authentification n'a pas été mesurée.

Le schéma part d'un même point : un index de développement qui contient les variantes linux/amd64 et linux/arm64 et deux attestations. À gauche, les trois outils de copie reproduisent l'index à l'identique. À droite, l'enchaînement docker pull, docker tag, docker push aboutit à un manifeste unique, de digest différent.

Un index de registre de développement, avec les variantes amd64 et arm64 et deux attestations, est copié par imagetools create, skopeo copy --all ou crane copy vers un index de production de même digest. Le même index passé par docker pull, docker tag et docker push devient un manifeste amd64 seul, de digest différent.Registre de devindex 07b8f23camd64, arm64, 2 attestationsimagetools createskopeo copy --allcrane copydocker pulldocker tagdocker pushRegistre de prodindex 07b8f23c4 entrées, même digestRegistre de prodmanifeste c7ca751aamd64 seul, digest différent
Trois outils de copie gardent le digest d'index ; le trio pull, tag, push le change sans échouer. Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Pour reproduire, travaillez dans un second dossier vide, ~/refcache/promo, pour ne pas écraser le Dockerfile du cache. Démarrez deux registres avec l'image registry:3.1.2 épinglée plus haut, publiés sur 127.0.0.1:5081 et 127.0.0.1:5082. Écrivez ensuite le Dockerfile de promotion, une base alpine et un fichier copié, puis publiez-le dans le premier registre pour deux plateformes :

Fenêtre de terminal
mkdir -p ~/refcache/promo && cd ~/refcache/promo
REG=registry:3.1.2@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3
docker run -d --name reg5081 -p 127.0.0.1:5081:5000 $REG
docker run -d --name reg5082 -p 127.0.0.1:5082:5000 $REG
echo hi > hello.txt
cat > Dockerfile <<'EOF'
FROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
COPY hello.txt /hello.txt
EOF
docker buildx build --platform linux/amd64,linux/arm64 \
-t 127.0.0.1:5081/app:1.0 --push .

Ce build multi-plateforme ne demande pas d'émulation QEMU, parce que le Dockerfile ne contient aucune instruction RUN : rien n'est exécuté pour arm64, seul un fichier est copié.

L'index publié contient quatre entrées : linux/amd64, linux/arm64 et deux attestations (unknown/unknown). Ces attestations changent à chaque build : le même Dockerfile construit plusieurs fois a donné des index de digests différents (07b8f23c…, d8a0b216…, 3b1eee7c… dans nos essais). Le digest de la mesure principale est sha256:07b8f23c637bdcb7679fdc30b5590e1e9d74e5a0b925b982aabd8220581dbc3f, et le vôtre sera différent.

Les trois commandes ci-dessous copient 127.0.0.1:5081/app:1.0 vers 127.0.0.1:5082/app:1.0 et conservent chacune le digest de l'index d'origine, quel qu'il soit. Elles partent du tag, comme dans la mesure. Dans un pipeline, la source s'écrit plutôt 127.0.0.1:5081/app@sha256:<digest relevé pendant les tests>, une forme que ces mesures n'ont pas rejouée.

Les options --src-tls-verify=false et --dest-tls-verify=false de Skopeo ne servent qu'à joindre les registres de test en HTTP. Retirez-les face à de vrais registres en HTTPS. Pour imagetools et crane, aucune option n'est nécessaire : ils joignent 127.0.0.1 en HTTP parce que la boucle locale est traitée comme non sécurisée, ce qui n'est pas le cas d'un nom de machine. Le guide Servir un registre de confiance, et s'y authentifier détaille ce point.

Fenêtre de terminal
# Buildx : copie l'index et ses manifestes
docker buildx imagetools create \
--tag 127.0.0.1:5082/app:1.0 127.0.0.1:5081/app:1.0
# Skopeo 1.22.3 : --all copie toutes les plateformes
SKO=quay.io/skopeo/stable:v1.22.3@sha256:2681eccb8b02c495b013c2669cbf7f41d5c0b0b0e644e7c69bd3ea9bcb20641f
docker run --rm --network host $SKO copy --all \
--src-tls-verify=false --dest-tls-verify=false \
docker://127.0.0.1:5081/app:1.0 docker://127.0.0.1:5082/app:1.0
# crane : copie l'index
CRA=gcr.io/go-containerregistry/crane:v0.22.1@sha256:1f968817b95790bed063f71175aa6b8ff879fa17064020415f3e18bb6e6a36e1
docker run --rm --network host $CRA copy \
127.0.0.1:5081/app:1.0 127.0.0.1:5082/app:1.0

L'image de crane est épinglée par son tag et son digest d'index. L'encadré facultatif ci-dessous détaille les versions de crane testées.

Un runner sans Docker n'a pas besoin de ces images : il installe Skopeo par le paquet de sa distribution et crane par l'archive de ses releases, comme pour les autres outils de la chaîne, et lance la même commande. Ce chemin n'a pas été mesuré ici. Debian 13 propose Skopeo 1.18.0+ds1-1+b5 (mesuré le 2026-11-03 pour la leçon Exporter une image sans démon avec Skopeo) : cette version diffère de la 1.22.3 mesurée ici, et les sorties peuvent différer.

Pour contrôler le résultat, comparez les digests des deux côtés. Ce contrôle ne prouve que la fidélité de la copie : si le tag 1.0 a été repoussé entre les tests et la promotion, les deux côtés restent égaux alors que l'image n'est plus celle qui a été testée. Comparez donc le digest de production au digest relevé au moment des tests, par la même commande lancée à ce moment-là :

Fenêtre de terminal
# au moment des tests : relever le digest de l'image testée
docker buildx imagetools inspect 127.0.0.1:5081/app:1.0 --format '{{.Manifest.Digest}}'
# après la promotion : relever le digest reçu par la production
docker buildx imagetools inspect 127.0.0.1:5082/app:1.0 --format '{{.Manifest.Digest}}'

Les deux lignes doivent afficher le même digest : sha256:07b8f23c… dans notre mesure, un autre dans la vôtre. C'est ce qui est arrivé avec les trois outils : le digest d'index est identique. imagetools et Skopeo ont laissé dans le registre de production exactement les 13 blobs du registre de développement, et crane ces 13 plus deux. Un blob est un fichier stocké dans le registre, par exemple une couche ou une configuration. Les trois outils copient donc aussi les deux attestations de l'index.

L'écart vient de crane. Avec crane copy, le registre de production contient 15 blobs. Sur ce registre de test, qui n'offre pas d'API des référents, crane écrit en plus deux petits index rangés à des tags de repli de la forme sha256-<digest d'une variante>, que sa sortie annonce par updating fallback tag … with new referrer. Le digest de l'index ne change pas, mais le dépôt de production contient deux objets de plus qu'avec les autres outils. La copie d'une signature externe n'a pas été mesurée, avec aucun des trois outils.

Que se passe-t-il si on oublie --all, ou si on utilise docker pull puis push ?

Section intitulée « Que se passe-t-il si on oublie --all, ou si on utilise docker pull puis push ? »

Dans les deux cas la commande réussit, et la production reçoit une image qui n'est plus celle qui a été testée. Le piège est discret : rien n'échoue.

Sans --all, skopeo copy ne garde que la plateforme de la machine qui exécute la commande. Dans la mesure, la production a reçu un manifeste unique de digest sha256:c7ca751a…, qui est la variante amd64, et non l'index sha256:07b8f23c…. Les digests c7ca751a… et b4810a9d… de cette section sont propres à ce build : vous obtiendrez d'autres valeurs, mais le même constat.

Le contre-exemple suivant est à éviter. Il a été joué après suppression des images locales, comme sur un runner neuf :

Fenêtre de terminal
# ❌ à éviter : ne promeut pas l'image testée
docker pull 127.0.0.1:5081/app:1.0
docker tag 127.0.0.1:5081/app:1.0 127.0.0.1:5082/app:1.0
docker push 127.0.0.1:5082/app:1.0
1.0: digest: sha256:c7ca751ac09f7569bbf4ebfaa58739e8a77f050f402bacec0f6c2541489a24f6 size: 668
Info -> Not all multiplatform-content is present and only the available single-platform image was pushed
sha256:07b8f23c637bdcb7679fdc30b5590e1e9d74e5a0b925b982aabd8220581dbc3f -> sha256:c7ca751ac09f7569bbf4ebfaa58739e8a77f050f402bacec0f6c2541489a24f6

Le code de retour est 0. La seule trace de la perte est la ligne Info, qui donne l'ancien digest d'index puis le nouveau. La variante arm64 et les attestations ont disparu de la production, et le digest a changé : une signature ou une vérification faite sur sha256:07b8f23c… ne s'applique plus. Avec docker pull --platform linux/arm64, le même enchaînement a publié le manifeste arm64 seul, sha256:b4810a9d….

Un premier essai, fait sur la machine qui avait construit l'image, a donné le même digest d'index : son magasin local contenait encore toutes les plateformes. Ce résultat ne se transpose pas à un runner qui part de zéro.

Sur une image à plateforme unique, le même enchaînement a également conservé le digest dans la mesure, mais cette mesure a été faite sur la machine qui avait construit l'image, après suppression de la copie locale ; ne comptez pas dessus.

Ne promouvez pas par pull, tag et push, et comparez toujours les digests après la copie.

Comment choisir entre imagetools, skopeo et crane ?

Section intitulée « Comment choisir entre imagetools, skopeo et crane ? »

Les trois conservent le digest d'index, donc le choix dépend de l'outil déjà présent dans votre pipeline et de ce que vous voulez en plus. Si vous construisez déjà avec Buildx, imagetools create ne demande rien de plus. Skopeo et crane n'ont pas besoin du démon Docker pour copier ; ici, ils tournent dans leur image, par commodité.

OutilPoint à retenir
docker buildx imagetools createDéjà présent avec Buildx : aucune image ni installation en plus dans un pipeline qui construit avec Buildx.
skopeo copy --allOutil autonome, exécuté ici dans son image épinglée. L'option --all est indispensable : sans elle, une seule plateforme part.
crane copyÉcrit en plus deux index de tags de repli (updating fallback tag … with new referrer) sur un registre sans API des référents.

Aucun des trois n'a été mesuré sur la copie d'une signature externe. Les guides Skopeo et Crane détaillent chaque outil, et Skopeo a aussi une page pour exporter une image sans démon : Exporter une image sans démon avec Skopeo.

Les essais laissent deux builders, trois registres, des images locales et du cache de build : retirez-les pour ne rien laisser tourner sur la machine. Les noms ci-dessous sont ceux utilisés plus haut.

Fenêtre de terminal
docker buildx rm bk1 bk2 -f
docker rm -fv reg-cache reg5081 reg5082
docker rmi app:t 127.0.0.1:5081/app:1.0 127.0.0.1:5082/app:1.0
docker buildx prune -af
rm -r ~/refcache

Si une commande répond No such image ou « builder introuvable », ce n'est pas grave : l'objet n'a pas été créé chez vous, il n'y a rien à retirer. docker rm -fv supprime aussi le volume anonyme de chaque registre. docker buildx prune -af vide tout le cache de build de la machine, pas seulement celui de ces essais : sautez cette ligne si d'autres projets en dépendent.

Les images téléchargées pour les essais (alpine, registry, moby/buildkit, Skopeo et crane) restent aussi dans le magasin local. Listez-les avec docker images et retirez avec docker rmi celles dont vous n'avez plus l'usage. Les dossiers de travail (app.c, les Dockerfile et hello.txt) sont partis avec rm -r ~/refcache. Vérifiez avec docker ps -a et docker buildx ls qu'il ne reste que le builder default.

Les points ci-dessous résument les mesures de cette page, sur Docker 29.9.2 et Buildx 0.38.1.

  • Le cache distant de BuildKit se partage par un registre avec --cache-to type=registry,ref=… et --cache-from type=registry,ref=….
  • Un builder docker-container ne joint pas un registre sur 127.0.0.1 sans --driver-opt network=host, il échoue en connection refused. Un curl http://127.0.0.1:5080/v2/ depuis la machine dit si le registre tourne.
  • Sur la mesure, max pèse 117,5 Mo contre 3,9 Mo pour min et ajoute environ 9 s à l'export.
  • Sans changement de code, min et max donnent le même résultat ; quand le code change, max a repris l'étape de dépendances du registre (6,6 s) là où min l'a rejouée (16,0 s).
  • Promouvoir une image, c'est copier son index : imagetools create, skopeo copy --all et crane copy gardent le même digest.
  • skopeo copy sans --all et docker pull, docker tag, docker push réussissent, mais perdent des plateformes et changent le digest.
  • Comparez le digest de production au digest relevé au moment des tests, et promouvez à partir de ce digest : un tag mobile peut être repoussé entre les tests et la promotion, et la comparaison des deux registres ne le verrait pas.

Les cartes ci-dessous prolongent cette page : la chaîne de CI qui publie l'image, les outils de copie, et le déploiement du même artefact d'un environnement à l'autre.

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn