Create Multi-Architecture Images with `podman manifest`

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package podman-5.8.2-5.el10_2.x86_64
Applies to Any Linux host with Podman installed
Privilege Rootful examples on the lab host; same commands work rootless
Scope Creating and publishing multi-architecture manifest lists with podman manifest — inspect remote indexes, pull by platform, create/add/annotate/inspect/push, podman build --platform --manifest, assembly without local cross-build, and emulation requirements. Does not cover full podman build basics, QEMU installation, podman farm build, registry authentication depth, or OCI artifact manifests.
Related guides Install Podman on RHEL

One image name such as example.com/app:v1 can resolve to different filesystems on AMD64, ARM64, and other platforms. Podman does that with a manifest list (Docker term) or OCI image index — not by storing one binary that runs everywhere. This guide walks through podman manifest to create that list, add per-architecture images, and publish a single tag clients can pull.


What is a multi-architecture container image?

A registry tag you pull is not always a single image tarball. For popular projects it is often an index that points at several platform-specific images:

text
example.com/app:v1
manifest list / OCI image index
        ├── linux/amd64 → image digest A
        ├── linux/arm64 → image digest B
        └── linux/s390x → image digest C

Each architecture keeps its own layers and config blob. The index only records which digest belongs to which os/architecture (and optional variant). When you run podman pull example.com/app:v1 on an AMD64 host, the client reads the index, picks the linux/amd64 entry, and downloads that image.

That separation matters for everything below:

  • Building and assembling are different jobs — you can publish a multi-architecture tag without cross-compiling on one machine.
  • podman manifest manages the index; it does not compile code for another CPU.
  • Pulling a foreign architecture stores that image locally even when the host cannot run it without emulation.

Inspect an existing multi-architecture image

Before creating your own list, inspect a published multi-architecture reference. Podman can read the remote index without pulling every platform image:

bash
podman manifest inspect docker.io/library/alpine:latest

Sample output (truncated):

output
{
    "schemaVersion": 2,
    "mediaType": "application/vnd.oci.image.index.v1+json",
    "manifests": [
        {
            "mediaType": "application/vnd.oci.image.manifest.v1+json",
            "size": 1022,
            "digest": "sha256:79ff19e9084a00eece421b2523fb93e22d730e2c0e525905de047e848e56d95f",
            "platform": {
                "architecture": "amd64",
                "os": "linux"
            }
        },
        ...
    ]
}

The fields you will use most often:

  • manifests — one object per platform (or per attestation) in the index
  • digest — content address of that platform's image manifest
  • platform.architecture, platform.os, and optional platform.variant — selector the client matches against
  • mediaType — distinguishes image manifests from attestation or unknown-platform entries on some registries

Official images may also list attestation manifests with "architecture": "unknown". Focus on entries with a real linux/amd64 or linux/arm64 platform when you verify multi-architecture coverage.


Pull a specific architecture

podman pull accepts --platform to choose one entry from a multi-architecture source. See Pull images with podman pull for general pull options; here the flag selects the manifest entry.

Pull the AMD64 variant of Alpine:

bash
podman pull --platform linux/amd64 docker.io/library/alpine:latest

After the pull completes, confirm what landed in local storage:

bash
podman image inspect docker.io/library/alpine:latest --format 'arch={{.Architecture}} os={{.Os}}'

Sample output:

output
arch=amd64 os=linux

Pull the ARM64 variant of the same tag:

bash
podman pull --platform linux/arm64 docker.io/library/alpine:latest

Check the architecture again — the tag now points at the ARM64 image you just selected:

bash
podman image inspect docker.io/library/alpine:latest --format 'arch={{.Architecture}} os={{.Os}}'

Sample output:

output
arch=arm64 os=linux

--platform selects an index entry. It does not convert AMD64 bytes into ARM64. The ARM64 image may sit in local storage on an AMD64 host, but running it without emulation leads to an exec format error — covered later in this page and in troubleshooting guides for that symptom.


Create a manifest list with podman manifest create

podman manifest create allocates a local manifest list object. Start with an empty list:

bash
podman manifest create localhost/demo-empty:v1

Podman prints the new list ID and stores the object locally until you push or remove it.

Inspect the empty list:

bash
podman manifest inspect localhost/demo-empty:v1

Sample output:

output
{
    "schemaVersion": 2,
    "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
    "manifests": []
}

An empty manifests array is expected — you add platform images next.

You can also seed a list from an existing remote multi-architecture image. The --all flag copies every manifest entry from the source index:

bash
podman manifest create --all localhost/demo-multi:v1 docker://docker.io/library/alpine:3.20

