Pour livrer une image à une machine sans réseau, docker save exige un démon Docker qui possède déjà l'image. Skopeo copie une image d'un registre vers une archive sans démon et sans passer par le magasin local. Cette leçon, pour qui sait déjà livrer une image par docker save et docker load, montre comment écrire cette copie, quel format d'archive choisir, et surtout ce que la voie Skopeo change à l'identité de l'image : un FROM épinglé sur le digest d'index ne s'y résout plus. Les sorties viennent de Skopeo 1.22.3, de Docker Engine 29.9.2 et de Debian 13, avec le magasin containerd.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Exporter une image par digest vers une archive
oci-archive:avecskopeo copy - Écrire la source sans tag et la destination avec un nom complet
- Choisir
oci-archive:plutôt quedocker-archive:en comparant les tailles - Dire ce que le chargement d'une archive Skopeo change à l'identité de l'image, et à un
FROMépinglé sur le digest d'index - Choisir la plateforme exportée avec
--override-arch - Diagnostiquer un export qui échoue à partir de son message
Prérequis
Section intitulée « Prérequis »- Épingler un socle, et livrer hors ligne : vous savez épingler un
FROMpar digest, exporter pardocker save, vérifier un reçusha256sumet charger sur une machine isolée. Cette leçon ne les réexplique pas. - La machine isolée de la leçon précédente, un conteneur
machine-isoleesans réseau, avecalpine:3.24.3chargé depuisalpine.tarproduite pardocker savesans--platform, et le dossierxfermonté dedans. Si vous l'avez nettoyée, recréez-la avec la section « Comment simuler une machine sans réseau ? » de la leçon précédente, puis exportez et chargezalpine:3.24.3comme dans sa section surdocker save. - Le guide sur Skopeo présente l'outil dans son ensemble (inspection, copie entre registres, synchronisation). Cette leçon n'en reprend que l'export vers une archive pour
docker load. - Un Docker Engine 29.9.2 sur la machine connectée.
Comment lancer Skopeo sans démon Docker ?
Section intitulée « Comment lancer Skopeo sans démon Docker ? »Skopeo est un outil en ligne de commande qui copie une image entre deux emplacements (un registre, une archive, un dossier) sans démon. Pour exporter une image, il sert sur un poste où l'on ne veut ni solliciter un démon, ni ajouter une image au magasin local. La voie a un coût : un outil de plus à amener, une écriture de référence différente de celle de Docker, et une identité d'image qui change au chargement. docker save reste donc la voie par défaut.
Dans la démonstration, Skopeo 1.22.3 se lance par l'image de l'éditeur, épinglée par son digest pour figer sa version. Cela emploie le démon Docker : la démonstration elle-même n'est pas sans démon. Sur un poste d'export sans Docker, on installe le binaire. Debian 13 propose skopeo 1.18.0+ds1-1+b5, plus ancien que la 1.22.3 : les mesures de cette leçon n'ont pas été refaites avec lui.
Tirez l'image de l'éditeur par son digest, créez un dossier de sortie réservé à votre compte, puis lancez la copie dans un conteneur jetable.
docker pull quay.io/skopeo/stable:v1.22.3@sha256:2681eccb8b02c495b013c2669cbf7f41d5c0b0b0e644e7c69bd3ea9bcb20641fmkdir sk && chmod 700 skdocker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD/sk:/out" \ quay.io/skopeo/stable:v1.22.3@sha256:2681eccb8b02c495b013c2669cbf7f41d5c0b0b0e644e7c69bd3ea9bcb20641f \ copy docker://docker.io/library/alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a \ oci-archive:/out/alpine-oci.tar:docker.io/library/alpine:3.24.3ls -l skGetting image source signaturesCopying blob sha256:7b229517ed222597277036d94892173207d01a731ab3a88eeb1de2e963a2c788Copying config sha256:b92841763c5c2140a20eeb2a44e94ebb0e80fd51d9d6b5895ea72fa999525d68Writing manifest to image destinationtotal 3772-rw-r--r-- 1 ansible ansible 3860992 Nov 3 20:14 alpine-oci.tarLe conteneur tourne avec votre identité (--user), pour que l'archive vous appartienne, et -e HOME=/tmp lui donne un dossier d'accueil. Sans --user, la même copie réussit, mais l'archive appartient à root : mesurée dans un second essai, elle porte root root dans ls -l. Le dossier sk de l'hôte est monté en /out. Le conteneur ne reçoit pas le socket du démon : Skopeo n'a aucun moyen de parler à Docker. La ligne Getting image source signatures est l'étape où Skopeo lit les signatures éventuelles de la source : le manuel de skopeo copy décrit la commande comme la copie d'une image avec ses manifestes, ses couches et ses signatures.
L'image de l'éditeur porte le tag v1.22.3 et le digest de son index, celui que le dépôt quay.io/skopeo/stable associait à ce tag le 2026-11-04. L'éditeur republie ce tag, donc seul le digest fige l'image que vous tirez.
Comment écrire la source et la destination d'un skopeo copy ?
Section intitulée « Comment écrire la source et la destination d'un skopeo copy ? »La commande prend deux arguments, la source puis la destination, et chacun commence par un transport : un préfixe qui dit où l'image se trouve. docker:// désigne un registre. oci-archive: désigne un fichier d'archive.
La destination oci-archive:/out/alpine-oci.tar:docker.io/library/alpine:3.24.3 se lit en trois parties : le transport, le chemin du fichier, puis le nom que l'image portera dans l'archive. Skopeo l'écrit dans l'annotation org.opencontainers.image.ref.name de l'index de l'archive, et c'est ce nom que docker load annoncera au chargement.
Regardez la source : la forme nom:tag@sha256:… du FROM ne vaut pas pour Skopeo. Avec le tag et le digest ensemble, il refuse : la bibliothèque containers/image ne sait pas demander une image par un tag et un digest à la fois, et son code refuse cette forme.
time="2026-11-03T20:14:50Z" level=fatal msg="Invalid source name docker://docker.io/library/alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a: Docker references with both a tag and digest are currently not supported"Écrivez la source avec le digest seul, docker://docker.io/library/alpine@sha256:…, comme ci-dessus. C'est l'inverse de docker pull et du FROM, qui acceptent tag@digest. Le nom alpine:3.24.3 va dans la destination, là où il servira à docker load.
Quel format d'archive choisir avec Skopeo ?
Section intitulée « Quel format d'archive choisir avec Skopeo ? »Skopeo sait écrire deux formats d'archive, et ils ne se valent pas. oci-archive: suit la disposition OCI et garde les couches compressées. docker-archive: écrit une archive au format Docker, dont la couche est décompressée, et qui porte une image par copie, sans index multi-architecture. Le tableau donne les tailles mesurées pour alpine:3.24.3 en amd64, avec celle de docker save.
| Commande | Taille (octets) | Droits | Contenu |
|---|---|---|---|
docker save | 3 961 856 | -rw------- | disposition OCI, avec l'index |
skopeo copy … oci-archive: | 3 860 992 | -rw-r--r-- | disposition OCI, couche compressée, manifeste amd64 seul |
skopeo copy … docker-archive: | 8 717 312 | -rw-r--r-- | archive Docker, couche décompressée |
Retenez oci-archive: : sa taille est voisine de celle de docker save. L'archive docker-archive: pèse environ 2,3 fois plus (8 717 312 octets contre 3 860 992), et l'image chargée porte une empreinte propre à la conversion (594f9501… à la mesure), qui n'existe nulle part en amont. La destination s'écrit pareil, docker-archive:/out/alpine-docker.tar:docker.io/library/alpine:3.24.3, mais la leçon ne s'en sert que comme contre-exemple.
Lancé dans un conteneur, comme ici, Skopeo écrit l'archive en -rw-r--r-- (0644), lisible par les autres comptes, là où docker save écrit en -rw-------. Le umask de votre shell n'y change rien, même après umask 077. Seul le dossier sk en 0700, créé par mkdir sk && chmod 700 sk ci-dessus, protège l'archive. Dès qu'elle sort de ce dossier, elle redevient lisible : lancez chmod 600 sk/alpine-oci.tar après l'écriture pour qu'elle se protège elle-même. La somme sha256sum se calcule et se vérifie comme pour une archive de docker save : elle doit être identique des deux côtés du transfert.
Que change Skopeo à l'identité de l'image chargée ?
Section intitulée « Que change Skopeo à l'identité de l'image chargée ? »Une archive de Skopeo se charge avec docker load, mais l'image chargée n'a plus la même identité que celle de docker save. Le schéma met les deux voies côte à côte : à gauche celle de Skopeo, à droite celle de docker save. Regardez ce que l'archive embarque, puis la dernière case de chaque colonne, celle de l'Id : seule la voie de Skopeo ne garde pas le digest d'index.
Protégez d'abord l'archive par chmod 600, puis écrivez son reçu et copiez les deux fichiers dans xfer, comme pour une archive de docker save. Le chmod 600 sert ici : xfer a été créé par un simple mkdir -p à la leçon précédente, donc sans droits restreints, et la copie reprend les droits de sa source, moins ceux que retire votre umask.
Sur la machine isolée, sha256sum -c doit afficher alpine-oci.tar: OK : toute autre ligne signifie que l'archive n'est pas celle qui est partie. La sortie montrée plus bas commence au chargement, juste après cette vérification. Comme dans la leçon précédente, le reçu doit voyager par un canal séparé de l'archive pour protéger d'une altération volontaire. Chargez ensuite l'archive sur la machine isolée, qui porte déjà alpine:3.24.3 venu de docker save, et lisez l'identité de l'image.
chmod 600 sk/alpine-oci.tar(cd sk && sha256sum alpine-oci.tar > alpine-oci.tar.sha256)cp sk/alpine-oci.tar sk/alpine-oci.tar.sha256 xfer/docker exec machine-isolee sh -c 'cd /xfer && sha256sum -c alpine-oci.tar.sha256'docker exec machine-isolee docker load -i /xfer/alpine-oci.tardocker exec machine-isolee docker image inspect alpine:3.24.3 --format 'Id={{.Id}} RepoDigests={{.RepoDigests}} Arch={{.Architecture}}'docker exec machine-isolee docker run --rm --pull never alpine:3.24.3@sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d cat /etc/alpine-releasedocker exec machine-isolee docker run --rm --pull never alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a cat /etc/alpine-releasedocker exec machine-isolee docker images --digests alpineLoaded image: alpine:3.24.3Id=sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d RepoDigests=[alpine@sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d] Arch=amd643.24.3docker: Error response from daemon: No such image: alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
Run 'docker run --help' for more informationREPOSITORY TAG DIGEST IMAGE ID CREATED SIZEalpine 3.24.3 sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d c26612752c78 12 days ago 12.9MBRegardez trois lignes. L'image se lance par le digest de son manifeste (c26612…). L'Id est le digest du manifeste amd64 (c26612…), comme après un docker push, et non le digest d'index (be2656…) que gardait docker load après docker save. Enfin, la référence par le digest d'index répond No such image. La colonne CREATED (« 12 days ago ») dépend du jour où vous lancez la commande : la vôtre affichera une autre durée.
Le chargement a aussi remplacé l'image : la machine isolée portait alpine:3.24.3 chargé par docker save, et une archive Skopeo chargée sous le même nom prend sa place. Le listing, filtré sur alpine, ne montre plus qu'une ligne, celle de c266…, et le digest d'index be26… a disparu de la machine isolée. Un FROM épinglé qui marchait cesse donc de marcher. Pour le montrer, écrivez deux Dockerfiles dans b1/ et b2/, copiez-les dans xfer, et construisez-les sur la machine isolée.
# b1/Dockerfile : épinglé sur le digest d'indexFROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aRUN true# b2/Dockerfile : épinglé sur le digest du manifeste amd64FROM alpine:3.24.3@sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0dRUN truecp -r b1 b2 xfer/docker exec machine-isolee docker build --progress=plain --network none --no-cache -t t:idx /xfer/b1docker exec machine-isolee docker build --progress=plain --network none --no-cache -t t:man /xfer/b2Le premier build échoue, et son erreur ressemble à une panne de réseau, pas à un contenu manquant :
#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 unreachableLe build cherche cet index (be26…) sur le registre, faute de le trouver dans le magasin, et se heurte à l'absence de réseau. Le second build, épinglé sur le digest du manifeste c26612…, réussit : il se termine par #5 [2/2] RUN true puis #5 DONE 0.1s. Un build FROM alpine:3.24.3 sans digest réussit aussi.
Pour retrouver le digest d'index, rechargez l'archive de docker save : après un docker rmi -f alpine:3.24.3 et un docker load -i /xfer/alpine.tar, la machine isolée lance l'image par son tag et par le digest d'index, deux fois 3.24.3. Si vous devez construire là-bas sur un socle épinglé sur l'index, livrez-le donc par docker save sans --platform. Avec --platform, docker save mène au même résultat que Skopeo : l'archive est réduite au manifeste, et l'image chargée porte son digest, comme le montre la leçon précédente.
Un piège vient du nom de destination. Recréez l'archive avec le nom court alpine:3.24.3 à la place de docker.io/library/alpine:3.24.3. Retirez ensuite alpine:3.24.3 de la machine isolée par docker rmi -f, puis chargez la nouvelle archive.
docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD/sk:/out" \ quay.io/skopeo/stable:v1.22.3@sha256:2681eccb8b02c495b013c2669cbf7f41d5c0b0b0e644e7c69bd3ea9bcb20641f \ copy docker://docker.io/library/alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a \ oci-archive:/out/court.tar:alpine:3.24.3cp sk/court.tar xfer/docker exec machine-isolee docker rmi -f alpine:3.24.3docker exec machine-isolee docker load -i /xfer/court.tardocker exec machine-isolee docker images --digests alpinedocker exec machine-isolee docker image inspect alpine:3.24.3docker load annonce Loaded image: alpine:3.24.3, mais l'image est inutilisable. La liste des images, puis docker image inspect alpine:3.24.3, donnent :
Loaded image: alpine:3.24.3REPOSITORY TAG DIGEST IMAGE ID CREATED SIZEalpine 3.24.3 <none> c26612752c78 12 days ago 12.9MBalpine 3.24.3 <none> c26612752c78 12 days ago 12.9MB
Error response from daemon: No such image: alpine:3.24.3Les deux lignes sans digest, et le No such image de docker image inspect, trahissent ce nom court : recréez l'archive avec le nom complet. L'archive court.tar ne sert plus, le nettoyage la retire avec le dossier sk.
Comment choisir la plateforme avec Skopeo ?
Section intitulée « Comment choisir la plateforme avec Skopeo ? »Skopeo copie la variante de la plateforme de la machine qui le lance, ici amd64. Ajoutez --override-arch arm64 avant la source pour choisir la variante arm64 dans l'index distant : elle n'a pas besoin d'être tirée d'abord, contrairement à docker save --platform.
docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD/sk:/out" \ quay.io/skopeo/stable:v1.22.3@sha256:2681eccb8b02c495b013c2669cbf7f41d5c0b0b0e644e7c69bd3ea9bcb20641f \ copy --override-arch arm64 docker://docker.io/library/alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a \ oci-archive:/out/arm.tar:docker.io/library/alpine:3.24.3Chargée sur un hôte amd64, cette archive se charge sans erreur, et l'échec arrive au lancement : Docker avertit que la plateforme de l'image ne correspond pas à celle de l'hôte, puis le premier programme lancé échoue sur exec format error. Ce chargement remplacerait l'image alpine:3.24.3 de la machine isolée, comme celui de la section précédente. La leçon sur l'épinglage et la livraison hors ligne montre l'avertissement et l'erreur exacts, avec une archive de docker save d'une autre architecture. Exportez donc la plateforme de la machine isolée.
Dernier point sur la copie elle-même : le magasin local ne reçoit pas l'image copiée. Mesuré avec busybox:1.37, une copie depuis Docker Hub n'a laissé aucune ligne busybox dans docker image ls -a. C'est ce qu'on attend d'une voie sans magasin, et c'est aussi pourquoi docker image ls ne vous dira pas ce qui a été exporté.
Que faire quand l'export Skopeo échoue ?
Section intitulée « Que faire quand l'export Skopeo échoue ? »Chaque échec de la leçon a un message ou un symptôme qui le désigne. Cherchez le vôtre dans la première colonne.
| Message ou symptôme | Cause | Geste |
|---|---|---|
Docker references with both a tag and digest are currently not supported | La source de skopeo copy porte un tag et un digest | Écrire la source avec le digest seul : docker://docker.io/library/alpine@sha256:… |
docker load annonce Loaded image: alpine:3.24.3, mais l'image est listée deux fois sans digest et inspect répond No such image | Destination oci-archive: écrite avec un nom court | Recréer l'archive avec le nom complet, docker.io/library/alpine:3.24.3 |
No such image: alpine@sha256:… avec le digest d'index après le chargement | L'archive porte le digest du manifeste, pas celui de l'index | Lancer par le tag ou par le digest du manifeste, ou livrer par docker save sans --platform |
failed to do request: Head "https://registry-1.docker.io/v2/library/alpine/manifests/sha256:…" … network is unreachable au build d'un FROM épinglé sur l'index | Même cause : l'index n'est plus dans le magasin de la machine isolée | Épingler le digest du manifeste, ou recharger l'archive de docker save faite sans --platform |
L'archive appartient à root | Conteneur lancé sans --user | Ajouter --user "$(id -u):$(id -g)" et -e HOME=/tmp |
Archive environ 2,3 fois plus lourde que celle de docker save, image chargée au digest inconnu en amont | Destination docker-archive: | Recréer l'archive en oci-archive: |
exec format error au lancement d'une image livrée | Archive d'une autre architecture que la machine | Exporter la bonne plateforme avec --override-arch |
L'échec de plateforme et celui du nom court se ressemblent : le chargement réussit dans les deux cas, et le défaut n'apparaît qu'à l'usage.
Comment nettoyer après les essais ?
Section intitulée « Comment nettoyer après les essais ? »La leçon laisse sur votre poste l'image de Skopeo, le dossier sk et les dossiers de Dockerfiles b1 et b2. Retirez-les d'abord : ces commandes ne touchent ni la machine isolée ni le dossier xfer. Relisez chaque chemin avant de lancer un rm -rf : il ne demande aucune confirmation.
docker image rm quay.io/skopeo/stable@sha256:2681eccb8b02c495b013c2669cbf7f41d5c0b0b0e644e7c69bd3ea9bcb20641frm -rf sk b1 b2Si l'image de Skopeo a déjà disparu, docker image rm l'écrit et sort en code 1 : les autres commandes s'exécutent quand même.
Jouez ensuite le nettoyage de la leçon précédente, la section « Comment nettoyer après les essais ? » de l'épinglage et de la livraison hors ligne. Il retire la machine isolée avec docker rm -fv machine-isolee : l'option -v emporte le volume anonyme qui porte tout son démon interne, donc aussi l'image t:man construite ici et l'image inutilisable chargée depuis court.tar. Il retire aussi le dossier xfer, après la machine qui le monte en /xfer, et les images alpine:3.24.3 et docker:dind de l'hôte.
Si vous gardez la machine isolée pour d'autres essais, retirez seulement l'image construite ici avec docker exec machine-isolee docker rmi t:man, et sachez qu'elle conserve l'image chargée depuis court.tar, inutilisable par son nom : la retirer avec la machine reste le geste le plus simple. Le dossier xfer garde aussi alpine-oci.tar, son reçu et court.tar, ce dernier en 0644 : retirez-les avec rm -f xfer/alpine-oci.tar xfer/alpine-oci.tar.sha256 xfer/court.tar.
Vérifiez enfin avec docker image ls qu'il ne reste aucune image <none> issue de la leçon ou de la précédente (une image tirée par digest sans tag, ou un socle orphelin après une reconstruction sous le même tag) : supprimez-la par son identifiant.
À retenir
Section intitulée « À retenir »Skopeo exporte une image d'un registre vers une archive sans démon : skopeo copy docker://…@sha256:… oci-archive:/out/archive.tar:docker.io/library/…:tag. La démonstration le lance par l'image de l'éditeur, ce qui emploie le démon ; sur un poste sans Docker, on installe le binaire.
La source s'écrit avec le digest seul, jamais tag@sha256:. La destination porte un nom complet : avec un nom court, l'image chargée est inutilisable.
On choisit oci-archive: : ses couches restent compressées, et sa taille est voisine de celle de docker save. docker-archive: pèse environ 2,3 fois plus et produit une empreinte inconnue en amont. Lancé dans un conteneur, Skopeo écrit l'archive en -rw-r--r-- quel que soit votre umask : gardez-la dans un dossier en 0700 et faites un chmod 600 si elle en sort.
Charger une archive Skopeo change l'identité : l'Id est le digest du manifeste, et la référence par le digest d'index échoue. Chargée sous le même nom qu'une image venue de docker save, elle la remplace, et un FROM épinglé sur l'index cesse de se résoudre. Pour garder le digest d'index, livrez par docker save sans --platform : avec --platform, il mène au même résultat que Skopeo, un digest de manifeste.
--override-arch choisit la plateforme dans l'index distant sans la tirer d'abord, et le magasin local ne reçoit pas l'image copiée.
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 : Épreuve : livrer une image utilisable ailleurs
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications