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

Par mis à jour le

Améliorer cette page

Logo Docker

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.

  • Exporter une image par digest vers une archive oci-archive: avec skopeo copy
  • Écrire la source sans tag et la destination avec un nom complet
  • Choisir oci-archive: plutôt que docker-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
  • Épingler un socle, et livrer hors ligne : vous savez épingler un FROM par digest, exporter par docker save, vérifier un reçu sha256sum et 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-isolee sans réseau, avec alpine:3.24.3 chargé depuis alpine.tar produite par docker save sans --platform, et le dossier xfer monté 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 chargez alpine:3.24.3 comme dans sa section sur docker 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.

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.

Fenêtre de terminal
docker pull quay.io/skopeo/stable:v1.22.3@sha256:2681eccb8b02c495b013c2669cbf7f41d5c0b0b0e644e7c69bd3ea9bcb20641f
mkdir sk && chmod 700 sk
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/alpine-oci.tar:docker.io/library/alpine:3.24.3
ls -l sk
Getting image source signatures
Copying blob sha256:7b229517ed222597277036d94892173207d01a731ab3a88eeb1de2e963a2c788
Copying config sha256:b92841763c5c2140a20eeb2a44e94ebb0e80fd51d9d6b5895ea72fa999525d68
Writing manifest to image destination
total 3772
-rw-r--r-- 1 ansible ansible 3860992 Nov 3 20:14 alpine-oci.tar

Le 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.

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.

CommandeTaille (octets)DroitsContenu
docker save3 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.

Deux colonnes côte à côte. Voie Skopeo : skopeo copy sans démon écrit une archive qui ne contient que le manifeste amd64, docker load la charge, l'Id est le digest du manifeste, et un FROM épinglé sur le digest d'index échoue. Voie docker save, sans --platform : docker pull par digest puis docker tag, docker save écrit une archive qui contient l'index et le manifeste amd64, docker load la charge, l'Id est le digest de l'index, et un FROM épinglé sur le digest d'index réussit.Voie Skopeoskopeo copy,sans démonArchive : le manifesteamd64 seuldocker loadId = digest du manifesteFROM épinglésur l'index : échoueVoie docker save, sans--platformdocker pull par digest,puis docker tagArchive : l'indexet le manifeste amd64docker loadId = digest de l'indexFROM épinglésur l'index : réussit
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

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.

Fenêtre de terminal
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.tar
docker 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-release
docker exec machine-isolee docker run --rm --pull never alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a cat /etc/alpine-release
docker exec machine-isolee docker images --digests alpine
Loaded image: alpine:3.24.3
Id=sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d RepoDigests=[alpine@sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d] Arch=amd64
3.24.3
docker: Error response from daemon: No such image: alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
Run 'docker run --help' for more information
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
alpine 3.24.3 sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d c26612752c78 12 days ago 12.9MB

Regardez 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'index
FROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
RUN true
# b2/Dockerfile : épinglé sur le digest du manifeste amd64
FROM alpine:3.24.3@sha256:c26612752c78b18faacd63c7d50f4592c2e7b0f25e0e20e82bd74e465c57ab0d
RUN true
Fenêtre de terminal
cp -r b1 b2 xfer/
docker exec machine-isolee docker build --progress=plain --network none --no-cache -t t:idx /xfer/b1
docker exec machine-isolee docker build --progress=plain --network none --no-cache -t t:man /xfer/b2

Le 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 unreachable

Le 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.

Fenêtre de terminal
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.3
cp sk/court.tar xfer/
docker exec machine-isolee docker rmi -f alpine:3.24.3
docker exec machine-isolee docker load -i /xfer/court.tar
docker exec machine-isolee docker images --digests alpine
docker exec machine-isolee docker image inspect alpine:3.24.3

docker 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.3
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
alpine 3.24.3 <none> c26612752c78 12 days ago 12.9MB
alpine 3.24.3 <none> c26612752c78 12 days ago 12.9MB
Error response from daemon: No such image: alpine:3.24.3

Les 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.

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.

Fenêtre de terminal
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.3

Chargé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é.

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ômeCauseGeste
Docker references with both a tag and digest are currently not supportedLa 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 imageDestination oci-archive: écrite avec un nom courtRecré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 chargementL'archive porte le digest du manifeste, pas celui de l'indexLancer 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'indexMê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 à rootConteneur lancé sans --userAjouter --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 amontDestination docker-archive:Recréer l'archive en oci-archive:
exec format error au lancement d'une image livréeArchive d'une autre architecture que la machineExporter 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.

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.

Fenêtre de terminal
docker image rm quay.io/skopeo/stable@sha256:2681eccb8b02c495b013c2669cbf7f41d5c0b0b0e644e7c69bd3ea9bcb20641f
rm -rf sk b1 b2

Si 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.

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.

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

6 questions
6 min.
70% requis

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

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