Stitch Images Together with docker manifest create

One tag, two CPU architectures: docker manifest create builds the list that makes it work. You will make a local manifest list that points one image tag at several platform-specific images, then annotate, inspect and push it. Allow about 15 minutes if the component image tags already exist and you have registry access.

This guide uses the Docker CLI installed on this machine: docker-ce-cli 5:29.8.1-1~ubuntu.24.04~noble.

A manifest list is metadata. It is not a new image holding another copy of every layer. Its entries point at image manifests such as an AMD64 Linux build and an ARM64 Linux build. The client or runtime picks the suitable entry when the published tag is pulled.

1. Check the installed command

docker manifest create is marked experimental by this Docker release. Experimental commands can change or disappear between releases, so check the local help before putting the syntax in automation. No elevated privilege is needed for this check.

$ docker version --format '{{.Client.Version}}'
29.8.1
$ docker manifest create --help
Usage:  docker manifest create MANIFEST_LIST MANIFEST [MANIFEST...]

Create a local manifest list for annotating and pushing to a registry

Two options matter here:

The command accepts one or more source manifests after the list name.

2. Choose one destination and its source images

Use the same registry, repository and release tag for the list and its component images. The source images must already be available from that registry. A tag is easy to overwrite, so use digests when your release process requires immutable inputs.

For this example, replace every uppercase value with a real value. The source names are illustrative, not images you can assume exist:

$ REGISTRY='registry.example.com'
$ REPOSITORY='team/widget'
$ VERSION='1.4.0'
$ LIST="$REGISTRY/$REPOSITORY:$VERSION"
$ docker manifest create "$LIST" \
    "$REGISTRY/$REPOSITORY:1.4.0-amd64" \
    "$REGISTRY/$REPOSITORY:1.4.0-arm64"

Docker queries the registry while creating the list. With reachable, correctly named images, the result is similar to:

Created manifest list registry.example.com/team/widget:1.4.0

The list lives in the CLI's local manifest-list state. Users pulling from the registry cannot see it yet, and the Docker engine does not use it for a pull.

3. Inspect the local result before changing it

Inspect the exact list name you created. This is an ordinary, unprivileged command. The verbose form shows each referenced digest and its platform when the registry data provides that information.

$ docker manifest inspect --verbose "$LIST"
{
    "Ref": "registry.example.com/team/widget:1.4.0",
    "Digest": "sha256:...",
    "Platform": {
        "architecture": "amd64",
        "os": "linux"
    }
}
$ docker manifest inspect "$LIST" | head

A displayed digest is not proof that the right build was selected. Compare the source tags or digests with the release record, and check that the list contains every intended operating-system and architecture pair.

Tip: If a source image reports the wrong platform, use docker manifest annotate only when you have verified the image and know its correct platform metadata.

4. Amend an existing list deliberately

Adding a source to a list that already exists locally requires --amend. First inspect the current list and record its entries. Then repeat the complete source list, including entries you want to keep:

$ docker manifest inspect --verbose "$LIST" > /tmp/widget-manifest-before.json
$ docker manifest create --amend "$LIST" \
    "$REGISTRY/$REPOSITORY:1.4.0-amd64" \
    "$REGISTRY/$REPOSITORY:1.4.0-arm64" \
    "$REGISTRY/$REPOSITORY:1.4.0-ppc64le"
$ docker manifest inspect --verbose "$LIST"

Checkpoint: The new output shows the three expected platforms. Confirm that before pushing. If the amendment is wrong, recreate the list with the correct complete source list. The local list is disposable, and the registry contents stay unchanged until a push.

5. Use an insecure registry only at a known boundary

For a registry with a missing or self-signed certificate, pass --insecure to the create operation so its registry queries use that exception:

$ docker manifest create --insecure \
    'registry.example.lan/team/widget:1.4.0' \
    'registry.example.lan/team/widget:1.4.0-amd64' \
    'registry.example.lan/team/widget:1.4.0-arm64'

Warning: This weakens transport verification for that registry transaction. Do not add the flag to make an ordinary TLS error disappear, and do not use it for a public registry. Fix the certificate or registry configuration where possible.

The later push also needs docker manifest push --insecure. Creating the list with the flag does not make the push safe automatically.

6. Push only after the checkpoint passes

Warning: Pushing publishes the manifest list under the destination tag and can change what future pulls select. Check the repository, tag, component digests and platform entries one last time. Pushing normally requires registry authentication, but it does not require sudo.

$ docker manifest inspect --verbose "$LIST"
$ docker manifest push "$LIST"
sha256:... 

The returned digest identifies the published manifest list. Verify the registry view, not just the local copy:

$ docker manifest inspect --verbose "$LIST"
$ docker pull "$LIST"
$ docker image inspect "$LIST" --format '{{.Os}}/{{.Architecture}}'

The last command describes the image selected for the current host. It does not list every platform in the manifest.

Recovery: To undo a mistaken local list, remove it with docker manifest rm "$LIST". That does not delete the registry images or undo a published tag. To recover a published mistake, publish a corrected list to the same tag according to your release policy, or use a new tag. Treat tag replacement as a release operation and record it.

Common traps

Done means