The 9 best Docker registry tools

This article explains the best Docker registry tools for browsing registries/images and manipulating/copying images. It comes with elaborate feature comparison tables. I also explain use cases that illustrate why and when you should use these tools. The analyzed tools include Skopeo, Regctl, ORAS CLI, crane, and many others. Finally, I provide a list of temporary registries you can use as a sandbox for testing these tools.

Introduction

There are many well-known (and little-known) third-party tools that help you work with Docker/container images. This article presents all the tools you need to know. I decided to group them in two categories:

  • Registry browser tools, which list images of a registry, list the tags of a specific image, dive deep into the JSON manifests that a tag points to, or even inspect the contents of individual layers
  • Image manipulation tools, which copy, manipulate, or delete image tags or manifests. These are all small CLI tools that can do their job without a heavy-weight daemon/server process like the Docker daemon

Why should you care about using such tools? Why is it useful to better understand the internal structure of a container image, or execute advanced operations on them? Here are three reasons:

  • It helps you with improving the efficiency of your image layer caching, and therefore improving your image build speed, because:
    • Understanding how to analyze images enables you to debug problems with remote caching.
    • You will realize that some statements in a Dockerfile do not produce an image layer (e.g. CMD or ENTRYPOINT), thus their order in the Dockerfile does not matter when you try to optimize layer caching. If you are interested in this topic, see this blog series about optimizing Dockerfiles, or this BuildKit-specific post.
  • By running a private image registry, you improve the resilience of your deployed system. However, you must know the tools for copying images from Docker Hub (or other registries) to your registry.
  • You will improve the overall security of your system by digitally signing images (and verifying existing signatures). These signatures are also uploaded to the image registry, and you need to copy/create them explicitly. Being able to inspect them helps with diagnosing problems. See this article series where I go into signing images in detail.

Registries for (temporary) testing

While using tools of either category, it helps having a temporary image registry at hand to which you can quickly push (or copy) images. After pushing them, you can use the tools presented below to inspect or manipulate them further, all without “messing” with your production registry.

I found the following image registries to be useful:

  • Local registries that you can start with “docker run”:
    • CNCF Distribution registry is the reference implementation of the distribution specification. It is based on an older distribution spec version 1.0.1. It does not offer a web interface. See this guide for getting started.
    • Zot registry implements the more modern distribution spec version 1.1 and offers a web interface. See here for how to get started with running Zot in Docker.
  • Remotely hosted registry: ttl.sh is a free registry that does not require any authentication to push or pull images. It automatically deletes the images after some time. Keep in mind that anyone can access the images you push there!

Note: Pushing images to a local registry (that runs over HTTP, not HTTPS) may require tuning in your image builder or image manipulation tool. These tools expect to work with HTTPS registries by default, often rejecting to communicate with HTTP-only registries, unless you configure them.

Registry browser tools

The following table provides an overview of the available tools.

 oci.dag.dev
Source, public demo instance
Docker Registry Browser
Source
ORAS Artifact explorer
Source, public demo instance
Docker Registry UI
Source, public demo instance
Registry explorer
Source
Implementation technologyWeb server rendering a web UIWeb server rendering a web UIWeb server rendering a web UIWeb server rendering a web UIDocker Desktop extension
Browse multiple registries⚠️ (yes, but only for registries you control)
Supports deleting tags
List all images of a registry
Display raw/detailed image manifest
Inspect image layer contents⚠️ (only for plain-text blobs, not for tar.gz layers)⚠️ (supports tar.gz directory listing, but won’t show contents of files within tar.gz)
Supports local HTTP-only registry
Requires ngrok-like workaround
Supports registry that requires authentication

Browsing local HTTP-only registries

Some tools presented in the above table, e.g., ORAS Artifact explorer, do not support accessing local HTTP-only registries. They only support HTTPS-based registries with a valid SSL certificate. To work around this limitation, you can use a service such as Ngrok to create a temporary SSL tunnel to your registry. For instance, after you set up the Ngrok CLI and started the CNCF Distribution registry (that I mentioned above) on its default port 5000, just run
ngrok http 5000” to create the tunnel to your local registry.

Let’s look at each solution in detail in the tabbed box below:

Of all browser tools I tested, this one offers the most features, by far.

It is a CLI written in Go that starts a web server.

To use it, open the public instance, https://oci.dag.dev/ (or an outdated version deployed on https://explore.ggcr.dev). Alternatively, you can run your local instance, e.g. to access a private registry that the public instance would be unable to connect over the internet:

  • Run “git clone https://github.com/jonjohnsonjr/dagdotdev.git”, then cd into the directory
  • Run “CACHE_DIR=/tmp/oci go run ./cmd/oci -v -auth” to start the server.
  • Note: the “-auth” parameter will make the tool read your locally-available Docker logins (that you have e.g. previously logged into via “docker login”), allowing the tool to access registries that require authentication.

Here are screenshots with hints to get you started:

oci.dag.dev start page
oci.dag.dev startpage: The top field opens a specific manifest whose digest or tag you already know. In the second field you would just provide the image reference (without digest or tag), or it could even be just the hostname of the registry (try it e.g. with “mcr.microsoft.com”). In that case, it will try to list all images.
oci.dag.dev showing an image index
oci.dag.dev showing a snippet of an image index, with many clickable elements
oci.dag.dev showing an image manifest
oci.dag.dev showing an image manifest

The tool can also decode X.509 certificates, or base64-encoded content. See here for an example of a Cosign signature, where you can click on the “ey…” base64-encoded content until you see the content of the signing certificate.

In summary: there are a lot more clickable elements than you think. Hover with your mouse cursor over the different text elements (especially black text), to discover which ones are clickable!

Docker Registry Browser is a web CLI written in Ruby on Rails that starts a web server. It connects to one specific image registry that you configure via the environment variable DOCKER_REGISTRY_URL. It is designed to be configured against a local or private registry, and it won’t work for many public registries, such as Docker Hub (which the author confirmed in this ticket). But it may work for some public registries, e.g. the Microsoft Container registry.

You can run it with Docker using a command such as this:
docker run --name registry-browser -e SECRET_KEY_BASE=1acaa0068339b2b1d23f3785dad9f0247398f4e9aaea09b3dd2af0732af956db8280ee14356a76f8f6d4ead7b361ea52b30c7802561396f09c59379dfc34b71d --rm -p 8080:8080 -e DOCKER_REGISTRY_URL=https://mcr.microsoft.com klausmeyer/docker-registry-browser”.

Opening http://localhost:8080 lists all images, and the nice web UI allows you to inspect tags and their meta-data, as shown in this screen shot.

The main shortcomings of the UI are the following:

  • It only shows meta-data that is typical for regular Docker images. If you look at other types of manifests, e.g., a Cosign signature, it shows almost no useful information
  • You cannot inspect the contents of the image layers

You can also use Docker Registry Browser for registries that do not support listing images, assuming are willing to manually type the URL. It follows the form “http://localhost:8080/repo/<image-name-without-tag>/tag/<tag-name>“. Thus, if you configure DOCKER_REGISTRY_URL=https://mirror.gcr.io (which mirrors Docker Hub), you can also inspect Docker Hub images this way.

If you set the environment variable ENABLE_DELETE_IMAGES=true you get a Delete button that deletes tags.

ORAS Artifact explorer is a CLI written in Go that starts a web server.

To use it, open the public demo instance, https://artifact-explorer.azurewebsites.net/. Alternatively, you can run your local instance, e.g. to access private registries that the public instance cannot reach over the internet:

  • Run “git clone https://github.com/VasuDevrani/artifact-explorer-oras.git” then cd into the directory
  • Run “cd cmd && go run .” to start the server.

On the start page, the form consists of 3 fields rendered next to each other: the first one specifies the registry server, the second one is for the image name, the third one for the tag (or a “sha265:1234…” digest). Whenever possible, the tool will try to auto-complete the field, e.g. by trying to get the list of all images (which is e.g. supported for the MCR registry), or listing the tags for an image:

ORAS artifact explorer start page
Start page of ORAS Artifact Explorer

Once you click on the “Explore” button, a view such as this one is shown:

ORAS artifact explorer details page

The left half shows a tree-like representation of the manifest you are looking at and all other artifacts that reference the artifact you are currently looking at. The right half shows the manifest’s contents and allows you to dig deeper into its sub-manifests.

Docker Registry UI seems to be a self-hosted JavaScript application that renders a web interface.

You can try the public demo instance on https://joxit.dev/docker-registry-ui/demo/ or follow the README instructions to get a self-hosted instance up, using prebuilt Docker images.

Unfortunately, the implementation uses a different approach than all the other browser tools I present here: when you want to inspect an image manifest, all other tools work like this: your browser makes a (proprietary) request to the server part of the tool, then that server part makes the distribution-spec-compliant call to the registry (e.g. GET <registry-host>/v2/<name>/manifests/<reference>).

In contrast, the implementation of Docker Registry UI tells your web browser to make the distribution-spec-compliant HTTP calls to the registry API directly. Because all web browsers implement the Same-origin policy security feature, this massively limits the compatibility with registries: you can only browse registries that you fully own, that is, where you can configure the Access-Control-Allow-Origin CORS header.

Registry explorer is a Docker Desktop extension that provides a quick and easy way to navigate through the manifests of a specific Docker image, using a graphical, tree-like representation. You can inspect the raw manifests, and also look at directory listings of tar.gz layers. However, it cannot display the (text) content of files in these layers, and it does not make use of the registry credentials you configured via “docker login”.

Registry manipulation tools

The following table lists various use cases (that manipulate or create images) that I found relevant in the past years, see the left column, and then elaborates which tool implements this use case: 

 CraneORASSkopeoRegctl
Copy image/manifest
Incl. referrers

– Incl. Cosign signatures (but not referrers)
– Can (re-) sign destination image with embedded Cosign
– Can validate existing Cosign signatures of source image (using a custom trust policy)

Incl. referrers or Cosign signatures
Synchronize images⚠️
All-or-nothing approach, via “copy --all-tags

All tags, or only specific ones (semantic version comparison or regex)

Via separate tools: regsync syncs all images/repositories, or specific tags via regex. regbot offers Lua scripting for custom use cases. 
Delete image/manifest
Delete blob
Create/build new manifest
Re-tag manifest✅ via “copy” cmd
Upload local files(s) / folder(s) as image/artifact✅ (only individual files, not folders)
Upload and attach artifact for existing manifest
Usable as library✅ (Golang)✅ (Golang, Java, Rust, Python)⚠️ (not directly, see here)✅ (regclient Golang library)

Here is an explanation of each use case. What is it and when does it play a role?

  • Copy image: Copying a few/specific tags (or digests) of an image (or other OCI artifacts) from one registry to another. For instance, you might not trust (or cannot access) Internet registries from your deployed environment, so instead you only trust private registries. A concrete example could be Docker Hub, which is affected by image pull limits, and it could cause downtime in your production environment if you were to pull images from Docker Hub directly during a deployment. So instead, you copy images from Docker Hub to a private registry and deploy the images from there.
  • Synchronize images: copying many (or all) tags/manifests of one (or more) images from a source registry to a destination registry, e.g. because you want to mirror the source registry. An example scenario is that you want to limit registry access in your deployment to your destination registry, e.g. an “air-gapped deployment”.
  • Delete image/manifest or delete blobs: deleting tags or manifests, typically to reclaim storage space in a private registry. See the “Caveats of deleting manifests via tag or digest” box below for more details and caveats.
  • Create/build new manifest: suppose the image builder tools that you or someone else uses created an image that does not perfectly fit your needs, yet. Instead of rebuilding the image from scratch, you want to manipulate the existing one, because it is faster. Think of this like a “video post production” fix for an image. Examples:
    • Merging separately-pushed platform images (that have one separate tag per platform) to a single multi-platform image tag
    • Changing how the image behaves, e.g. by manipulating the ENTRYPOINT or CMD of an image, or its environment variables 
    • Change annotations of the image manifest, or delete sensitive build arguments whose values you don’t want to be present in the image manifest
    • Adding or removing files to the image
  • Re-tag manifest: adding (yet another) tag to an already-uploaded image manifest, without wasting time downloading the entire image and its layers (which would happen if you used “docker pull … && docker tag <old> <new> && docker push <new>”). Example: an environment promotion scenario, where a Continuous Deployment pipeline adds tags such as “staging” or “production” to an existing image already tagged with “development”, and then deploys that new tag.
  • Upload local files(s) / folder(s) as image/artifact: there are scenarios where you need to access various (large) files in your deployed environment, which you don’t want to include into your application (code) image, as it would become too large, or because you wouldn’t be able to change the files independently from the application code. Examples are machine learning models, database dumps, tool binaries, website assets, etc. Instead, you upload these files/folders as OCI artifacts to the image registry, treating the image registry as arbitrary blob storage (basically an alternative to S3 storage).
  • Upload and attach artifact for existing manifest: similar to the above use case (“Upload local files(s) / folder(s) as image/artifact”), but this uses the registry’s references/referrers feature that allows a registry client to look up associated artifacts for an image (see the Notation article for details). E.g. uploading an SBOM (or an image signature) for a specific image. See this article series to learn more about signing and attesting images.
  • Usable as library: in cases when the existing commands of the CLI tool don’t behave the way you need them to, or some feature is missing, you will need to write custom tooling (in some language), using a library (SDK) to interact with image manifests. These are edge cases, but they do exist. Think of it as replacing complex Bash scripts (that make various calls to the above CLI tools) with a more maintainable Go program that can do better error handling and input/output validation.
    • Example 1: want to regularly delete unused images from your registry, but the existing commands only support the push/pull date as selection criterion. However, you also need to consider the image size because you only want to delete images larger than 1 GB.
    • Example 2: after synchronizing an off-the-shelf image from Docker Hub, you want to ensure that the image in your registry does not contain certain binaries such as a /bin/sh or curl, to improve the security of your image. You could implement a custom tool that scans the layers of the synchronized image and if it finds such binaries, it adds another layer that removes them.

Typically, registries charge you for consumed storage space used by the blobs and manifests. Thus, if you operate an image registry, you should clean up regularly, or your costs will grow indefinitely.

A basic clean-up strategy involves three levels:

  • 1) identify (and delete) tags you no longer want to keep.
  • 2) identify (and delete) untagged manifests you don’t want to keep. Untagged manifests can still be pulled if you know their digest. A manifest could become untagged e.g. because the tag that used to point to it (such as “latest”) was changed to now point to a different manifest. Most registries will not automatically delete untagged manifests (and their blobs)!
  • 3) automatically delete blobs no longer referenced by any manifest: every registry does this by default, by keeping track of which manifests reference which blobs. In rare cases, there might be problems with this automatic blob garbage collection (or you might not want to wait until it runs). Thus, some of the above tools support deleting specific blobs by digest. However, use this carefully, because you can break your manifests/images, if you delete a blob that is still referenced by a manifest.

Level 1 and 2 can be done manually, using the above tools, or automatically, via policies that the registry implementation may allow you to configure. It often makes sense to have different (time) thresholds for level 1 and level 2: for instance, you may want to explicitly delete tags that were created for temporary build artifacts in your CI/CD pipeline after a short time interval of their creation, e.g. after 1-2 hours, because you are sure no one will need them again. But you may want to keep untagged manifests of publicly released images longer (e.g. for weeks or months), because your users might still want to be able to pull them via the manifest’s digest.

When explicitly deleting tags or manifests using the above tools, registries have “interesting” behavior:

  • Every registry implementation that wants to support manifest deletion in general (and be compliant with the OCI distribution spec) must support deleting manifests by digest (level 2). When you do so, the registry will also delete all tags that pointed to that manifest, and it can reclaim disk space (level 3) afterwards.
  • Some registry implementations also support deleting manifests by tag (level 1), e.g., the Zot registry. In that case, once you delete the last tag of an image manifest, the image can still be pulled by its digest, and still consumes storage space in your registry.

