Copying Container Images Between Container Registries

James Spurin
3 min readApr 3, 2023

--

When developing technical content related to Cloud Native and Container technologies, I am particularly mindful of using third-party containers that are beyond my control.

Those following a tutorial may experience difficulties if the container image in question has been altered or removed.

For example, in a tutorial that I am currently working on, I utilise a friendly image from google-samples, hosted on gcr.io, the Google Compute Registry.

Terminal making use of the gcr.io/google-samples/hello-app:1.0 image

To mitigate this, a possible approach is to copy the container image to a registry location that you control and within the tutorial, make reference to an alternative “mirror” that can be used, should there be an unexpected change in the future.

Manually copying images, particularly those containing multiple platforms such as amd64 and arm64, can be both challenging and time consuming. Thankfully, Google offers an excellent CLI tool that presents a user-friendly solution which can be easily executed as a standalone container using tools like Docker.

Crane, as the name suggests, simplifies the process of transferring container images between registries. The CLI tool uses a crane bird carrying a container as it’s logo to visually emphasise its functionality.

Mighty Crane Bird, showing the container image who’s boss with the ease in which it can move it between one location and another.

Full information available here: https://github.com/google/go-containerregistry/tree/main/cmd/crane

In this brief example, I aim to copy the source container image from the Google Container Registry to my own Docker Hub account, under the username spurin. I want to accomplish this as quickly as possible so I prefer not to build the Crane CLI tool for this task.

Fortunately, an official Crane image is available for us to use. We’ll need to complete this in two stages with the first stage being authentication to our target repository (assuming that the first repository is publicly available).

During this process, it is essential to ensure persistence for our authenticated tokens; thus, we’ll use the -v flag to specify a local directory volume. As I’m using a Mac, I’ll store these tokens locally in the /Users/james/crane directory

$ docker run -v /Users/james/crane:/home/nonroot --rm gcr.io/go-containerregistry/crane auth login registry.docker.com -u spurin --password NotMyRealPasswordHonest
2023/04/03 16:48:45 logged in via /home/nonroot/.docker/config.json

Once authenticated, we can now utilise Crane to copy container images for all available platforms from one location to another -|

$ docker run -v /Users/james/crane:/home/nonroot --rm gcr.io/go-containerregistry/crane copy gcr.io/google-samples/hello-app:1.0 registry.docker.com/spurin/hello-app:1.0
2023/04/03 16:51:25 Copying from gcr.io/google-samples/hello-app:1.0 to registry.docker.com/spurin/hello-app:1.0
2023/04/03 16:51:26 retrying without mount: POST https://registry.docker.com/v2/spurin/hello-app/blobs/uploads/?from=google-samples%2Fhello-app&mount=sha256%3Afbad7aa519f7da86dde82ab83e3130d8024e9ffebc315cead1fecb230e208340&origin=REDACTED: UNAUTHORIZED: authentication required; [map[Action:pull Class: Name:spurin/hello-app Type:repository] map[Action:push Class: Name:spurin/hello-app Type:repository] map[Action:pull Class: Name:google-samples/hello-app Type:repository]]
2023/04/03 16:51:27 retrying without mount: POST https://registry.docker.com/v2/spurin/hello-app/blobs/uploads/?from=google-samples%2Fhello-app&mount=sha256%3Afda4ba87f6fbeebb651625814b981d4100a98c224fef80d562fb33853500f40e&origin=REDACTED: UNAUTHORIZED: authentication required; [map[Action:pull Class: Name:spurin/hello-app Type:repository] map[Action:push Class: Name:spurin/hello-app Type:repository] map[Action:pull Class: Name:google-samples/hello-app Type:repository]]
2023/04/03 16:51:27 retrying without mount: POST https://registry.docker.com/v2/spurin/hello-app/blobs/uploads/?from=google-samples%2Fhello-app&mount=sha256%3A74c357f2d0d662fcf1a514820bf1d01ed7578702ba4a28cc9162deb40906af7c&origin=REDACTED: UNAUTHORIZED: authentication required; [map[Action:pull Class: Name:spurin/hello-app Type:repository] map[Action:push Class: Name:spurin/hello-app Type:repository] map[Action:pull Class: Name:google-samples/hello-app Type:repository]]
2023/04/03 16:51:27 retrying without mount: POST https://registry.docker.com/v2/spurin/hello-app/blobs/uploads/?from=google-samples%2Fhello-app&mount=sha256%3A13753a81eccfdd153bf7fc9a4c9198edbcce0110e7f46ed0d38cc654a6458ff5&origin=REDACTED: UNAUTHORIZED: authentication required; [map[Action:pull Class: Name:spurin/hello-app Type:repository] map[Action:push Class: Name:spurin/hello-app Type:repository] map[Action:pull Class: Name:google-samples/hello-app Type:repository]]
2023/04/03 16:51:28 pushed blob: sha256:13753a81eccfdd153bf7fc9a4c9198edbcce0110e7f46ed0d38cc654a6458ff5
2023/04/03 16:51:28 pushed blob: sha256:fbad7aa519f7da86dde82ab83e3130d8024e9ffebc315cead1fecb230e208340
2023/04/03 16:51:32 pushed blob: sha256:fda4ba87f6fbeebb651625814b981d4100a98c224fef80d562fb33853500f40e
2023/04/03 16:51:33 pushed blob: sha256:74c357f2d0d662fcf1a514820bf1d01ed7578702ba4a28cc9162deb40906af7c
2023/04/03 16:51:33 registry.docker.com/spurin/hello-app:1.0: digest: sha256:845f77fab71033404f4cfceaa1ddb27b70c3551ceb22a5e7f4498cdda6c9daea size: 949

And just like that, we’re good to go!

I hope you found this short overview useful. You should be able to leverage crane to copy to and from any container registries!

--

--

James Spurin

CNCF Ambassador | Docker Captain | Founder & Technical Content Creator at DiveInto