Votre Dockerfile commence par FROM alpine:3.24.3, et rien ne garantit que ce tag désigne demain le contenu d'aujourd'hui. Cette leçon, pour qui sait déjà lire l'identité d'une image et publier dans un registre, montre comment écrire dans le FROM l'empreinte exacte du socle, comment savoir qu'une version plus récente est sortie, puis comment livrer l'image finie à une machine qui n'atteint aucun registre public. Deux voies sont mesurées : l'archive, produite par docker save, et un registre dont on transporte le volume. Les sorties viennent de Docker Engine 29.9.2 avec Buildx 0.38.1, sur Debian 13 et le magasin d'images containerd.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Relever le digest d'index d'un tag et l'écrire dans un
FROM nom:tag@sha256:… - Prouver qu'un build épinglé reste sur l'ancien socle quand le tag est déplacé
- Détecter par un script qu'un socle plus récent est publié
- Exporter une image avec
docker save, savoir ce que--platformchange à l'identité de l'image chargée, vérifier l'archive avecsha256sumavant de la charger - Livrer une image par un registre dont on transporte le volume, et la tirer par digest sans réseau
- Expliquer pourquoi un miroir pull-through ne suffit pas hors ligne, et diagnostiquer une livraison qui échoue
Prérequis
Section intitulée « Prérequis »- Lire une image OCI et son identité : tag, index, manifeste et digest. La leçon ne les redéfinit pas.
- Choisir et maintenir une image de base Docker : pourquoi un tag de socle est mutable et comment suivre un socle dans le temps.
- Publier et récupérer une image de façon maîtrisée :
docker push,docker pullet le registre local en HTTP sur127.0.0.1. - Servir un registre de confiance, et s'y authentifier : la leçon précédente monte un registre avec TLS et un mot de passe. Ici le registre reste en HTTP sur la boucle locale : cela suffit pour la démonstration, pas pour un registre que d'autres machines atteignent.
- Un Docker Engine 29.9.2 avec Buildx (livré avec lui) sur un hôte
amd64, et la possibilité de lancer un conteneur privilégié pour simuler la machine sans réseau. Les tailles et les digests de manifeste de la leçon sont ceux d'un hôteamd64. python3, que la leçon emploie pour lire un jeton et pour altérer une archive volontairement.
Les FROM de la leçon portent leur digest, et les commandes qui lancent alpine:3.24.3 ou busybox:1.37 aussi, sous la forme image:tag@sha256:…. Seules les commandes qui prouvent l'absence d'une image gardent le tag seul : le bloc concerné porte un commentaire # ❌, parce que ces commandes doivent échouer. Les empreintes affichées sont celles de la mesure : celles d'une image que vous construisez vous-même diffèrent des nôtres, ce qui est normal.
Trois comportements ne sont pas garantis, parce que la mesure ne les a pas rejoués : la mise à jour automatique par Renovate, le chargement d'une archive multi-architecture, et un autre magasin que containerd. La leçon les signale au passage. La copie d'un index complet vers un registre par skopeo copy --all n'est pas rejouée dans cette leçon : la page Cache BuildKit en CI et promotion d'image entre registres la mesure, et le guide Skopeo mesure --all vers une archive.
Comment relever le digest d'un tag ?
Section intitulée « Comment relever le digest d'un tag ? »Pour épingler un socle, il faut d'abord lire son empreinte. La commande à retenir est docker buildx imagetools inspect : elle interroge le registre sans tirer l'image et affiche, sans option, le digest de l'index, la liste des variantes par architecture.
docker buildx imagetools inspect alpine:3.24.3 | head -12Name: docker.io/library/alpine:3.24.3MediaType: application/vnd.oci.image.index.v1+jsonDigest: sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
Manifests: Name: docker.io/library/alpine:3.24.3@sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d MediaType: application/vnd.oci.image.manifest.v1+json Platform: linux/amd64 Annotations: org.opencontainers.image.source: https://github.com/alpinelinux/docker-alpine.git#5aec467fb81238bae9441eea2edb000655d7044f:x86_64 org.opencontainers.image.url: https://hub.docker.com/_/alpine org.opencontainers.image.version: 3.24.3Regardez la ligne Digest du haut : c'est celle de l'index, et c'est elle qu'on épingle, pas celle de la première entrée Name, qui est le manifeste linux/amd64. Épingler l'index garde le build portable d'une architecture à l'autre.
Pour n'imprimer que le digest, utilisez --format. La sortie n'a pas de retour à la ligne final, ce qui convient à une affectation de variable.
docker buildx imagetools inspect alpine:3.24.3 --format '{{.Manifest.Digest}}'sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aAu moment de la mesure, alpine:3.24.3 et alpine:latest renvoyaient le même digest, alpine:3.22 un autre (sha256:2e0b81d6…). Une autre commande, docker manifest inspect, existe toujours, mais elle liste les manifestes de l'index sans afficher le digest de l'index lui-même : elle ne sert pas à relever l'empreinte à épingler.
Comment retrouver ce digest par l'API du registre ?
Section intitulée « Comment retrouver ce digest par l'API du registre ? »Cette sous-section est facultative. L'API du registre répond à la même question que imagetools : Docker Hub exige un jeton, même anonyme, puis on interroge le manifeste du tag avec un en-tête Accept qui réclame l'index. L'en-tête docker-content-digest de la réponse porte l'empreinte. Le dialogue avec un registre authentifié est détaillé dans la leçon sur le registre de confiance.
TOK=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/alpine:pull" | python3 -c 'import sys,json;print(json.load(sys.stdin)["token"])')curl -sI -H "Authorization: Bearer $TOK" \ -H 'Accept: application/vnd.oci.image.index.v1+json' \ https://registry-1.docker.io/v2/library/alpine/manifests/3.24.3 \ | grep -i -E 'docker-content-digest|^HTTP'HTTP/2 200docker-content-digest: sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aLe digest est le même que celui de imagetools. Ne lancez pas ces commandes avec set -x : le shell afficherait le jeton Bearer.
Comment épingler un socle dans la ligne FROM ?
Section intitulée « Comment épingler un socle dans la ligne FROM ? »La forme à écrire est FROM nom:tag@sha256:<digest d'index>. Docker construit alors sur le contenu désigné par le digest. Le tag, lui, ne sert plus qu'à l'humain qui lit le fichier : il dit de quelle version on parle.
FROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aRUN cat /etc/alpine-releaseUn piège se cache dans cette forme : quand un digest est présent, Docker ignore le tag, et il ne prévient pas. Pour le voir, écrivez un tag faux, 3.22, devant le digest de la 3.24.3 :
FROM alpine:3.22@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aRUN cat /etc/alpine-releaseRangez ce Dockerfile dans un dossier menteur/, comme les autres de la leçon, et construisez-le en désignant ce dossier.
docker build --progress=plain --no-cache -t t:menteur menteur#4 [1/2] FROM docker.io/library/alpine:3.22@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a#4 resolve docker.io/library/alpine:3.22@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a 0.0s done#5 [2/2] RUN cat /etc/alpine-release#5 0.151 3.24.3La construction réussit, et la ligne RUN affiche 3.24.3 : c'est le digest qui a décidé. Le fichier, lui, continue d'annoncer 3.22. Un tag qui ne correspond pas au digest trompe le prochain lecteur sans qu'aucun outil le signale. Après chaque épinglage, vérifiez donc que le tag écrit est bien celui dont vous avez relevé le digest.
Un digest qui n'existe pas n'est pas ignoré, lui. Docker arrête le build et le dit :
ERROR: failed to build: failed to solve: alpine@sha256:0000000000000000000000000000000000000000000000000000000000000000: failed to resolve source metadata for docker.io/library/alpine@sha256:0000000000000000000000000000000000000000000000000000000000000000: docker.io/library/alpine@sha256:0000000000000000000000000000000000000000000000000000000000000000: not foundLe message se lit de droite à gauche : not found est la réponse du registre, failed to resolve source metadata dit que le socle n'a pas pu être résolu, et l'erreur de build en découle. Une faute de frappe dans le digest se repère donc tout de suite.
Que garde un build épinglé quand le tag est déplacé ?
Section intitulée « Que garde un build épinglé quand le tag est déplacé ? »Un build épinglé reste sur l'ancien contenu, même si le tag désigne désormais autre chose. Pour le prouver sans attendre qu'un registre public bouge, il faut un tag que vous déplacez vous-même. Un registre local sur la boucle locale suffit, comme dans la leçon sur Docker Hub : l'adresse 127.0.0.1 est acceptée en HTTP sans réglage.
docker volume create livraison-voldocker run -d --name registre-livraison -p 127.0.0.1:5001:5000 -v livraison-vol:/var/lib/registry registry:3.1.2@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3Construisez un premier socle maison, nommé socle-maison:2026-11, qui ajoute un fichier /v contenant version-1. Rangez ce Dockerfile dans un dossier v1/, le suivant dans v2/ et celui de l'application dans app/. Poussez le socle, puis relevez son digest dans la variable OLD.
FROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aRUN echo version-1 > /vdocker build -q -t 127.0.0.1:5001/socle-maison:2026-11 v1 >/dev/null && docker push 127.0.0.1:5001/socle-maison:2026-11 | tail -2OLD=$(docker buildx imagetools inspect 127.0.0.1:5001/socle-maison:2026-11 --format '{{.Manifest.Digest}}'); echo "OLD=$OLD"f27d9101980d: Pushed2026-11: digest: sha256:68dc2aea13813b3242bdff68332dd2e6c9dc136b15298d22a461e48b6ca112fe size: 855OLD=sha256:68dc2aea13813b3242bdff68332dd2e6c9dc136b15298d22a461e48b6ca112feRefaites la même chose avec un second Dockerfile, rangé dans v2/, qui écrit version-2 dans /v, sous le même tag : c'est le déplacement. Le registre accepte le push, et le tag pointe maintenant vers un autre digest, que vous relevez dans NEW.
docker build -q -t 127.0.0.1:5001/socle-maison:2026-11 v2 >/dev/null && docker push 127.0.0.1:5001/socle-maison:2026-11 | tail -2NEW=$(docker buildx imagetools inspect 127.0.0.1:5001/socle-maison:2026-11 --format '{{.Manifest.Digest}}'); echo "NEW=$NEW"7b229517ed22: Layer already exists2026-11: digest: sha256:6e8876d4f66de01da0b1744bfc0878f8993c858f115f6f4fe84fe42a0fe98772 size: 855NEW=sha256:6e8876d4f66de01da0b1744bfc0878f8993c858f115f6f4fe84fe42a0fe98772Écrivez maintenant l'application sur le socle, avec le digest de l'ancien contenu, supprimez l'image locale du socle pour forcer la résolution, puis construisez en demandant au moteur de revérifier le registre (--pull) et de ne rien réutiliser (--no-cache).
FROM 127.0.0.1:5001/socle-maison:2026-11@sha256:68dc2aea13813b3242bdff68332dd2e6c9dc136b15298d22a461e48b6ca112feRUN cat /v > /vuCMD ["cat","/vu"]docker image rm 127.0.0.1:5001/socle-maison:2026-11docker build --pull --no-cache -t demo-web:1.0 app 2>&1 | grep -E 'FROM|ERROR|resolve'docker run --rm demo-web:1.0#4 [1/2] FROM 127.0.0.1:5001/socle-maison:2026-11@sha256:68dc2aea13813b3242bdff68332dd2e6c9dc136b15298d22a461e48b6ca112fe#4 resolve 127.0.0.1:5001/socle-maison:2026-11@sha256:68dc2aea13813b3242bdff68332dd2e6c9dc136b15298d22a461e48b6ca112fe 0.0s doneversion-1Regardez la dernière ligne : le conteneur affiche version-1, alors que le tag désigne version-2. Le build est resté sur le socle épinglé. Le registre sert toujours l'ancien digest : une requête sur manifests/<ancien digest> répond 200. L'épinglage garantit le contenu, pas sa disponibilité : si le registre cesse un jour de servir ce digest, le build échoue avec l'erreur not found vue plus haut, et c'est préférable à un contenu qui change en silence.
Comment savoir qu'un socle plus récent est publié ?
Section intitulée « Comment savoir qu'un socle plus récent est publié ? »Un socle épinglé vieillit, et c'est voulu : vous décidez quand remonter. Il faut seulement que quelque chose vous prévienne qu'une remontée est possible. Un script de quelques lignes compare le digest épinglé dans le Dockerfile au digest que le registre sert maintenant pour le tag, sans outil tiers. Enregistrez-le sous le nom verifie-socle.sh.
#!/usr/bin/env bash# Compare le digest épinglé dans le Dockerfile au digest que le registre sert pour le tag.set -euo pipefailref=$(awk '/^FROM /{print $2; exit}' "$1")name=${ref%@*}; pinned=${ref#*@}served=$(docker buildx imagetools inspect "$name" --format '{{.Manifest.Digest}}')if [ "$served" = "$pinned" ]; then echo "à jour : $name"; else echo "SOCLE PLUS RÉCENT : $name sert $served, épinglé $pinned"; exit 1; fiLe script lit la première ligne FROM du fichier passé en argument. Il sépare la référence en deux avec ${ref%@*}, qui garde le nom et le tag, et ${ref#*@}, qui garde le digest. Puis il demande au registre quel digest il sert pour nom:tag.
C'est la raison de garder le tag dans le FROM : sans lui, le script n'aurait aucun nom à interroger, et le digest épinglé ne dirait rien sur la suite de la branche.
Le script a des limites. Il ne lit que la première ligne FROM : un Dockerfile multi-étapes demande un script par socle. Un FROM --platform=... lui fait lire l'option à la place de la référence. Et un registre injoignable, ou une limite de pulls atteinte, rend aussi rc=1 : ce code dit « le contrôle a échoué », pas forcément « un socle plus récent existe ». Lisez le message avant de conclure.
Appliqué à l'application de la section précédente, dont le tag a été déplacé :
bash verifie-socle.sh app/Dockerfile; echo "rc=$?"SOCLE PLUS RÉCENT : 127.0.0.1:5001/socle-maison:2026-11 sert sha256:6e8876d4f66de01da0b1744bfc0878f8993c858f115f6f4fe84fe42a0fe98772, épinglé sha256:68dc2aea13813b3242bdff68332dd2e6c9dc136b15298d22a461e48b6ca112ferc=1Le message nomme les deux empreintes, et le code de retour vaut 1. C'est lui qui fait échouer une étape de pipeline : une tâche planifiée qui lance le script cesse d'être verte dès qu'un socle plus récent est publié, et personne n'a besoin de lire un journal pour le savoir.
Le script ne dit pas si la nouvelle version est bonne pour vous, il dit seulement que le socle a bougé ; la décision de remonter, avec les contrôles de l'application, est celle de la leçon sur le choix d'un socle.
Des outils automatisent la même veille et ouvrent la demande de mise à jour à votre place. Le guide sur Renovate et la page sur le maintien à l'échelle des références épinglées en traitent. D'après sa documentation, Renovate épingle par digest : il garde le tag dans la ligne FROM et propose une mise à jour quand le contenu du tag change.
Un digest figé ne reçoit aucun correctif de sécurité : l'image épinglée garde ses vulnérabilités tant que vous ne remontez pas le socle. L'épinglage rend la mise à jour volontaire, il ne la supprime pas, et le script de détection ci-dessus sert à ne pas laisser un socle vieillir sans que quelqu'un l'ait décidé.
Archive ou registre : quelle voie pour livrer hors ligne ?
Section intitulée « Archive ou registre : quelle voie pour livrer hors ligne ? »Une machine sans accès à Internet ne peut pas faire docker pull sur Docker Hub. Il faut donc lui apporter l'image par un autre chemin, physique ou interne. Le support de transfert (clé USB, partage réseau interne) change d'un site à l'autre ; la leçon s'intéresse à ce qu'on transporte. Deux voies existent, et elles diffèrent sur ce point.
Le schéma ci-dessous montre les deux chaînes côte à côte. Regardez les deux cases marquées sha256sum -c : dans les deux voies, la somme se vérifie avant tout chargement, et le reçu voyage par un canal séparé des archives. La voie de gauche transporte un fichier que docker load consomme, produit par docker save. La voie de droite transporte un registre entier, son image et son volume, qu'on relance sur la machine isolée avant d'y tirer l'image par son digest.
L'archive est la voie la plus courte quand on livre une ou deux images vers une machine. Le registre transporté demande plus de gestes, et il convient quand plusieurs machines isolées tirent les mêmes images, ou qu'un outil attend une référence de registre plutôt qu'un fichier.
Comment simuler une machine sans réseau ?
Section intitulée « Comment simuler une machine sans réseau ? »Pour vérifier une livraison, il faut une machine qui ne peut vraiment rien joindre. Si vous n'en avez pas sous la main, un conteneur Docker-dans-Docker démarré avec --network none en joue le rôle. C'est un moyen de démonstration, pas une méthode de livraison : l'option --privileged donne à ce conteneur presque tous les droits sur la machine hôte, et on ne la pose que sur un poste de test.
mkdir -p xferdocker run -d --name machine-isolee --privileged --network none \ -e DOCKER_TLS_CERTDIR= -v "$PWD/xfer:/xfer:ro" \ docker:dind@sha256:9e79b879b177fbd72c34376a3dd3d91747da5db49e1f560f76aea59561471977until docker exec machine-isolee docker info >/dev/null 2>&1; do sleep 2; donedocker exec machine-isolee docker version --format 'server {{.Server.Version}}'docker exec machine-isolee docker info --format 'driver={{.Driver}} status={{.DriverStatus}} images={{.Images}}'server 29.9.2driver=overlayfs status=[[driver-type io.containerd.snapshotter.v1]] images=0La boucle until attend que le démon interne réponde : il lui faut environ 18 secondes après le lancement du conteneur, et un docker exec … docker version lancé aussitôt échoue avec failed to connect to the docker API. L'option -e DOCKER_TLS_CERTDIR= désactive le TLS entre le client et le démon interne : sans risque ici, puisque --network none ferme le conteneur au réseau, et cela évite de gérer des certificats. Le dossier xfer de l'hôte est monté en lecture seule : il joue le support de transfert. Le démon interne est lui aussi un Docker 29.9.2 sur le magasin containerd, avec zéro image.
Vérifiez maintenant que l'isolement est réel, en demandant à ce conteneur de joindre Docker Hub :
# ❌ Par le tag, à dessein : ces trois commandes prouvent l'absence de l'image, pas son identitédocker exec machine-isolee wget -T 3 -qO- https://registry-1.docker.io/v2/docker exec machine-isolee docker pull alpine:3.24.3docker exec machine-isolee docker run --pull never --rm alpine:3.24.3 truewget: bad address 'registry-1.docker.io'Error response from daemon: failed to resolve reference "docker.io/library/alpine:3.24.3": failed to do request: Head "https://registry-1.docker.io/v2/library/alpine/manifests/3.24.3": dial tcp: lookup registry-1.docker.io on 10.10.114.1:53: dial udp 10.10.114.1:53: connect: network is unreachabledocker: Error response from daemon: No such image: alpine:3.24.3
Run 'docker run --help' for more informationTrois messages se lisent ici. bad address dit que le nom du registre ne se résout même pas. L'erreur du pull finit par network is unreachable : c'est l'échec attendu hors ligne, le même par tag et par digest.
Le troisième change la nature de l'échec. Avec --pull never, Docker ne cherche plus de registre et répond No such image, parce que l'image n'est pas dans le magasin local. Cette commande sert de preuve : si elle fonctionne après le chargement, l'image est bien arrivée.
Pour tout le reste de la leçon, les commandes préfixées par docker exec machine-isolee se lancent sur la machine isolée. Sur une vraie machine, retirez ce préfixe.
Comment exporter une image avec docker save ?
Section intitulée « Comment exporter une image avec docker save ? »docker save écrit une archive tar de l'image, avec ses couches et ses métadonnées. Tirez l'image par son digest, redonnez-lui son tag avec docker tag, puis exportez-la : -o nomme le fichier de sortie. Le docker tag n'est pas décoratif, car un pull par digest ne pose pas le tag, et la section sur docker pull tag@digest montre plus bas ce qui arrive sans lui.
docker pull alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9adocker tag alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a alpine:3.24.3docker save -o xfer/alpine.tar alpine:3.24.3ls -l xfer/alpine.tartar tf xfer/alpine.tar-rw------- 1 ansible ansible 3961856 Nov 3 19:03 xfer/alpine.tarblobs/blobs/sha256/blobs/sha256/3da1513d2124248e6e2a526d532ff365833915ce1abe0813bfc5344190827406blobs/sha256/3ec36f3670df56d1b1d4323910cf6747576cb31e5e5d32c64715339876d86f20blobs/sha256/7b229517ed222597277036d94892173207d01a731ab3a88eeb1de2e963a2c788blobs/sha256/964745728d174226819257152a79b34561fe7321472eb9c8e32f461cbc7969c2blobs/sha256/b92841763c5c2140a20eeb2a44e94ebb0e80fd51d9d6b5895ea72fa999525d68blobs/sha256/bcd0699075b7694fe6061b0cf0bc8134b3f900f8d7456021c06b7cbbef460f8bblobs/sha256/be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9ablobs/sha256/c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0dindex.jsonmanifest.jsonoci-layoutL'archive pèse environ 4 Mo, et deux choses se lisent dans cette sortie. Les droits sont -rw------- : seul le compte qui a créé l'archive peut la lire, donc un autre compte chargé du transfert devra d'abord obtenir les droits. Le dossier blobs/sha256/ contient des fichiers nommés par leur empreinte, dont be2656…, l'index, et c26612…, le manifeste amd64 : l'archive embarque la même structure que le registre.
Pour livrer plusieurs images d'un coup, docker save accepte plusieurs noms dans la même commande. La section suivante le fait avec alpine:3.24.3 et busybox:1.37, une fois busybox:1.37 tirée : l'archive deux.tar pèse 6 192 640 octets, soit environ la somme des deux archives séparées, et elle se charge en une seule commande sur la machine isolée.
Pourquoi docker pull tag@digest ne pose-t-il pas le tag ?
Section intitulée « Pourquoi docker pull tag@digest ne pose-t-il pas le tag ? »Si vous tirez une image par tag et par digest (docker pull busybox:1.37@sha256:…), Docker récupère le contenu mais ne crée pas le tag dans le magasin local. Le tag est ignoré, exactement comme dans un FROM : seul le digest compte. Le piège apparaît au moment d'exporter.
docker pull -q busybox:1.37@sha256:a7dbc319b1438115a207e11930dc4bb3e643dcb3ec0bdd694bb36cd8fad289ecdocker images --digests busyboxdocker save -o xfer/bb.tar busybox:1.37docker.io/library/busybox:1.37@sha256:a7dbc319b1438115a207e11930dc4bb3e643dcb3ec0bdd694bb36cd8fad289ecREPOSITORY TAG DIGEST IMAGE ID CREATED SIZEbusybox <none> sha256:a7dbc319b1438115a207e11930dc4bb3e643dcb3ec0bdd694bb36cd8fad289ec a7dbc319b143 2 years ago 6.77MBError response from daemon: No such image: busybox:1.37La colonne TAG affiche <none> : l'image est là, mais sans nom. docker save busybox:1.37 échoue donc avec No such image, ce qui surprend quand on vient de la tirer. La parade tient en une commande, qui pose le tag sur l'image déjà présente :
docker tag busybox@sha256:a7dbc319b1438115a207e11930dc4bb3e643dcb3ec0bdd694bb36cd8fad289ec busybox:1.37docker save -o xfer/deux.tar alpine:3.24.3 busybox:1.37docker save -o xfer/bb.tar busybox:1.37L'archive bb.tar ne contient que busybox:1.37 : la section sur le reçu s'en sert. Le même réflexe vaut pour l'image registry de la voie par registre : tirée par digest, elle doit être retaguée avant d'être exportée.
Quelle plateforme docker save exporte-t-il ?
Section intitulée « Quelle plateforme docker save exporte-t-il ? »docker save sans option exporte toutes les plateformes dont le contenu est présent dans le magasin local, pas toutes celles que l'index annonce. Ce comportement est celui que décrit la documentation de docker image save. Sur un hôte amd64, un docker pull busybox:1.37@sha256:… ne télécharge que la variante amd64 : l'archive de docker save -o xfer/bb.tar busybox:1.37 pèse alors 2 235 392 octets, et son index.json décrit tout l'index alors que seul le manifeste amd64 est dans l'archive.
Tirez ensuite l'autre variante, puis exportez vers un fichier distinct de bb.tar, que la section sur le reçu réutilise.
docker pull -q --platform linux/arm64 busybox@sha256:a7dbc319b1438115a207e11930dc4bb3e643dcb3ec0bdd694bb36cd8fad289ecdocker save -o xfer/bb-multi.tar busybox:1.37ls -l xfer/bb-multi.tarLa même commande produit alors 4 144 640 octets, avec les manifestes amd64 et arm64, que vous exportiez le tag ou la référence busybox@sha256:….
L'option --platform change ce qui est exporté, et elle change aussi l'identité de l'image chargée. Sans elle, l'archive garde l'index : son index.json désigne l'index (be2656… pour alpine:3.24.3), exactement comme dans l'archive de la section précédente. Avec --platform, l'archive est réduite à un seul manifeste : son index.json désigne le manifeste c26612…, et le blob de l'index be2656… n'y figure plus.
docker save --platform linux/amd64 -o xfer/alpine-amd64.tar alpine:3.24.3docker save --platform linux/arm64 -o xfer/alpine-arm64.tar alpine:3.24.3Error response from daemon: no suitable export target found: image with reference alpine:3.24.3 was found but does not provide the specified platform (linux/arm64)La première commande réussit (3 952 640 octets, contre 3 961 856 pour l'archive sans option). La seconde échoue parce que la variante arm64 d'alpine n'a jamais été téléchargée sur cette machine : --platform choisit parmi ce que le magasin local possède, il ne va pas la chercher. Après un docker pull --platform linux/arm64 de busybox, docker save --platform linux/arm64 -o xfer/bb-arm.tar busybox:1.37 fonctionne et produit une archive de 1 915 392 octets.
Une archive faite avec --platform ressemble donc à ce que reçoit un registre après un docker push d'une seule plateforme : une image dont le digest est celui du manifeste, pas celui de l'index. La section suivante montre ce que cela change sur la machine isolée.
Retenez la conséquence pour une livraison. Sachez pour quelle architecture est la machine isolée, et exportez sans --platform si vous épinglez vos FROM sur le digest d'index : l'archive garde l'index, et exporte les plateformes présentes dans le magasin. Une archive qui couvre plusieurs architectures s'obtient en tirant chaque variante avant l'export. Exportez avec --platform seulement pour réduire l'archive, et acceptez alors que l'image chargée porte le digest du manifeste. La réduction ne vaut que si le magasin local porte plusieurs plateformes : avec la seule plateforme amd64 d'alpine, l'écart n'est que de 9 216 octets (3 952 640 contre 3 961 856). Le chargement d'une archive multi-architecture n'a pas été rejoué dans cette leçon. La construction des variantes est celle de la leçon sur le multi-architecture.
Que préserve docker load sur la machine isolée ?
Section intitulée « Que préserve docker load sur la machine isolée ? »Sur la machine isolée, on compare d'abord l'empreinte de l'archive à celle qui est partie, puis on la charge. Dans la mesure, sha256sum xfer/alpine.tar sur la machine connectée et sha256sum /xfer/alpine.tar sur la machine isolée donnent la même somme, 6fdb2c195eee3b0c1ccf5198c506c3c1ec8a360529c374849d6cc655404b191c. Ici la comparaison se fait à l'œil ; la section sur la vérification de l'archive l'automatise avec un reçu et sha256sum -c.
docker exec machine-isolee sha256sum /xfer/alpine.tardocker exec machine-isolee docker load -i /xfer/alpine.tardocker exec machine-isolee docker images --digestsdocker exec machine-isolee docker image inspect alpine:3.24.3 --format 'Id={{.Id}} RepoDigests={{.RepoDigests}}'docker exec machine-isolee docker run --rm --pull never alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a cat /etc/alpine-releasedocker exec machine-isolee docker run --rm --pull never alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a cat /etc/alpine-release6fdb2c195eee3b0c1ccf5198c506c3c1ec8a360529c374849d6cc655404b191c /xfer/alpine.tarLoaded image: alpine:3.24.3REPOSITORY TAG DIGEST IMAGE ID CREATED SIZEalpine 3.24.3 sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a be2656061b53 12 days ago 13MBId=sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a RepoDigests=[alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a]3.24.33.24.3Regardez trois points. Le tag arrive avec l'image : Loaded image le nomme. L'identifiant Id est le digest de l'index, celui que vous aviez épinglé, et RepoDigests le confirme : sur ce magasin containerd, le chargement d'une archive faite sans --platform ne change pas l'identité. Enfin, l'image se lance avec --pull never, donc sans aucun accès réseau, par son tag épinglé (tag@sha256:…) comme par son digest seul.
Une image construite localement se comporte de même : son Id, ses RepoDigests et ses couches (RootFS.Layers) sont identiques avant docker save et après docker load. Cette identité préservée vaut pour le magasin containerd de la leçon, et pour une archive faite sans --platform. La leçon sur l'export sans démon avec Skopeo montre une voie qui change cette identité.
Que devient l'identité d'une archive faite avec --platform ?
Section intitulée « Que devient l'identité d'une archive faite avec --platform ? »Chargez l'archive alpine-amd64.tar de la section précédente sur la machine isolée, par-dessus l'image de alpine.tar déjà chargée : les deux portent le nom alpine:3.24.3, donc la nouvelle archive remplace l'image existante (comportement mesuré avec une archive de Skopeo dans la leçon sur l'export sans démon avec Skopeo ; la sortie ci-dessous vient d'une machine vide). Lisez ensuite l'identité de l'image et tentez de la lancer par le digest d'index.
docker exec machine-isolee docker load -i /xfer/alpine-amd64.tardocker exec machine-isolee docker image inspect alpine:3.24.3 --format 'Id={{.Id}} RepoDigests={{.RepoDigests}}'docker exec machine-isolee docker run --rm --pull never alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a cat /etc/alpine-releasedocker exec machine-isolee docker run --rm --pull never alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a cat /etc/alpine-releaseLoaded image: alpine:3.24.3Id=sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d RepoDigests=[alpine@sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d]docker: Error response from daemon: No such image: alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
Run 'docker run --help' for more informationdocker: Error response from daemon: No such image: alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
Run 'docker run --help' for more informationRegardez l'Id : c'est le digest du manifeste amd64 (c26612…), et non celui de l'index (be2656…). Les deux lancements par le digest d'index échouent avec No such image (code 125), alors que l'image est bien chargée : elle porte simplement un autre digest.
Un build dont le FROM est épinglé sur l'index échoue de la même façon, avec une erreur qui ressemble à une panne de réseau, parce que Docker cherche alors le digest d'index sur le registre :
#2 ERROR: failed to do request: Head "https://registry-1.docker.io/v2/library/alpine/manifests/sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a": dial tcp: lookup registry-1.docker.io on 10.10.114.1:53: dial udp 10.10.114.1:53: connect: network is unreachableAvec une archive faite sans --platform (alpine.tar), le même build réussit, comme les deux lancements --pull never. Le choix se résume donc à ceci : sans --platform, le FROM épinglé sur l'index se résout ; avec --platform, il faut épingler le FROM sur le digest du manifeste, celui que la première entrée Name de docker buildx imagetools inspect affiche, et dont l'archive porte le contenu.
Le chargement de l'archive --platform a donc remplacé l'image venue de alpine.tar : une archive chargée sous un nom déjà présent prend la place de celle qui le portait. Les sections qui suivent s'appuient sur le digest d'index : retirez cette image et rechargez l'archive faite sans option.
docker exec machine-isolee docker rmi -f alpine:3.24.3docker exec machine-isolee docker load -i /xfer/alpine.tarUntagged: alpine:3.24.3Deleted: sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0dLoaded image: alpine:3.24.3Comment vérifier l'archive avant de la charger ?
Section intitulée « Comment vérifier l'archive avant de la charger ? »Le code de retour de docker load ne prouve pas que l'archive est intacte. Une archive altérée d'un seul octet, de même taille, se charge avec le code 0, et l'image apparaît dans docker images. La documentation de docker image load ne décrit aucune vérification d'intégrité : la somme de contrôle, calculée avant le départ et vérifiée avant le chargement, est le contrôle fiable contre une archive abîmée (sa limite face à une altération volontaire est donnée plus bas).
Sur la machine connectée, écrivez le reçu : ici dans xfer, pour la démonstration. Sur la machine isolée, vérifiez-le avant tout docker load.
(cd xfer && sha256sum bb.tar > bb.tar.sha256 && sha256sum -c bb.tar.sha256)bb.tar: OKPour voir ce que la vérification attrape, corrigez un octet d'une couche dans une copie de l'archive. Le script extrait l'archive, change l'octet 1000 d'une couche, puis réarchive. Le dossier extrait est en lecture seule, d'où le chmod. Comme busybox est tiré par digest, la couche modifiée (68fe9bff…) porte chez vous le même nom que dans la mesure.
mkdir -p alt && cd alt && tar xf ../xfer/bb.tar && cd ..chmod -R u+w altpython3 - <<'PY'p="alt/blobs/sha256/68fe9bff2ad4ad46da3e58db03faa64eb73c0ff16d546b0c42539eaa9854cb7f"b=bytearray(open(p,"rb").read()); b[1000]^=0xff; open(p,"wb").write(b)PY(cd alt && tar cf ../xfer/alteree.tar .)(cd xfer && cp bb.tar.sha256 alteree.tar.sha256 && sed -i 's/ bb.tar/ alteree.tar/' alteree.tar.sha256 && sha256sum -c alteree.tar.sha256; echo rc=$?)alteree.tar: FAILEDsha256sum: WARNING: 1 computed checksum did NOT matchrc=1La somme refuse l'archive. Voyez maintenant ce que fait docker load sur la machine isolée si on lui donne quand même cette archive. La sortie montrée suppose que busybox:1.37 n'a pas encore été chargé sur cette machine, ce qui est le cas à ce stade de la leçon, où seule alpine y a été chargée ; avec busybox déjà présent, le résultat n'a pas été mesuré.
docker exec machine-isolee docker load -i /xfer/alteree.tar; echo rc=$?docker exec machine-isolee docker run --rm --pull never busybox:1.37@sha256:a7dbc319b1438115a207e11930dc4bb3e643dcb3ec0bdd694bb36cd8fad289ec echo okLoaded image: busybox:1.37Error unpacking image busybox:1.37: apply layer error for "docker.io/library/busybox:1.37": failed to extract layer sha256:5a9bfd996c6d49c6c3cb4ddfb76e8d09cf16b6b7cf6601d1f2dffbdbc3edb5d4: NotFound: failed to get reader from content store: content digest sha256:68fe9bff2ad4ad46da3e58db03faa64eb73c0ff16d546b0c42539eaa9854cb7f: not foundrc=0docker: Error response from daemon: apply layer error for "": failed to extract layer sha256:5a9bfd996c6d49c6c3cb4ddfb76e8d09cf16b6b7cf6601d1f2dffbdbc3edb5d4: NotFound: failed to get reader from content store: content digest sha256:68fe9bff2ad4ad46da3e58db03faa64eb73c0ff16d546b0c42539eaa9854cb7f: not found
Run 'docker run --help' for more informationLe chargement affiche Loaded image puis une erreur d'extraction, et rend rc=0. L'image est listée, mais docker run échoue. Le message content digest … not found montre que le magasin a refusé le blob altéré : un contrôle par empreinte existe bel et bien, mais son échec ne passe pas dans le code de retour de docker load. Le reçu reste donc nécessaire, et il aurait évité de découvrir le problème sur la machine de production.
Le reçu protège d'une archive abîmée. Contre une altération volontaire, il n'a de valeur que s'il voyage par un autre canal que l'archive elle-même : un attaquant qui change l'archive change aussi un reçu rangé à côté. Pour une garantie d'origine, la signature de l'image va plus loin, comme le montre le guide sur Cosign.
Deux autres échecs se reconnaissent à leur message. Une archive tronquée (ici 2 000 000 octets d'une archive qui en comptait plus) se refuse nettement, avec un code non nul :
failed to ingest "blobs/sha256/7b229517ed222597277036d94892173207d01a731ab3a88eeb1de2e963a2c788": short read: expected 3853109 bytes but got 1911424: unexpected EOFEt une archive de la mauvaise architecture se charge sans erreur. L'échec arrive au lancement : docker run avertit que la plateforme de l'image (linux/arm64/v8) ne correspond pas à celle de l'hôte (linux/amd64/v3), puis le premier programme lancé échoue.
WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v3) and no specific platform was requestedexec /bin/uname: exec format errorexec format error signifie que le noyau ne sait pas exécuter ce binaire : revenez à la plateforme exportée.
Comment livrer par un registre transporté ?
Section intitulée « Comment livrer par un registre transporté ? »La voie par registre transporte un registre de la machine connectée vers la machine isolée : l'image du registre, et son volume, le dossier où il range les images poussées. Une fois relancé sur la machine isolée, il sert les images comme un registre ordinaire, et on les tire par digest.
Commencez par alimenter le registre de la machine connectée. Un docker push d'une image dont seule la plateforme locale est présente n'envoie que cette plateforme, et le registre reçoit alors le digest du manifeste amd64, pas celui de l'index. Poussez la copie du socle et l'application, et lisez les digests reçus :
docker tag alpine:3.24.3 127.0.0.1:5001/reference/alpine:3.24.3docker push 127.0.0.1:5001/reference/alpine:3.24.3 | tail -1docker tag demo-web:1.0 127.0.0.1:5001/demo-web:1.0docker push 127.0.0.1:5001/demo-web:1.0 | tail -1curl -s http://127.0.0.1:5001/v2/_catalog sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a -> sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d1.0: digest: sha256:afba17b4f22057ebb22ee737fb4ff0c6b3dfad0434ec348ea003bad9525fc9c5 size: 855{"repositories":["demo-web","reference/alpine","socle-maison"]}La première ligne dit que l'index be26… est devenu le manifeste c266… : le digest épinglé sur Docker Hub n'existe donc pas dans ce registre, qui ne connaît que le manifeste amd64. Un FROM épinglé sur l'index de Hub ne se résoudra pas sur cette copie. L'image construite localement, elle, garde son index (afba17b4…). Pour recopier un index complet vers un registre, skopeo copy --all copie l'index et toutes ses variantes, alors que sans elle seule la plateforme de la machine est copiée. La page Cache BuildKit en CI et promotion d'image entre registres mesure cette copie vers un registre ; le guide Skopeo mesure --all vers une archive.
Transportez ensuite le registre. Arrêtez-le d'abord, pour que son volume ne bouge pas pendant la copie. Archivez le volume avec un conteneur jetable, exportez l'image registry, et écrivez un reçu pour les deux fichiers.
docker stop registre-livraisonumask 077docker run --rm -v livraison-vol:/v:ro alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a tar cf - -C /v . > xfer/registre-data.tardocker tag registry@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3 registry:3.1.2docker save -o xfer/registry-image.tar registry:3.1.2(cd xfer && sha256sum registre-data.tar registry-image.tar > transfert.sha256 && cat transfert.sha256)Le volume pèse environ 4 Mo et l'image du registre environ 21 Mo. Après le docker stop, docker ps -a montre registre-livraison en Exited (2) : c'est le code de sortie du registre arrêté par le signal, pas un échec. Le docker tag répond au piège de pull tag@digest vu plus haut : l'image registry a été tirée par digest, donc sans tag.
La redirection > du shell crée registre-data.tar avec les droits de votre umask, souvent lisibles par les autres comptes : le umask 077 placé juste avant la commande réserve le fichier à votre compte, comme l'archive de docker save. Il reste actif dans ce terminal jusqu'à sa fermeture. Le reçu transfert.sha256 est rangé dans xfer pour la démonstration, mais il se transmet à part des archives, par un autre canal, sans quoi il ne protège pas d'une altération volontaire.
Sur la machine isolée, vérifiez les sommes, chargez l'image du registre, restaurez le volume, puis relancez le registre. Le conteneur qui restaure le volume emploie l'image alpine : elle doit déjà être chargée sur la machine isolée, ce qui est le cas après le docker load de alpine.tar plus haut, car aucun pull n'est possible.
docker exec machine-isolee sh -c 'cd /xfer && sha256sum -c transfert.sha256'docker exec machine-isolee docker load -i /xfer/registry-image.tardocker exec machine-isolee docker volume create regvoldocker exec machine-isolee docker run --rm -v regvol:/v -v /xfer:/xfer:ro alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a tar xf /xfer/registre-data.tar -C /vdocker exec machine-isolee docker run -d --name registre-livraison -p 127.0.0.1:5000:5000 -v regvol:/var/lib/registry registry:3.1.2@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3docker exec machine-isolee wget -qO- http://127.0.0.1:5000/v2/_catalogdocker exec machine-isolee wget -qO- http://127.0.0.1:5000/v2/demo-web/tags/listregistre-data.tar: OKregistry-image.tar: OKLoaded image: registry:3.1.2{"repositories":["demo-web","reference/alpine","socle-maison"]}
{"name":"demo-web","tags":["1.0"]}Le catalogue est identique à celui de la machine connectée : le registre a bien repris son contenu. Reste à tirer l'image par son digest et à la lancer sans réseau. Le --pull never est ici la preuve : il interdit tout accès, et l'image doit déjà être là.
docker exec machine-isolee docker pull 127.0.0.1:5000/demo-web@sha256:afba17b4f22057ebb22ee737fb4ff0c6b3dfad0434ec348ea003bad9525fc9c5 | tail -2docker exec machine-isolee docker run --rm --network none --pull never 127.0.0.1:5000/demo-web@sha256:afba17b4f22057ebb22ee737fb4ff0c6b3dfad0434ec348ea003bad9525fc9c5Status: Downloaded newer image for 127.0.0.1:5000/demo-web@sha256:afba17b4f22057ebb22ee737fb4ff0c6b3dfad0434ec348ea003bad9525fc9c5127.0.0.1:5000/demo-web@sha256:afba17b4f22057ebb22ee737fb4ff0c6b3dfad0434ec348ea003bad9525fc9c5version-1La sortie version-1 vient du conteneur lancé sans réseau, depuis une image tirée d'un registre qui n'existait pas sur cette machine avant le chargement du volume. Deux erreurs se reconnaissent à leur message. Un digest que le registre ne possède pas, ou un dépôt qui n'existe pas, répondent not found :
Error response from daemon: failed to resolve reference "127.0.0.1:5000/demo-web@sha256:0000000000000000000000000000000000000000000000000000000000000000": 127.0.0.1:5000/demo-web@sha256:0000000000000000000000000000000000000000000000000000000000000000: not foundError response from daemon: failed to resolve reference "127.0.0.1:5000/inconnu:1.0": 127.0.0.1:5000/inconnu:1.0: not foundCe registre reste en HTTP sur la boucle locale. Servi à d'autres machines, il doit passer en TLS avec authentification, comme dans la leçon sur le registre de confiance.
Pourquoi un miroir pull-through n'est-il pas une solution hors ligne ?
Section intitulée « Pourquoi un miroir pull-through n'est-il pas une solution hors ligne ? »Un miroir pull-through est un registre qui relaie un registre amont et garde une copie de ce qu'on lui demande. Le démarrer avec REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io donne un miroir de Docker Hub. En ligne, il fonctionne : un docker pull d'une image par digest réussit, et son cache grossit. Mais hors ligne il ne reste pas démarré : avec registry 3.1.2, il contacte l'amont au lancement et s'arrête en panique.
docker exec machine-isolee docker volume create miroirdocker exec machine-isolee docker run -d --name miroir -p 127.0.0.1:5001:5000 -v miroir:/var/lib/registry -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io registry:3.1.2@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3sleep 15docker exec machine-isolee docker logs miroir 2>&1 | grep '^panic'docker exec machine-isolee docker pull 127.0.0.1:5001/library/busybox:1.37panic: Get "https://registry-1.docker.io/v2/": dial tcp: lookup registry-1.docker.io on 10.10.114.1:53: read udp 172.17.0.4:45198->10.10.114.1:53: i/o timeoutError response from daemon: failed to resolve reference "127.0.0.1:5001/library/busybox:1.37": failed to do request: Head "https://127.0.0.1:5001/v2/library/busybox/manifests/1.37": dial tcp 127.0.0.1:5001: connect: connection refusedLa première ligne est le message de panique du miroir : il contacte l'amont à son démarrage, n'obtient aucune réponse (délai dépassé sur la résolution du nom), panique et s'arrête. Quelques secondes après le lancement, le conteneur passe de Up à Exited (2).
Le pull échoue donc de deux façons. Tant que le conteneur est Up, il répond connection reset by peer ; une fois arrêté, il répond connection refused, puisque plus rien n'écoute sur le port. C'est cette seconde erreur que montre la sortie ci-dessus, relevée après le sleep 15. Un miroir pull-through accélère les pulls et réduit les appels vers Hub tant que le réseau fonctionne, mais il ne remplace pas une livraison hors ligne.
Son cache, en revanche, est un simple volume de registre. Les étapes ci-dessous se reproduisent en exercice, sur le modèle de la section précédente (la sortie montrée plus bas vient de ce déroulé) :
- démarrez le même miroir côté connecté, dans un conteneur
miroir-connectepublié avec-p 127.0.0.1:5003:5000, dont le volumemiroir-cacheest monté sur/var/lib/registry; - tirez-y l'image par son digest, avec
docker pull 127.0.0.1:5003/library/hello-world@sha256:c567b231f65614688711a75f31518a064258a51d442117a3e771a3a8fdba78ef: le miroir la télécharge depuis Docker Hub et la garde dans son volume ; - arrêtez-le, puis archivez son volume comme celui de la section précédente ;
- restaurez l'archive dans le volume
miroirde la machine isolée ; - retirez côté connecté le conteneur
miroir-connecte, son volumemiroir-cacheet l'image tirée à l'étape 2, que le nettoyage final traite aussi.
Servi par un registre ordinaire, sans la variable REGISTRY_PROXY_REMOTEURL, ce volume répond :
docker exec machine-isolee docker rm -f miroirdocker exec machine-isolee docker run -d --name miroir-nu -p 127.0.0.1:5002:5000 -v miroir:/var/lib/registry registry:3.1.2@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3docker exec machine-isolee docker pull 127.0.0.1:5002/library/hello-world@sha256:c567b231f65614688711a75f31518a064258a51d442117a3e771a3a8fdba78efdocker exec machine-isolee docker run --rm --pull never --network none 127.0.0.1:5002/library/hello-world@sha256:c567b231f65614688711a75f31518a064258a51d442117a3e771a3a8fdba78efStatus: Downloaded newer image for 127.0.0.1:5002/library/hello-world@sha256:c567b231f65614688711a75f31518a064258a51d442117a3e771a3a8fdba78ef127.0.0.1:5002/library/hello-world@sha256:c567b231f65614688711a75f31518a064258a51d442117a3e771a3a8fdba78ef
Hello from Docker!Le cache d'un miroir se transporte donc comme n'importe quel volume de registre. Il ne contient que les images déjà demandées à ce miroir : ce qu'on n'a jamais tiré en ligne n'y est pas. La documentation de Distribution précise aussi que le miroir supprime périodiquement le vieux contenu de son cache : un volume copié longtemps après le dernier pull peut avoir perdu des images.
Que faire quand la livraison hors ligne échoue ?
Section intitulée « Que faire quand la livraison hors ligne échoue ? »Chaque échec de la leçon a un message qui le désigne. Cherchez le vôtre dans la première colonne.
| Message ou symptôme | Cause | Geste |
|---|---|---|
failed to resolve source metadata … not found dans un build, sur une machine qui joint le registre | Le registre répond, mais il ne possède pas le digest du FROM | Relever le digest d'index avec docker buildx imagetools inspect --format '{{.Manifest.Digest}}' |
La ligne RUN cat /etc/alpine-release affiche une autre version que le tag du FROM | Le tag est ignoré quand un digest est présent | Corriger le tag pour qu'il corresponde au digest épinglé |
docker save busybox:1.37 : No such image après un pull par digest | pull tag@digest ne pose pas le tag | docker tag busybox@sha256:… busybox:1.37, puis docker save |
no suitable export target found … does not provide the specified platform | La plateforme demandée à --platform n'est pas dans le magasin local | La tirer d'abord avec docker pull --platform |
failed to ingest … short read … unexpected EOF au docker load | Archive tronquée pendant le transfert | Refaire le transfert, puis sha256sum -c |
Error unpacking image … content digest … not found avec rc=0 | Archive altérée : docker load ne le signale pas par son code | Vérifier le reçu sha256sum -c avant tout chargement |
No such image: alpine@sha256:<digest d'index> après le chargement d'une archive faite avec --platform | L'archive ne porte que le manifeste : l'image chargée a le digest du manifeste, pas celui de l'index | Refaire l'archive sans --platform, ou épingler le FROM sur le digest du manifeste |
failed to do request: Head "…/manifests/sha256:<digest d'index>" … network is unreachable au build d'un FROM épinglé sur l'index, après le chargement d'une archive faite avec --platform | L'image chargée porte le digest du manifeste : le moteur ne trouve pas l'index en local et le cherche sur le registre, injoignable | Refaire l'archive sans --platform, ou épingler le FROM sur le digest du manifeste |
exec format error au lancement d'une image livrée | Archive d'une autre architecture que la machine | Exporter la bonne plateforme (tirer cette variante, puis --platform) |
network is unreachable au docker pull (pour un build, voir la ligne sur le digest d'index) | La machine n'a pas de réseau, c'est attendu | Livrer par archive ou registre, et lancer avec --pull never |
No such image avec --pull never | L'image n'a pas été chargée | docker load, ou docker pull depuis le registre livré |
panic: Get "https://registry-1.docker.io/v2/" dans les journaux d'un registre, puis connection reset by peer ou connection refused au docker pull | Un miroir pull-through contacte l'amont au démarrage, panique et s'arrête (Exited (2)) | Servir le volume de cache par un registre ordinaire |
You have reached your pull rate limit (HTTP 429) au docker pull ou au script verifie-socle.sh | Docker Hub limite les pulls d'une adresse ou d'un compte | S'authentifier ou attendre, voir Publier et récupérer une image |
failed to resolve reference … : not found sur le registre livré | Digest ou dépôt absent du registre transporté | Lire _catalog et tags/list pour voir ce qu'il contient |
Un échec à l'étape du docker pull par digest, après une livraison par registre, peut venir d'un digest copié depuis Docker Hub : rappelez-vous que docker push d'une image locale n'envoie que le manifeste de la plateforme locale, dont le digest diffère de celui de l'index.
Comment nettoyer après les essais ?
Section intitulée « Comment nettoyer après les essais ? »La leçon laisse sur votre poste un conteneur privilégié, un registre, un volume, des images et des archives. Retirez d'abord la machine isolée : ses volumes regvol et miroir, ses conteneurs registre-livraison et miroir-nu et les images qu'elle a chargées vivent tous dans le démon qu'elle contient. L'option -v de docker rm -fv compte ici : l'image docker:dind déclare /var/lib/docker comme volume, et Docker en crée un volume anonyme qui porte tout le démon interne et les données de la livraison. Sans -v, le volume anonyme du démon reste sur votre poste.
Retirez ensuite le registre de la machine connectée et son volume livraison-vol, puis les images de la leçon et les dossiers de transfert. Si une commande docker image rm répond qu'une image n'existe pas, c'est qu'elle a déjà disparu : les autres noms de la liste sont quand même traités, mais la commande sort en code 1. Si elle répond qu'une image est utilisée par un conteneur, supprimez d'abord ce conteneur avec docker rm -f.
docker rm -fv machine-isoleedocker rm -f registre-livraisondocker volume rm livraison-vol# seulement si vous avez fait l'exercice du miroir côté connectédocker rm -f miroir-connectedocker volume rm miroir-cachedocker image rm 127.0.0.1:5003/library/hello-world@sha256:c567b231f65614688711a75f31518a064258a51d442117a3e771a3a8fdba78efdocker image rm t:menteur demo-web:1.0 127.0.0.1:5001/demo-web:1.0 127.0.0.1:5001/reference/alpine:3.24.3docker image rm busybox:1.37 registry:3.1.2 alpine:3.24.3docker image rm docker:dind@sha256:9e79b879b177fbd72c34376a3dd3d91747da5db49e1f560f76aea59561471977rm -rf xfer alt v1 v2 app menteur verifie-socle.shLes images alpine, busybox et registry servent peut-être à d'autres usages sur votre machine : gardez-les si c'est le cas. Vérifiez avec docker image ls qu'il ne reste aucune image <none> issue de la leçon : une image tirée par digest sans tag, ou le socle version-1 devenu orphelin quand vous l'avez reconstruit sous le même tag. Supprimez-la alors par son identifiant. L'image 127.0.0.1:5001/socle-maison:2026-11 n'apparaît pas dans la liste : la section sur le tag déplacé l'a déjà supprimée.
Le dernier rm -rf retire aussi les dossiers v1/, v2/, app/ et menteur/ et le script verifie-socle.sh, écrits au fil des sections. L'option -f évite une erreur pour un dossier que vous n'avez pas créé, alt/ par exemple si vous avez sauté la démonstration d'altération. Relisez la liste avant de lancer la commande : elle ne demande aucune confirmation.
Mettre en pratique
Section intitulée « Mettre en pratique »Le premier lab vous demande de garder un build sur le socle prévu alors que le tag de ce socle change dans un registre local : la validation reconstruit l'image après un changement de source et contrôle le socle effectivement livré. Le second vous demande de préparer une image publiée pour une machine sans réseau, avec sa somme d'intégrité : la validation contrôle que l'image se récupère hors ligne sans dépendre du cache Docker local.
À retenir
Section intitulée « À retenir »Le digest d'index d'un tag se lit avec docker buildx imagetools inspect <tag> --format '{{.Manifest.Digest}}', sans tirer l'image, et s'écrit dans le FROM sous la forme nom:tag@sha256:…. Quand un digest est présent, Docker ignore le tag sans avertir : un tag qui ne correspond pas au digest trompe le lecteur, pas le build.
Un build épinglé reste sur l'ancien contenu quand le tag est déplacé, tant que le registre sert encore ce digest. Pour savoir qu'un socle plus récent existe, comparez le digest épinglé à celui que le registre sert pour le tag, ce qui suppose de garder le tag dans le FROM, et faites échouer la tâche par un code de retour non nul.
docker pull tag@digest ne pose pas le tag : sans docker tag, un docker save par nom échoue. docker save sans option garde l'index et exporte les plateformes présentes dans le magasin local. --platform ne choisit que parmi elles et réduit l'archive à un manifeste : l'image chargée porte alors le digest du manifeste, et un FROM épinglé sur l'index ne s'y résout plus. Pour garder le digest d'index, exportez sans --platform. L'archive est créée en -rw-------.
Le code de retour de docker load ne prouve pas l'intégrité : une archive altérée se charge avec le code 0. On écrit un reçu sha256sum avant le transfert et on le vérifie avec sha256sum -c avant le chargement. Ce reçu protège d'une archive abîmée ; contre une altération volontaire, il doit voyager par un autre canal que l'archive.
Livrer par registre revient à transporter l'image registry et son volume, arrêté pendant la copie, puis à tirer l'image par digest sur la machine isolée. Un docker push d'une image locale n'envoie que la plateforme locale, dont le digest n'est pas celui de l'index. --pull never prouve que l'exécution n'a pas besoin du réseau.
Un miroir pull-through ne remplace pas une livraison hors ligne : il s'arrête en panique peu après son lancement quand son amont est injoignable. Son cache, servi par un registre ordinaire, répond, mais il ne contient que ce qui avait été demandé en ligne. Le conteneur --privileged --network none qui simule la machine isolée est un moyen de démonstration, pas une méthode de livraison.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de cette leçon est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Exporter une image sans démon avec Skopeo
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications