Skopeo's transport prefixes, its split credential flags, and the dir: layout it calls non-standardized
Work with remote images registries - retrieving information, images, signing content
At a glance
- What is it?
- Skopeo moves, inspects, deletes and syncs container images with no daemon and, for most operations, no root, and its real behavior lives in six transport prefixes, two different sets of credential flags, and one local layout the project refuses to call a standard.
- Who is it for?
- Adopt skopeo when you need registry-level image work on a host where you cannot run a daemon or cannot use root, and pin everything you move to a digest you resolved yourself. Skip it if you want an image build, a run, or a layer-level cache: that is Podman or Buildah territory, and skopeo points at those as its neighbors rather than competing with them.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- podman-container-tools/skopeo11,401 ★ · +153
| Repository | 2026-09-22 | 2026-11-14 |
|---|---|---|
| podman-container-tools/skopeo | 11.2k | 11.4k |
153 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
inspect fetches a manifest and stops short of the layers
Skopeo's inspect command fetches a repository manifest and stops there. It emits a docker inspect-style JSON document for a whole repository or a single tag, reporting the available tags, labels, creation date, architecture and operating system before anything lands on the host's disk. The output for fedora:latest carries RepoTags, a Digest, a Created timestamp, DockerVersion, a Labels map and a Layers array:
$ skopeo inspect docker://registry.fedoraproject.org/fedora:latestAdding --config switches to the container configuration block, where Env, Cmd and Labels live, and piping into jq selects individual fields:
$ skopeo inspect --config docker://registry.fedoraproject.org/fedora:latest | jqThe one thing inspect does not do is verify anything. The example that pulls out a single field is titled Show unverified image's digest. Read that digest as the identity of whatever the registry just handed back, and resolve and verify your own digest before you paste one into a build file or a policy.
Two prefixes refuse the reference a digest-pinning script depends on
Every image reference opens with a transport prefix, and the prefix decides what a legal reference looks like. containers-storage:docker-reference points at a local containers/storage image store whose location and store name both come from /etc/containers/storage.conf, which is the shared backend for Podman, CRI-O and Buildah. docker://docker-reference is a registry speaking the Docker Registry HTTP API V2, and that covers registries such as docker.io, quay.io and private registries; local directories use the dir: and oci: prefixes. oci:path:tag names a tag inside a directory compliant with the Open Container Image Layout Specification.
Two prefixes carry restrictions that break scripts. docker-archive:path[:docker-reference] uses the reference only when writing the file, and that reference must not contain a digest. docker-daemon:docker-reference must contain either a tag or a digest, and when reading you may instead pass docker-daemon:algo:digest as an image ID. The consequence for a pipeline that pins every image by digest: it works against docker:// and against docker-daemon:, then fails at the point where it asks for a docker save archive, because the digest it wants to pin is the exact thing that prefix rejects.
dir: writes loose files, and the project calls that format non-standardized
dir:path is the odd one out among the transports. It writes the manifest, the layer tarballs and the signatures as individual files inside a directory, and the project describes that as a non-standardized format whose stated purpose is debugging or noninvasive container inspection.
$ skopeo copy oci:busybox_ocilayout:latest dir:existingemptydirectoryThat line also shows the shape of the use: an existing, empty directory is the target, and what you get back is a pile of files rather than an image reference you can hand to something else. So dir: cannot serve as an interchange format between hosts or between tools. When another runtime or another registry client has to read the result, use oci:path:tag, which is compliant with the Open Container Image Layout Specification, or docker-archive:path for a docker save-style file. Reach for dir: when the point is to look at the bytes of an image without unpacking it, and do not build a pipeline on it.
inspect and delete take --creds, copy takes --src-creds and --dest-creds
Credential flags are not uniform across subcommands. inspect and delete read --creds. copy reads --src-creds and --dest-creds, one for each end of the transfer. When those flags are absent, skopeo falls back to configuration written by skopeo login, podman login, buildah login or docker login, so a session authenticated through any of those four tools already carries usable credentials:
$ skopeo login --username USER myregistrydomain.com:5000
Password:
$ skopeo inspect docker://myregistrydomain.com:5000/busyboxFor docker:// references the default authorization state lives in $XDG_RUNTIME_DIR/containers/auth.json. That path is derived from the environment variable, so a service or container that never sets XDG_RUNTIME_DIR has no default file for the fallback to land in, and credentials have to travel on the command line instead.
sync moves a whole repository, and no tag filter is documented for it
sync is the command aimed at air-gapped deployments: it moves an external image repository into an internal store, and the single example given writes the result to removable media.
$ skopeo sync --src docker --dest dir registry.example.com/busybox /media/usbBoth ends are named with the same transport vocabulary copy uses, --src docker and --dest dir here, and the middle argument is a repository rather than one tagged image. The README's one sync example transfers a whole repository and shows no option for narrowing it to chosen tags. If you need to see what landed before anything reaches the internal registry, stage into a scratch directory first.
The README has no install command, and the Makefile hardcodes two paths
The README carries no installation command block at all. It links to install.md for how to install or build skopeo, points at the container image published on quay.io/skopeo/stable, and states that this repository plus those container images are the only upstream builds, with packages from distributions you trust covered in install.md as well. Sites presenting themselves as the home of skopeo or offering their own skopeo downloads are not affiliated with the project, so a binary from one of them arrives with no upstream provenance.
Building from source runs through the Makefile, and its paths are the part that needs attention. PREFIX defaults to /usr/local, and CONTAINERSCONFDIR resolves to /usr/local/etc/containers on FreeBSD but /etc/containers everywhere else, which in turn drives REGISTRIESDDIR at ${CONTAINERSCONFDIR}/registries.d. LOOKASIDEDIR is fixed at /var/lib/containers/sigstore, and shell completions are split across separate bash, zsh and fish directories under PREFIX. A distribution that keeps configuration anywhere other than /etc/containers therefore has to set CONTAINERSCONFDIR by hand, since the Makefile does not detect it. Linting is pinned to golangci-lint 2.15.0, and go.mod names go 1.26.0 as the minimum toolchain.
Two release lines were tagged on the same day in September
Release lines here move on their own schedule. v1.24.0 shipped 2026-07-30, and then v1.24.1 and v1.22.3 both landed on 2026-09-16, which means the 1.22 line still receives patches alongside 1.24. The last push was on 2026-11-02, after both tags, and the repository is not archived. The practical consequence is that a loose version constraint can resolve to either the 1.22 or the 1.24 branch without telling you which, so pin the full version when you care about behavior.
The tree also vendors its dependencies and keeps two documentation directories side by side. The top level holds vendor/, go.sum, cmd/, docs/, docs-experimental/, hack/, integration/, systemtest/, rpm/ and contrib/, alongside default.yaml and default-policy.json, plus GOVERNANCE.md, MAINTAINERS.md, ROADMAP.md, SECURITY.md, SUPPORT.md and LLM_POLICY.md. Nothing in the names distinguishes released documentation from provisional documentation, so treat docs-experimental/ as unfinished and go by docs/. The go.mod in this tree pins go.podman.io/image/v5 v5.42.0, go.podman.io/common v0.70.0 and go.podman.io/storage v1.65.0, so a from-source build tracks those exact points.
Editorial conclusion
Adopt skopeo when you need registry-level image work on a host where you cannot run a daemon or cannot use root, and pin everything you move to a digest you resolved yourself. Skip it if you want an image build, a run, or a layer-level cache: that is Podman or Buildah territory, and skopeo points at those as its neighbors rather than competing with them. Before the first transfer, confirm that /etc/containers/storage.conf and $XDG_RUNTIME_DIR/containers/auth.json point where you think they do on your build hosts, and pick a release line on purpose, since 1.22 and 1.24 were both tagged on 2026-09-16.
Frequently asked questions
What is Skopeo used for?
It performs operations on container images and image repositories with no daemon running and no root needed for most of them: copying images between storage mechanisms, inspecting a remote image and its layers without pulling it to the host, deleting an image from a repository, and syncing an external repository into an internal registry for air-gapped deployments.
what is skopeo copy
copy moves an image between two transport prefixes. The documented examples go from quay.io to a private registry, and from an OCI layout directory into an existing empty local directory.
what is skopeo inspect
inspect fetches the manifest of a repository or tag and prints docker inspect-like JSON covering tags, labels, creation date, architecture, operating system and layers, without pulling the image. Adding --config shows the container configuration instead.
how to install skopeo
install.md is the project's installation and build documentation, and skopeo is also published as a container image on quay.io/skopeo/stable. The README itself contains no installation command block.
what is skopeo sync
sync moves an external image repository into an internal store, which the project positions for air-gapped deployments. The documented example uses --src docker and --dest dir to write a repository onto removable media.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/podman-container-tools-skopeo)Star history chart for your README
[](https://hysenlabs.com/projects/podman-container-tools-skopeo)Was this useful?