41

On a private registry (myregistry.com), say I have an image tagged with 'v1.2.3'. I then push it by:

docker push myregistry.com/myimage:v1.2.3

If I want to associate another tag, say 'staging', and push that tag to my registry, I can:

docker tag myregistry.com/myimage:v1.2.3 myregistry.com/myimage:staging
docker push myregistry.com/myimage:staging

Though this works, the second docker push still runs through each image, attempting to push it (albeit skipping upload). Is there a better way just to add a remote tag?

Walery Strauch
  • 6,792
  • 8
  • 50
  • 57
jvliwanag
  • 1,508
  • 1
  • 13
  • 29

6 Answers6

32

The way you've stated, docker tag ...; docker push ... is the best way to add a tag to an image and share it.

In the specific example you've given, both tags were in the same repo (myregistry.com/myimage). In this case you can just docker push myregistry.com/myimage and by default the docker daemon will push all the tags for the repo at the same time, saving the iteration over layers for shared layers.

You can use the same process (docker tag ...; docker push ...) to tag images between repositories as well, e.g.

docker tag myregistry.com/myimage:v1.2.3 otherregistry.com/theirimage:v2
docker push otherregistry.com/theirimage
Andy
  • 35,844
  • 6
  • 43
  • 50
  • 1
    what if I want to tag only 1 image and just push a couple of them? i.e. say my local docker stores `v.1.1`, `v.1.2` and `v.1.3`. I would like to tag `v.1.3` with `latest` and push both `v.1.3` and `latest` but leave older versions where they are? – gru Oct 23 '15 at 09:15
  • 3
    This does not work if you have multi-platform images and the original image you are trying to re-tag is not on the local machine (i.e. you first have to pull it). This is because "docker pull" only pulls the image layers that match the local machine's platform architecture, then if you push the new tag(s) you will only push an image with a single platform architecture layer. – Francois Nel Dec 30 '21 at 00:39
29

You can achieve this with docker buildx imagetools create

docker buildx imagetools create myregistry.com/myimage:v1.2.3 --tag myregistry.com/myimage:staging

this will simply download the image manifest of myregistry.com/myimage:v1.2.3 and re-tag (and push) it as myregistry.com/myimage:staging

NOTE: this will also retain the multi-platform manifest list when you "re-tag" (e.g. when your image is build for both linux/arm64 and linux/amd64). Where as the conventional docker pull/push strategy will only retain the image manifest for the platform/architecture of the system you do the pull/push from.

Francois Nel
  • 1,662
  • 2
  • 19
  • 29
  • 1
    Actually I like this approach. In fact, I came up with it myself. Unfortunately `docker buildx imagetools create` creates a *manifest list* (wrapping the one input), which may not be appropriate in all cases. – JeffRSon Nov 30 '22 at 16:02
6

pull/tag/push method will have time&network costs, you can just remotely tag your image with:

only for changing TAG the answer https://stackoverflow.com/a/38362476/8430173 works , but I wanted to change the repository name too.

by many thanks to this, I changed the repoName too!

(by help of his Github project):

1- get manifests (in v2 schema)
2- post every layer.digest in the new repo
3- post config.layer
4- put whole manifest to new repo


details:

1- GET manifest from reg:5000/v2/{oldRepo}/manifests/{oldtag} withaccept header:application/vnd.docker.distribution.manifest.v2+json

2- for every layer : POST reg:5000/v2/{newRepo}/blobs/uploads/?mount={layer.digest}&from={oldRepoNameWithaoutTag}

3- POST reg:5000/v2/{newRepo}/blobs/uploads/?mount={config.digest}&from={oldRepoNameWithaoutTag}

4- PUT reg:5000/v2/{newRepo}/manifests/{newTag} with content-type header:application/vnd.docker.distribution.manifest.v2+json and body from step 1 response

5- enjoy!

Community
  • 1
  • 1