Depending on the client CLI you choose from the above list, doing a “delete by tag” operation will have different effects:

  • Crane just makes the DELETE HTTP request with whatever digest or tag you provided as CLI argument. If you provide a tag and the registry does not support deletion by tag (as is the case with the CNCF Distribution registry), you will get this error: “DIGEST_INVALID: provided digest did not match uploaded content”
  • Oras‘ “oras manifest delete” command resolves the tag to the manifest’s digest first and then prompts you: “Are you sure you want to delete the manifest sha256:1234… and all tags associated with it?”. If you confirm, it proceeds and does exactly what the message says.
  • Skopeo behaves like Oras but does not show a warning! It immediately deletes the manifest by its digest!
  • Regctl has two commands:
    • regctl manifest delete” verifies on the client side that you provided a digest, then makes the DELETE call that deletes the manifest by digest (and thus, all tags).
    • regctl tag delete” first tries to make the DELETE HTTP call using the tag. If the registry does not support it (returning a 4XX HTTP status code), regctl uses a clever workaround: it uploads a new random dummy manifest, bends the to-be-deleted tag to point to that dummy manifest, then deletes the dummy manifest by its digest. Thus, the original manifest (that the tag used to point to) remains.

Here are a few more details about each tool:

Crane (not to be confused with Michael Sauter’s crane which focuses on containers) offers various image manipulation commands. See the reference docs for all individual commands and Recipes that combine commands for different use cases.

Crane has one interesting gimmick: it can host a simple local registry, which is useful for integration tests that require an image registry, but you don’t have a container run-time to run that registry’s image.

Using crane append, you can either add layers to an existing image, or create a new image from scratch. This way, you can provide a zipped tarball of files or folders and push them as layers, uploading them as OCI artifacts. Note, however, that the structure of the JSON manifests is different compared to what the ORAS CLI would produce. 

The ORAS CLI, backed by Microsoft, focuses on uploading files or folders as OCI artifacts, either as standalone artifacts (using “oras push”), or by attaching artifacts to pre-existing artifacts (via “oras attach”). In the latter case, ORAS uses the OCI distribution spec’s referrers feature. The CLI can also discover which other artifacts reference a given artifact digest/tag.

The CLI also supports other useful operations, such as deleting or copying manifests. The copy command (“oras cp“) has an optional CLI argument include copying referencing artifacts along with the artifact you specified in the command.

ORAS also comes with a dedicated retagging command. However, the same can be achieved (in any tool) using the copy command (e.g. “oras cp server.com/img:old-tag server.com/img:new-tag“).

What sets ORAS apart from other implementations is that its library has been reimplemented in various languages other than Go: there is a port for Python, Java, and Rust. 

Skopeo is backed by RedHat. It was created in 2016, being about 4 years older than Regctl, crane, and ORAS, which all started around 2020.

Skopeo has various inspection commands, but it lacks support for referrers or manipulating OCI artifacts.

Skopeo’s two most-used commands are probably “skopeo copy” and “skopeo sync”.

  • Copy command:
    • As you would expect, “skopeo copy” copies one specific image tag or manifest, either between different registries, or from one image to another image, or even just within one specific image (creating an additional tag).
    • For a multi-platform image, Skopeo by default only copies the architecture of the machine that runs the command. Use the --multi-arch=all switch to override this. In that case, all CPU architectures are copied. It is impossible to tell Skopeo to copy only specific architectures!
    • Several features make Skopeo’s copy command more powerful, compared to the other tools’ copy command:
      • Skopeo can copy existing Cosign signatures along with the image. This is not configurable via a CLI argument, but you need to create a registry configuration file in /etc/containers/registries.d/default.yaml (or create an arbitrarily-named YAML file in a different folder whose path you provide to Skopeo via --registries.d <folder-path>). In that file, you need to set use-sigstore-attachments: true for both the source and destination registry. See the snippet from this tutorial.
      • Skopeo can sign images during the copy process with Cosign (the Cosign functionality is embedded into the Skopeo CLI, you don’t need a separate Cosign CLI). See here for a tutorial that explains how to use a static private key for signing (which does not involve Fulcio/Rekor). See here to learn how you can use keyless signing (with Fulcio/Rekor), using a “Sigstore signing params” file.
      • Skopeo can be configured to retry several times, which is useful when the source or destination registry has temporary issues.
      • Skopeo can be configured with a trust policy, validating a PGP or Cosign signature of the source image, refusing to copy the image if the source image’s signature is invalid. You do this in a containers-policy.json file, see the docs. You can override the file location via “--policy <path-to-policy>”.
  • Sync-command
    • The sync command also supports the three signature-related features offered by Skopeo’s copy command, that I discussed above. In other words: the sync command will check the validity of source image signatures (if you configured a trust policy), copy existing image signatures, or sign destination images.
    • The sync command can discover all tags and copy them, or copy only a configurable subset. In the latter case, you need to set up a sync.yaml file (see the documentation for an example) in which you configure either one or more regular expressions (e.g. “^1\.13.*-alpine$” to match something like “1.13-alpine” or  “1.13.2-alpine”), or semantic version comparison strings (e.g. “>= 3.5”). However, be careful with the latter, because many Docker images don’t use “vanilla” semantic versions (such as “1.2.3”) in their tags, but more complicated version tags like “edge-1.2.3-alpine3.18”, which Skopeo’s semver parser cannot process, thus such images won’t be copied!

