--- myst: html_meta: description: Inspect, copy, and delete container images between registries and local storage with Skopeo on Ubuntu Server. --- (skopeo)= # Skopeo for system admins [Skopeo](https://github.com/containers/skopeo) works with container images and image registries. It needs no daemon and no root privileges. You can inspect a remote image before you deploy it, copy images between registries or into local storage, mirror whole repositories, and delete images from a registry. ## Install Skopeo {manpage}`skopeo(1)` is available in Ubuntu: ```{terminal} :copy: :user: :host: :dir: sudo apt install skopeo ``` ## Image names Many Skopeo commands take an image name prefixed by a transport, which specifies where the image lives: `docker://` : An image in a remote registry, for example `docker://docker.io/ubuntu:26.04`. `docker-daemon:` : An image in the local Docker daemon storage. `containers-storage:` : An image in the local storage used by [Podman](https://podman.io/) and [Buildah](https://buildah.io/). `dir:` : An image unpacked into a local directory. `oci:` and `oci-archive:` : An image in an {term}`OCI` layout directory or `tar` archive. See {manpage}`containers-transports(5)` for the full list and the exact syntax of each transport. ## Inspect a remote image {manpage}`skopeo-inspect(1)` downloads only the image metadata, not its layers, so you can check what an image contains before you pull it: ```{terminal} :copy: :user: :host: :dir: skopeo inspect docker://docker.io/ubuntu:26.04 { "Name": "docker.io/library/ubuntu", "Digest": "sha256:2260313b31c8c011cd2eebe728008efac1b3982be73eb71348ea2648d2c0e09b", "RepoTags": [ "24.04", "25.10", "26.04", "latest" ], "Created": "2026-08-17T09:00:47.315779976Z", "DockerVersion": "", "Labels": { "org.opencontainers.image.created": "2026-08-17T09:02:45.677319+00:00", "org.opencontainers.image.title": "ubuntu", "org.opencontainers.image.version": "26.04" }, "Architecture": "amd64", "Os": "linux", "Layers": [ "sha256:06e9d71331fb2b620a4f6c8064e0f84b284bb69a42c7c57b1c962bd4a4cdee76", "sha256:f3db1cd940786339b09d8a60e47c66fea9502d788e6fab5bec91a4a77d4ced1c" ], "LayersData": [ { "MIMEType": "application/vnd.oci.image.layer.v1.tar+gzip", "Digest": "sha256:06e9d71331fb2b620a4f6c8064e0f84b284bb69a42c7c57b1c962bd4a4cdee76", "Size": 41569203, "Annotations": { "ci.umo.uncompressed_blob_size": "111523840" } }, { "MIMEType": "application/vnd.oci.image.layer.v1.tar+gzip", "Digest": "sha256:f3db1cd940786339b09d8a60e47c66fea9502d788e6fab5bec91a4a77d4ced1c", "Size": 393, "Annotations": { "ci.umo.uncompressed_blob_size": "10240" } } ], "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ] } ``` You can use {manpage}`skopeo-list-tags(1)` to list the available tags in a repository. ```{terminal} :copy: :user: :host: :dir: skopeo list-tags docker://docker.io/ubuntu { "Repository": "docker.io/library/ubuntu", "Tags": [ "22.04", "24.04", "26.04", "latest" ] } ``` ## Authenticate to a registry Public images need no credentials. For a private registry, log in with {manpage}`skopeo-login(1)`. Skopeo stores the credentials in `${XDG_RUNTIME_DIR}/containers/auth.json` (see {manpage}`containers-auth.json(5)`): ```{terminal} :copy: :user: :host: :dir: skopeo login registry.example.com Username: myuser Password: Login Succeeded! ``` Alternatively, pass credentials per command with `--creds user:password` (for `inspect` and `delete`), or `--src-creds` and `--dest-creds` (for `copy`). Skopeo can also automatically use authentication credentials set by `docker login`, `podman login` or `buildah login`. ## Copy images ### Between registries Copying an image into an internal registry with {manpage}`skopeo-copy(1)` doesn't require a local daemon, and the image is never unpacked while copying: ```{terminal} :copy: :user: :host: :dir: skopeo copy \ docker://docker.io/ubuntu:26.04 \ docker://registry.example.com/mirror/ubuntu:26.04 Getting image source signatures Copying blob 06e9d71331fb done Copying blob f3db1cd94078 done Copying config af52039db3 done Writing manifest to image destination ``` By default, Skopeo only copies the image that matches the architecture of your machine. Add `--all` to copy every architecture of a multi-architecture image. Both `copy` and `sync` can sign the images they write with `--sign-by` or `--sign-by-sigstore`, but the signatures are only checked if you configure a trust policy. Refer to {manpage}`containers-policy.json(5)` and {manpage}`containers-registries.d(5)` for more details. ### To and from local storage Copy a remote image into the local Docker daemon: ```{terminal} :copy: :user: :host: :dir: skopeo copy docker://docker.io/ubuntu:26.04 docker-daemon:ubuntu:26.04 ``` Or save it as an OCI archive: ```{terminal} :copy: :user: :host: :dir: skopeo copy docker://docker.io/ubuntu:26.04 oci-archive:ubuntu-26.04.tar:ubuntu:26.04 ``` The same command works in reverse, with the archive as the source and a registry as the destination. ## Mirror multiple images `copy` handles one image at a time, while {manpage}`skopeo-sync(1)` copies a set of images in a single run and leaves out anything the destination already has. When using `sync`, specify the transports with `--src` and `--dest` instead of prefixing the image names. To sync a single image, or a whole repository, into an internal registry: ```{terminal} :copy: :user: :host: :dir: skopeo sync --src docker --dest docker docker.io/ubuntu:26.04 registry.example.com/mirror INFO[0000] Tag presence check imagename="docker.io/ubuntu:26.04" tagged=true INFO[0000] Copying image ref 1/1 from="docker://ubuntu:26.04" to="docker://registry.example.com/mirror/ubuntu:26.04" Getting image source signatures Copying blob 06e9d71331fb done Copying blob f3db1cd94078 done Copying config af52039db3 done Writing manifest to image destination INFO[0005] Synced 1 images from 1 sources ``` A source repository without a tag syncs every tag in it. You can also sync to a directory. The `--scoped` option keeps the source registry name in the path so that images from different registries cannot collide: ```{terminal} :copy: :user: :host: :dir: skopeo sync --src docker --dest dir --scoped docker.io/ubuntu:26.04 /media/usb ``` This writes the image to `/media/usb/docker.io/library/ubuntu:26.04`. To sync it back into a registry, use `--src dir --dest docker`. ### Syncing from a YAML file When you run the same mirror repeatedly, list the images in a {term}`YAML` file and pass `--src yaml`. You can name tags one by one, or match them with a regular expression: ```yaml docker.io: images: ubuntu: - "24.04" - "26.04" images-by-tag-regex: nginx: ^1\.2[0-9]-alpine$ ``` Check what a sync would do before you run it with `--dry-run`: ```{terminal} :copy: :user: :host: :dir: skopeo sync --src yaml --dest docker --dry-run sync.yaml registry.example.com/mirror INFO[0000] Processing repo registry=docker.io repo=ubuntu INFO[0000] Processing repo registry=docker.io repo=nginx INFO[0000] Querying registry for image tags registry=docker.io repo=nginx INFO[0000] Getting tags image=docker.io/library/nginx WARN[0001] Running in dry-run mode INFO[0001] Would have copied image ref 1/2 from="docker://ubuntu:24.04" to="docker://registry.example.com/mirror/ubuntu:24.04" INFO[0001] Would have copied image ref 2/2 from="docker://ubuntu:26.04" to="docker://registry.example.com/mirror/ubuntu:26.04" INFO[0001] Would have copied image ref 1/10 from="docker://nginx:1.20-alpine" to="docker://registry.example.com/mirror/nginx:1.20-alpine" INFO[0001] Would have copied image ref 2/10 from="docker://nginx:1.21-alpine" to="docker://registry.example.com/mirror/nginx:1.21-alpine" INFO[0001] Would have copied image ref 3/10 from="docker://nginx:1.22-alpine" to="docker://registry.example.com/mirror/nginx:1.22-alpine" INFO[0001] Would have copied image ref 4/10 from="docker://nginx:1.23-alpine" to="docker://registry.example.com/mirror/nginx:1.23-alpine" INFO[0001] Would have copied image ref 5/10 from="docker://nginx:1.24-alpine" to="docker://registry.example.com/mirror/nginx:1.24-alpine" INFO[0001] Would have copied image ref 6/10 from="docker://nginx:1.25-alpine" to="docker://registry.example.com/mirror/nginx:1.25-alpine" INFO[0001] Would have copied image ref 7/10 from="docker://nginx:1.26-alpine" to="docker://registry.example.com/mirror/nginx:1.26-alpine" INFO[0001] Would have copied image ref 8/10 from="docker://nginx:1.27-alpine" to="docker://registry.example.com/mirror/nginx:1.27-alpine" INFO[0001] Would have copied image ref 9/10 from="docker://nginx:1.28-alpine" to="docker://registry.example.com/mirror/nginx:1.28-alpine" INFO[0001] Would have copied image ref 10/10 from="docker://nginx:1.29-alpine" to="docker://registry.example.com/mirror/nginx:1.29-alpine" INFO[0001] Would have synced 12 images from 2 sources ``` Add `--keep-going` if you want the sync to carry on when one image fails instead of stopping. ## Delete an image from a registry Use {manpage}`skopeo-delete(1)` to remove an image: ```{terminal} :copy: :user: :host: :dir: skopeo delete docker://registry.example.com/mirror/ubuntu:26.04 ``` This deletes the manifest, so the tag stops resolving. The registry only reclaims the layers when it runs garbage collection. ## Further reading * [Skopeo upstream documentation](https://github.com/containers/skopeo) * {ref}`container-tools-in-the-ubuntu-space`