Negar Zamiri
  • 641
  • 2
  • 11
  • 16
  • I get this error with this approach, any idea? Error: `{"errors":[{"code":"MANIFEST_INVALID","message":"manifest invalid","detail":"Corrupt manifest my-app-name: No content to map due to end-of-input\n at [Source: (com.amazonaws.services.s3.model.S3ObjectInputStream); line: 1, column: 0]"}]}` – xbmono Sep 18 '20 at 06:34
  • @xbmono may you provide your complete request, too? – Negar Zamiri Sep 22 '20 at 22:53
  • Sorry Negar. I had to revert back all my changes because this approach didn't work and I don't have the actual request now. Thanks for replying to my comment. – xbmono Sep 23 '20 at 06:41
  • Now that ghcr.io supports GITHUB_TOKEN, you can do the following in GA `GHCR_TOKEN=$(echo ${{ secrets.GITHUB_TOKEN }} | base64) curl -H "Authorization: Bearer {GHCR_TOKEN}" https://ghcr.io/v2/USER/IMAGE/tags/list OLD_TAG=test MANIFEST=$(curl -H "Authorization: Bearer ${GHCR_TOKEN}" https://ghcr.io/v2/USER/IMAGE/manifests/$OLD_TAG) CONTENT_TYPE="application/vnd.docker.distribution.manifest.v2+json" NEW_TAG=latest curl -f -X PUT -H "Content-Type: ${CONTENT_TYPE}" -H "Authorization: Bearer ${GHCR_TOKEN}" -d "${MANIFEST}" "https://ghcr.io/v2/USER/IMAGE/manifests/$NEW_TAG" ` – 13013SwagR Jun 03 '22 at 17:44
6

With google's crane you just do

crane tag myregistry.com/myimage:v1.2.3 staging

It works with both docker images and OCI images, no image is downloaded locally and it even skips the layer verifications, as they are guaranteed to already be in the repository.

It's even available in a docker image: gcr.io/go-containerregistry/crane

Note that there are other similar tools, like regctl or skopeo

Gaëtan Lehmann
  • 868
  • 1
  • 10
  • 11
4

For single platform images, you can use

docker pull repo:oldtag
docker tag repo:oldtag repo:newtag
docker push repo:newtag

However, there are a few downsides.

  1. You pull all the layers even if you don't need to run the image locally.
  2. You are dereferencing multi-platform images to your local platform.

This can be done with curl, especially if you are in the same repository (when you go across repositories, you also need to copy all the blobs). However, that has two challenges of its own:

  1. You need to accept all of the possible media types for the image you are pulling, track the media type you received, and use that same media type when pushing the manifest back to the registry. There are at least 6 media types I'm familiar with:
  • a signed and unsigned docker schema v1
  • the common manifest and manifest list in docker schema v2
  • the OCI image and index
  1. If you go across repositories, you need to parse the manifest for the included blobs, which can be recursive for docker manifest lists and OCI indexes. You may be able to do a server side blob mount to avoid pulling and pushing the blob, but that will depend on the server support. And we're also going to see anonymous blob mounts come to registries, which allows a blob mount even if you don't know the source repo on that registry (useful for everyone pushing to a cloud registry with an image based on an official docker image from Docker Hub).

  2. Authorization gets complicated, particularly if you have bearer tokens to request and maintain between commands.

The solution I'd recommend is using a tool that handles the registry API for you. I've been working on my own tooling for this, regclient, and there are other similar projects like Google's crane and RedHat's skopeo. These should each handle the media types, copying blobs when needed, and authorization issues that would complicate the curl command.

As an example with regclient's regctl command, you'd run:

regctl image copy repo:oldtag repo:newtag
BMitch
  • 231,797
  • 42
  • 475
  • 450
  • This solution is the only way I found (besides curl) that does not create manifest lists (like `docker manifest create` or `docker buildx imagetools create` do. Was using regclient previously anyway - thanks! – JeffRSon Nov 30 '22 at 16:22
2

There is a simpler method with the new experimental manifest Docker commands. It only requires downloading and uploading the manifest file (JSON overview) of an image. The commands below have been tested with the GitLab registry. First build and push a Docker image in some previous stage:

docker build -t registry.gitlab.com/<group>/<project>/<image-name>:<tag-a> .
docker push registry.gitlab.com/<group>/<project>/<image-name>:<tag-a>

Then at a later stage where the image has not been pulled:

docker manifest create registry.gitlab.com/<group>/<project>/<image-name>:<tag-b> \
    registry.gitlab.com/<group>/<project>/<image-name>:<tag-a>
docker manifest push registry.gitlab.com/<group>/<project>/<image-name>:<tag-b>

You can then pull the image with the new tag. The first step did not seem to work with public Docker Hub images, but any suggestions are welcome. Additionally, to see the manifest itself, run:

docker manifest inspect registry.gitlab.com/<group>/<project>/<image-name>:<tag-a>
Moritz
  • 2,987
  • 3
  • 21
  • 34
  • "docker manifest create" only seems to work with OCI images that are not manifest lists them selves (e.g. when the image is a multi-platform image manifest). Probably why it did not work with public Docker Hub images as most of them are multi-platform images – Francois Nel Dec 30 '21 at 00:31
  • Furthermore, `docker manifest create` itself creates manifest lists. – JeffRSon Nov 30 '22 at 16:18