Regctl is one of several tools that build on top of the regclient Go library.

Aside from various inspection commands, Regctl offers extensive manipulation options for image indices (“regctl index add“) and individual image manifests (“regctl image mod“). The “regctl image copy” can optionally copy Cosign-tags (--digest-tags) or OCI artifacts that reference the image (--referrers).

Regctl is the only tool offering a non-destructive “tag delete” command.

Apart from from regctl, the maintainers of the regclient library also built two separate CLIs:

  • regsync is an image mirroring tool that you configure with a YAML file. It can run continuously as a server (where you configure a cron job in the yaml file), or you call it as CLI in a “sync once” mode. regsync can be configured to synchronize several or all images (other tools don’t support this!), or only one specific image. Regarding the tags to be synchronized, regsync can either sync all tags, or you can specify one or more regular expressions in an allow-list and a deny-list. regsync processes both of these lists, thus you can configure an allow list such as “3\\.\\d+” to sync all tags named “3.x”, but then add “3\\.0” to the deny list, thus effectively synchronizing only 3.1 and newer.
  • regbot is a CLI that allows building custom image/tag manipulation workflows using the Lua scripting language. Regbot exposes a Lua API for manipulating images that you can call in your Lua script. I think that regbot targets people who need very customized routines (beyond what “regctl copy” or regsync offer) that copy/manipulate/delete images, assuming that these people are uncomfortable with implementing such a customized routine in Go directly, but would be comfortable using Lua. You can find elaborate examples here, such as “copy the 3 most recent minor versions from the Alpine image” or “delete images that have a timestamp in a specific image annotation that is more than 30 days old”.

Why copying Cosign signatures across registries does work

When you copy an image from one registry to another and the source registry stores a Cosign signature of that image, then you can (and should) copy that Cosign signature to the destination registry along with the image. This allows you to verify the image when deploying it from the destination registry.

At first, I thought that copying Cosign signatures should not work. Why? Because the signature’s subject clearly indicates the full image name (including the source registry hostname) and its digest, as the following example demonstrates (which is the content of a application/vnd.dev.cosign.simplesigning.v1+json manifest):

{
  "critical": {
    "identity": {
      "docker-reference": "ttl.sh/signtestt" // full image name of the source registry
    },
    "image": {
      "docker-manifest-digest": "sha256:1c4eef651f65e2f7daee7ee785882ac164b02b78fb74503052a26dc061c90474"
    },
    "type": "cosign container image signature"
  },
  "optional": null
}
Code language: JSON / JSON with Comments (json)

However, Cosign only verifies that the digest of the image you are verifying matches the value of
docker-manifest-digest of the above manifest. Cosign’s verification process ignores the docker-reference value, presumably to make copying image signatures possible.

Conclusion

I presented many tools, but you likely won’t have time to try each one. Hence, this is my personal recommendation:

  • For image browsing, I only use (and highly recommend) oci.dag.dev, because it offers the most features
  • For manipulating images, it depends on the use case. Skopeo has the advantage of being most feature-complete when it comes to copying/syncing images that involve Cosign signatures, as it allows checking signatures or signing unsigned images.

Did I miss any tool, or do you want to share your experience? Let me know in the comments!

Leave a Comment