Inspect the result to see multiple platforms (and any attestation entries the upstream index carries):

bash
podman manifest inspect localhost/demo-multi:v1

Sample output (truncated):

output
{
    "schemaVersion": 2,
    "mediaType": "application/vnd.oci.image.index.v1+json",
    "manifests": [
        {
            "digest": "sha256:c64c687cbea9300178b30c95835354e34c4e4febc4badfe27102879de0483b5e",
            "platform": {
                "architecture": "amd64",
                "os": "linux"
            }
        },
        ...
    ]
}

--all is a shortcut when you want a local copy of someone else's index. The assembly workflow in the next sections builds a smaller list from images you control.


Add images with podman manifest add

When per-architecture images already exist locally or in a registry, add them to your list one at a time.

Create a fresh list for the lab assembly:

bash
podman manifest create localhost/demo-app:v1

Pull and tag an AMD64 image, then add it:

bash
podman pull --platform linux/amd64 docker.io/library/alpine:3.20

Tag immediately so the AMD64 layer set keeps its own name before the next platform pull overwrites the shared tag:

bash
podman tag docker.io/library/alpine:3.20 localhost/demo-amd64:v1

Add that image to the manifest list:

bash
podman manifest add localhost/demo-app:v1 localhost/demo-amd64:v1

Repeat for ARM64 — pull the ARM64 variant first:

bash
podman pull --platform linux/arm64 docker.io/library/alpine:3.20

Preserve the ARM64 layers under a dedicated tag before adding it to the list:

bash
podman tag docker.io/library/alpine:3.20 localhost/demo-arm64:v1

Add the ARM64 image to the same manifest list:

bash
podman manifest add localhost/demo-app:v1 localhost/demo-arm64:v1

Each podman manifest add prints the manifest list ID when it succeeds.

When the source reference is itself a manifest list, --all expands every entry into your list. Start with another empty list:

bash
podman manifest create localhost/demo-from-remote:v1

Merge every platform entry from the upstream Alpine index:

bash
podman manifest add --all localhost/demo-from-remote:v1 docker.io/library/alpine:3.20

That mirrors create --all but lets you start from an empty list and merge additional images later.


Set architecture metadata with --arch

podman manifest add accepts --arch and --variant to set or override platform metadata on an entry:

bash
podman manifest add --arch arm64 --variant v8 localhost/demo-app:v1 IMAGE
IMPORTANT
--arch arm64 records architecture metadata for the manifest entry. It does not transform AMD64 machine code into ARM64. Only add images whose binaries truly match the platform you declare. Mislabeled entries break pulls on the target architecture and can pass inspection while failing at runtime.

Most well-built images already ship correct OS and architecture fields in their config. You usually do not need --arch for a correctly identified single-platform image. Use it when selecting or overriding platform metadata deliberately; never use it to relabel binaries built for another architecture.


Modify entries with podman manifest annotate

podman manifest annotate updates platform metadata on an entry that is already in the list:

bash
podman manifest annotate --variant v8 localhost/demo-app:v1 sha256:DIGEST

On the lab host, annotating by image tag failed because Podman tried to resolve localhost/... through the local registry transport. Annotating by digest worked:

bash
podman manifest annotate --variant v8 localhost/demo-app:v1 sha256:45e09956dc667c5eff3583c9d94830261fb1ca0be10a0a7db36266edf5de9e1d

Podman prints the manifest list ID when the annotation succeeds.

You can adjust architecture, os, variant, and annotations on an existing entry. In practice, annotation is rare — use it when registry metadata is wrong, not on every publish path.


Build multi-architecture images with podman build

podman build can target multiple platforms and collect the results into a manifest list. That is the build-side counterpart to manual manifest add:

bash
podman build --platform linux/amd64,linux/arm64 --manifest localhost/demo-built:v1 .

Equivalent repeated form:

bash
podman build --platform linux/amd64 --platform linux/arm64 --manifest localhost/demo-built:v1 .
  • --platform names the target platforms for the build
  • --manifest tells Podman to append each platform image to the named manifest list instead of stopping at a single -t tag

On the lab host, a single-platform AMD64 build with --manifest succeeded:

bash
podman build --platform linux/amd64 --manifest localhost/demo-built:v1 -t localhost/demo-built-amd64:v1 .

Inspect the manifest after the build:

bash
podman manifest inspect localhost/demo-built:v1

Sample output:

output
{
    "schemaVersion": 2,
    "mediaType": "application/vnd.oci.image.index.v1+json",
    "manifests": [
        {
            "digest": "sha256:c93f2a8bd3845c1df5c80e88023dcc93ff1f982fa36a922403d2a98734f1438e",
            "platform": {
                "architecture": "amd64",
                "os": "linux"
            }
        }
    ]
}

One linux/amd64 entry is present — expected after a single-platform build.

Flag details, cache behavior, and Containerfile conventions live in Build images with podman build. This page focuses on how --manifest ties builds to a publishable index.


Why foreign-architecture builds need emulation

Podman can pull, tag, and index images for another architecture, but a Containerfile RUN step executes binaries for the target platform. On an AMD64 host building linux/arm64, the build container must run ARM64 /bin/sh (or equivalent):

text
amd64 host
building arm64 image
RUN /bin/sh ...
host must execute ARM64 binary

That normally requires one of:

  • QEMU user-mode emulation and binfmt_misc registration
  • a native ARM64 builder or CI node
  • a remote build farm connection

The RHEL 10 lab host used for this article has no QEMU binfmt_misc handlers. Re-run the same Containerfile for linux/arm64:

bash
podman build --platform linux/arm64 --manifest localhost/demo-built:v1 .

The build stops at the first RUN step with an exec format error:

output
STEP 2/3: RUN echo built-on-amd64 > /built.txt
exec container process `/bin/sh`: Exec format error
Error: building at STEP "RUN echo built-on-amd64 > /built.txt": while running runtime: exit status 1

Do not fake a successful cross-architecture RUN build when emulation is missing. Inspect and assemble manifest lists locally, document the emulation requirement, and build foreign architectures on native hardware or in CI.


Assemble a multi-architecture image without cross-building locally

Manifest assembly and image compilation are separate problems. A common production pattern:

  1. Build or pull linux/amd64 on AMD64 infrastructure
  2. Build or pull linux/arm64 on ARM64 infrastructure (or from a registry)
  3. Create a manifest list
  4. Add both images
  5. Inspect and push under one tag

Conceptually:

text
amd64 image ─┐
             ├── podman manifest → app:v1
arm64 image ─┘

The localhost/demo-app:v1 list built earlier follows that pattern. After both podman manifest add steps, inspect the finished list:

bash
podman manifest inspect localhost/demo-app:v1

Sample output:

output
{
    "schemaVersion": 2,
    "mediaType": "application/vnd.oci.image.index.v1+json",
    "manifests": [
        {
            "digest": "sha256:c64c687cbea9300178b30c95835354e34c4e4febc4badfe27102879de0483b5e",
            "platform": {
                "architecture": "amd64",
                "os": "linux"
            }
        },
        {
            "digest": "sha256:45e09956dc667c5eff3583c9d94830261fb1ca0be10a0a7db36266edf5de9e1d",
            "platform": {
                "architecture": "arm64",
                "os": "linux",
                "variant": "v8"
            }
        }
    ]
}

Both linux/amd64 and linux/arm64 entries are present — the tag is ready to push.

Confirm the source images before you publish — check the AMD64 member first:

bash
podman image inspect localhost/demo-amd64:v1 --format 'arch={{.Architecture}}'

Sample output:

output
arch=amd64

The ARM64 member should report arm64:

bash
podman image inspect localhost/demo-arm64:v1 --format 'arch={{.Architecture}}'

Sample output:

output
arch=arm64

Each member image matches the platform recorded in the manifest list.


Push a multi-architecture manifest to a registry

Publishing sends the manifest list and the images it references. Typical syntax:

bash
podman manifest push --all localhost/demo-app:v1 docker://REGISTRY/NAMESPACE/demo-app:v1

On Podman 5.8.x --all defaults to true, but specifying it makes the intent explicit: push the referenced platform images along with the manifest list. Registry authentication, TLS, and namespace layout are covered in Log in to a container registry and Push images to a registry — this page stays on manifest mechanics.

After push, verify the remote index the same way you inspected Alpine at the start:

bash
podman manifest inspect REGISTRY/NAMESPACE/demo-app:v1

Look for one manifest object per intended platform.architecture before you announce the release.


Test platform selection after publish

On a published multi-architecture tag, podman pull --platform proves the index resolves correctly. Using the public Alpine tag as a stand-in, pull the AMD64 entry:

bash
podman pull --platform linux/amd64 docker.io/library/alpine:latest

Confirm the architecture field on the image that arrived:

bash
podman image inspect docker.io/library/alpine:latest --format 'arch={{.Architecture}}'

Sample output:

output
arch=amd64

Pull the ARM64 entry from the same remote tag:

bash
podman pull --platform linux/arm64 docker.io/library/alpine:latest

The stored image should now report arm64:

bash
podman image inspect docker.io/library/alpine:latest --format 'arch={{.Architecture}}'

Sample output:

output
arch=arm64

Each pull retrieved the matching architecture from the remote index. Running the ARM64 image on an AMD64 host still requires emulation — pulling proves manifest selection, not runtime compatibility.


podman manifest vs podman build --manifest

Need Command
Create an empty list or copy a remote index podman manifest create (optional --all)
Add existing architecture-specific images podman manifest add
Copy every entry from a source list podman manifest add --all
Fix platform metadata on an entry podman manifest annotate
Inspect a list or remote index podman manifest inspect
Publish the list and referenced images podman manifest push
Build platform images and append to a list podman build --platform ... --manifest ...

podman manifest never compiles your application — it publishes one name that points at several builds. podman build --manifest is the integrated path when you build every platform on builders that can execute the Containerfile.


Common multi-architecture mistakes

Symptom Likely cause Fix
Image runs on build host but fails elsewhere with exec format error Pulled or built for wrong architecture; no emulation on this host Inspect with podman image inspect --format '{{.Architecture}}'; see Fix Podman exec format error for diagnosis
Manifest lists only one architecture after push Second platform never added or push omitted a referenced image podman manifest inspect locally and remotely; add missing images before push
RUN fails during podman build --platform linux/arm64 on AMD64 No QEMU/binfmt or native ARM builder Build on ARM64 hardware, CI, or install emulation — do not label amd64 binaries as arm64
Clients always get amd64 despite arm64 nodes Index missing arm64 entry or cluster pulls without platform pin Inspect remote manifest; ensure arm64 digest is listed
Manifest push succeeds but pull fails on one platform Referenced digest not available in registry Re-push manifest after confirming all member images exist remotely

Setting --arch on an AMD64 binary and declaring it ARM64 creates invalid lists that pass JSON inspection but break at runtime. Treat architecture fields as descriptions of the binary, not as conversion switches.


What about podman farm build?

podman farm build distributes builds across configured Podman connections or remote builders. It can help when you have ARM64 (or other) machines registered as build endpoints. Farm setup and multi-connection wiring are outside this article's scope — the manifest commands here are what you use to publish the combined tag once per-architecture images exist.


References

Summary

A multi-architecture container tag is a manifest list or OCI image index: one name that points at separate images per linux/amd64, linux/arm64, and other platforms. You inspected a remote index with podman manifest inspect, pulled individual platforms with --platform, and saw that pulling ARM64 on AMD64 stores the image without making it runnable.

You created lists with podman manifest create, populated them with podman manifest add, and assembled localhost/demo-app:v1 from independently pulled AMD64 and ARM64 images — no cross-compilation on the lab host. podman manifest annotate adjusts metadata on an entry; --arch overrides labels only and does not change machine code.

podman build --platform ... --manifest ... builds and collects platform images when your builders can execute the Containerfile. On this host, AMD64 builds succeeded and ARM64 RUN steps failed without QEMU — an expected result you should plan for in CI or on native hardware. Push with podman manifest push, verify the remote index, and use podman pull --platform to confirm clients resolve the right digest.

When something still fails at runtime, check the actual architecture with podman image inspect before you blame the registry. Manifest management and image compilation stay separate jobs; mastering both is what makes a single tag work across clusters and laptop architectures.


Frequently Asked Questions

1. What is the difference between a manifest list and a container image?

A container image holds filesystem layers for one CPU architecture. A manifest list or OCI image index is a pointer object that lists several platform-specific images under one name and tag. Clients choose the entry that matches their architecture at pull time.

2. Does podman manifest convert amd64 images to arm64?

No. podman manifest create, add, and annotate manage metadata and references only. They do not translate machine code between architectures. Each manifest entry must point at a real image built for that platform.

3. Can I build arm64 images on an amd64 host without QEMU?

Not when the Containerfile runs foreign-architecture binaries in RUN instructions. You need QEMU user-mode emulation, a native arm64 builder, or CI that builds on arm64 hardware. You can still assemble a multi-architecture manifest by adding separately built images.

4. What is the difference between podman manifest and podman build --manifest?

podman manifest commands create and manage a manifest list from existing images. podman build --platform with --manifest builds platform images and adds them to a manifest list in one workflow. Both produce the same kind of published reference.

5. Why does podman pull --platform not change my host architecture?

--platform selects which manifest entry to download. It does not convert the image and does not let an amd64 host execute arm64 binaries unless emulation is configured separately.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)