| 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:
example.com/app:v1
│
▼
manifest list / OCI image index
│
├── linux/amd64 → image digest A
├── linux/arm64 → image digest B
└── linux/s390x → image digest CEach 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 manifestmanages 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:
podman manifest inspect docker.io/library/alpine:latestSample output (truncated):
{
"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 indexdigest— content address of that platform's image manifestplatform.architecture,platform.os, and optionalplatform.variant— selector the client matches againstmediaType— 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:
podman pull --platform linux/amd64 docker.io/library/alpine:latestAfter the pull completes, confirm what landed in local storage:
podman image inspect docker.io/library/alpine:latest --format 'arch={{.Architecture}} os={{.Os}}'Sample output:
arch=amd64 os=linuxPull the ARM64 variant of the same tag:
podman pull --platform linux/arm64 docker.io/library/alpine:latestCheck the architecture again — the tag now points at the ARM64 image you just selected:
podman image inspect docker.io/library/alpine:latest --format 'arch={{.Architecture}} os={{.Os}}'Sample 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:
podman manifest create localhost/demo-empty:v1Podman prints the new list ID and stores the object locally until you push or remove it.
Inspect the empty list:
podman manifest inspect localhost/demo-empty:v1Sample 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:
podman manifest create --all localhost/demo-multi:v1 docker://docker.io/library/alpine:3.20Inspect the result to see multiple platforms (and any attestation entries the upstream index carries):
podman manifest inspect localhost/demo-multi:v1Sample output (truncated):
{
"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:
podman manifest create localhost/demo-app:v1Pull and tag an AMD64 image, then add it:
podman pull --platform linux/amd64 docker.io/library/alpine:3.20Tag immediately so the AMD64 layer set keeps its own name before the next platform pull overwrites the shared tag:
podman tag docker.io/library/alpine:3.20 localhost/demo-amd64:v1Add that image to the manifest list:
podman manifest add localhost/demo-app:v1 localhost/demo-amd64:v1Repeat for ARM64 — pull the ARM64 variant first:
podman pull --platform linux/arm64 docker.io/library/alpine:3.20Preserve the ARM64 layers under a dedicated tag before adding it to the list:
podman tag docker.io/library/alpine:3.20 localhost/demo-arm64:v1Add the ARM64 image to the same manifest list:
podman manifest add localhost/demo-app:v1 localhost/demo-arm64:v1Each 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:
podman manifest create localhost/demo-from-remote:v1Merge every platform entry from the upstream Alpine index:
podman manifest add --all localhost/demo-from-remote:v1 docker.io/library/alpine:3.20That 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:
podman manifest add --arch arm64 --variant v8 localhost/demo-app:v1 IMAGE--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:
podman manifest annotate --variant v8 localhost/demo-app:v1 sha256:DIGESTOn the lab host, annotating by image tag failed because Podman tried to resolve localhost/... through the local registry transport. Annotating by digest worked:
podman manifest annotate --variant v8 localhost/demo-app:v1 sha256:45e09956dc667c5eff3583c9d94830261fb1ca0be10a0a7db36266edf5de9e1dPodman 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:
podman build --platform linux/amd64,linux/arm64 --manifest localhost/demo-built:v1 .Equivalent repeated form:
podman build --platform linux/amd64 --platform linux/arm64 --manifest localhost/demo-built:v1 .--platformnames the target platforms for the build--manifesttells Podman to append each platform image to the named manifest list instead of stopping at a single-ttag
On the lab host, a single-platform AMD64 build with --manifest succeeded:
podman build --platform linux/amd64 --manifest localhost/demo-built:v1 -t localhost/demo-built-amd64:v1 .Inspect the manifest after the build:
podman manifest inspect localhost/demo-built:v1Sample 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):
amd64 host
↓
building arm64 image
↓
RUN /bin/sh ...
↓
host must execute ARM64 binaryThat normally requires one of:
- QEMU user-mode emulation and
binfmt_miscregistration - 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:
podman build --platform linux/arm64 --manifest localhost/demo-built:v1 .The build stops at the first RUN step with an exec format error:
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 1Do 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:
- Build or pull
linux/amd64on AMD64 infrastructure - Build or pull
linux/arm64on ARM64 infrastructure (or from a registry) - Create a manifest list
- Add both images
- Inspect and push under one tag
Conceptually:
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:
podman manifest inspect localhost/demo-app:v1Sample 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:
podman image inspect localhost/demo-amd64:v1 --format 'arch={{.Architecture}}'Sample output:
arch=amd64The ARM64 member should report arm64:
podman image inspect localhost/demo-arm64:v1 --format 'arch={{.Architecture}}'Sample output:
arch=arm64Each 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:
podman manifest push --all localhost/demo-app:v1 docker://REGISTRY/NAMESPACE/demo-app:v1On 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:
podman manifest inspect REGISTRY/NAMESPACE/demo-app:v1Look 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:
podman pull --platform linux/amd64 docker.io/library/alpine:latestConfirm the architecture field on the image that arrived:
podman image inspect docker.io/library/alpine:latest --format 'arch={{.Architecture}}'Sample output:
arch=amd64Pull the ARM64 entry from the same remote tag:
podman pull --platform linux/arm64 docker.io/library/alpine:latestThe stored image should now report arm64:
podman image inspect docker.io/library/alpine:latest --format 'arch={{.Architecture}}'Sample output:
arch=arm64Each 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
- podman-manifest man page
- podman-manifest-push man page — pushing the index and referenced platform images
- podman-build man page —
--platformand--manifest - OCI Image Index specification
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.

