Developer machines changed
Apple Silicon put ARM64 machines on many developers' desks. An image containing only x86_64 binaries cannot run natively on that hardware, even though both machines can run Docker.
Cloud workloads use ARM too
AWS Graviton instances use ARM64 processors. A container scheduled there needs an ARM64 image variant or a compatible emulation layer; an x86_64-only image is not enough.
When no platform matches
When Docker pulls an image, it asks the registry for a manifest list. If your Apple Silicon Mac (linux/arm64) requests an image that only has linux/amd64 built, Docker fails with:
ERROR: no matching manifest for linux/arm64/v8 in the manifest list
One Tag, Multiple Platforms
A tag such as `nginx:latest` can resolve to an image index that points to several platform-specific manifests. The client selects the entry that matches its requested operating system and architecture.
Supported Platforms
What stays consistent
The same command, such as `docker run nginx:latest`, can select a different image manifest on an Intel machine, Apple Silicon, or an ARM cloud instance. The publisher still has to build and test every advertised variant; sharing a tag does not make platform-specific behavior identical.
CPU Architectures Explained
A compiled program contains instructions for a particular CPU architecture. An instruction set architecture (ISA) defines those machine instructions and registers; operating-system and ABI conventions add details such as system calls and calling conventions. An x86_64 binary and an ARM64 binary can implement the same application, but the processors decode different instructions.
x86_64 (AMD64)
AMD introduced this 64-bit extension to x86. Intel and AMD processors use it across many developer machines and servers. Its instruction encoding is variable-length and retains compatibility with the older x86 family.
Powers most cloud workloads with high single-threaded performance. Compiled code runs natively on any Intel or AMD 64-bit CPU.
ARM64 (AArch64)
AArch64 is the 64-bit execution state introduced with ARMv8-A. Apple Silicon and AWS Graviton are familiar implementations, alongside mobile and embedded systems. Software still needs binaries and dependencies built for the target platform.
Simpler RISC design with fixed-length 4-byte instructions. Every instruction is exactly 32 bits, making decoding simpler and pipelines more efficient.
ARMv7 (armhf)
This 32-bit ARM target appears in older Raspberry Pi and embedded systems. An image index can distinguish it as `linux/arm/v7`, including the architecture variant that an `arm` label alone would leave ambiguous.
IBM s390x
This is the 64-bit architecture used by IBM Z systems. Some image publishers include an `s390x` variant, and its big-endian byte order can expose assumptions hidden on common little-endian platforms.
x86_64 Registers
- General purpose: rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8–r15
- 64-bit wide (8 bytes each)
- rax is the "accumulator" — used for return values and many instructions
- Complex instruction encoding due to legacy 8086 backwards compatibility
ARM64 Registers
- 31 general purpose: x0–x30 (64-bit) with w0–w30 as 32-bit views
- x30 is the link register (holds return address)
- No dedicated accumulator — all registers are symmetric
- Much simpler register model than x86
Endianness
Both x86_64 and ARM64 typically operate in little-endian mode for desktop/server workloads (IBM s390x is the exception — big-endian). This means multi-byte values are stored least-significant byte first.
Cross-Compilation Toolchains
| Target | Cross-Compiler | Notes |
|---|---|---|
| ARM64 (on x86_64) | gcc-aarch64-linux-gnu |
GNU toolchain for ARM64 |
| ARM64 (on x86_64) | clang --target=aarch64-linux-gnu |
LLVM/Clang |
| ARMv7 (on x86_64) | gcc-arm-linux-gnueabihf |
ARM hard-float |
| s390x (on x86_64) | gcc-s390x-linux-gnu |
IBM Z |
| x86_64 (on ARM64) | gcc-x86_64-linux-gnu |
Reverse cross-compile |
Buildx can coordinate cross-compilation, emulation, or native builders. Which path it uses depends on the builder, Dockerfile, toolchain, and registered emulators.
Operating Systems & Base Images
A Linux base image contains executables and libraries built for a target architecture. Pulling `alpine:latest` for ARM64 therefore selects a different manifest and layer content from an x86_64 pull, even though both requests use the same tag.
Linux Distributions
Alpine
A small base built around musl libc and BusyBox. Its size is useful, but native dependencies built for glibc may require different packages or compilation.
Debian
Full-featured, larger (~120 MB+), glibc. Most official images use Debian as a base.
Ubuntu
Debian-based with familiar tooling. Good balance of size and compatibility.
Distroless
Minimal runtime images that commonly omit a shell and package manager. The smaller contents reduce what ships, while also changing how you debug a running container.
Scratch
An empty starting filesystem. The application must bring every runtime file it needs, which suits carefully built static binaries and similarly self-contained artifacts.
libc Differences: glibc vs musl
Alpine Uses musl, Not glibc
Alpine uses musl libc, while Debian and Ubuntu use glibc. A native binary or Python wheel built against glibc may not run on musl without a compatible build. Go programs can often be built as static binaries, but cgo and other native dependencies can reintroduce platform and libc requirements.
Python and Node official images ship with statically linked binaries that work on both glibc and musl systems. The pip install process downloads the correct wheel for your architecture from PyPI.
Windows Base Images
| Image | Size (compressed) | Use Case |
|---|---|---|
mcr.microsoft.com/windows/servercore |
~5 GB | Full .NET Framework, legacy apps, MSI installers |
mcr.microsoft.com/windows/nanoserver |
~350 MB | Cloud-native .NET apps, microservices, .NET Core/5+ |
mcr.microsoft.com/windows |
Full Windows | Windows with desktop experience (rarely used in containers) |
Windows Images Are Huge
Windows base images are gigabytes vs megabytes for Linux. A Windows Server Core image is ~5 GB compressed. Plan for longer pull times and larger storage in CI/CD pipelines.
Windows Versions in Tags
ltsc2022
Windows Server 2022 Long-Term Servicing Channel. Current recommended version.
2004
Windows Server 2004 (older, rarely used now). Kept for compatibility with older systems.
Windows base images are platform-specific. Cannot run Windows containers on Linux hosts and vice versa.
Single-Arch Image Anatomy
A Docker image is a collection of read-only layers plus a JSON manifest and runtime configuration.
Image Directory Structure
Manifest JSON (per-platform)
Config JSON Contents
Layer DAG (Directed Acyclic Graph)
FROM python:3.12
Base image layer — typically the largest layer (Python runtime ~100 MB)
COPY requirements.txt /
Adds requirements file — tiny layer
RUN pip install
Installs dependencies — small layer with pip packages
COPY app /app
Adds your application code — most specific layer
Content-Addressable Storage
Layers Are Content-Addressable
Each object in the registry is addressed by its SHA256 digest. Two images sharing the same base layers only store those layers once in the registry. This enables deduplication, integrity verification, and parallel pulls.
Alpine's base layer might be shared across 10,000 images in a registry. This dramatically reduces storage costs and pull bandwidth.
The Manifest List (Multi-Arch Manifest)
An image index is a top-level object whose entries point to platform-specific image manifests. A publisher can attach `nginx:latest` to that index, allowing one tag to resolve to different content for each platform included in the list.
Manifest List Structure
How the Client Chooses a Variant
docker pull nginx:latest
Client calls the registry requesting the tag
Registry returns manifest list
The manifest list contains all platform-specific manifests
Client reads os/arch from kernel
Determines the requested platform from the client, daemon, or an explicit platform override
Client finds matching platform entry
Searches manifest list for matching architecture and OS
Client fetches that specific manifest
Downloads the manifest for its platform (e.g., sha256:aaa111...)
Client downloads the layers
Fetches all layer blobs referenced by that manifest
Why Not Just One Image Per Tag?
Without Manifest Lists
One tag would equal one platform only. Users would need `nginx:amd64` and `nginx:arm64` separate tags. CI/CD would need conditional logic to select the right tag per architecture.
Publishers can attach several platform manifests to one tag. When the registry returns that index, the client selects a compatible entry for the requested platform.
OCI Image Index
Docker originally defined Manifest V2 Schema 2. This was later contributed to the OCI project as the OCI Image Index specification. The OCI version uses application/vnd.oci.image.index.v1+json media type. Modern registries support both.
Registry Variant Resolution
When you pull an image, the registry sends the manifest list and your Docker client selects the matching platform entry.
How Docker Hub Builds Multi-Arch Images
GitHub webhook triggers Docker Hub
On every git push, GitHub notifies Docker Hub
Docker Hub clones the repo
Builds proceed for each defined build rule
Parallel platform builds
Each platform builds in a separate VM or QEMU container
Manifest list generated
After all platforms build, a manifest list is created and pushed along with all manifests
GitHub Actions with Buildx
In this workflow, Buildx asks the configured builder to produce each requested platform variant and publishes an index that refers to the resulting manifests. The builds may run natively, through cross-compilation, or under emulation.
AWS ECR Variant Resolution
ECR supports multi-arch manifests natively. aws ecr batch-get-image returns all platform variants. docker pull from ECR uses the same manifest list resolution as Docker Hub.
ECR also supports WCI (Windows Container Image) variant support for Windows containers.
What Happens on Mismatched Architectures
No Matching Manifest Error
If no matching platform exists in the manifest list, Docker errors with: "no manifest for linux/arm64 in manifest list".
Solution: rebuild the image with --platform support, or find an alternative multi-arch image.
Buildx — Building for Multiple Platforms
Buildx exposes BuildKit features through the Docker CLI. A builder can produce multiple platforms, share build cache, and distribute work across local or remote nodes.
Creating a Multi-Platform Builder
The --platform Flag
This requests both platform outputs. The configured builder decides whether each stage runs on a native node, uses a cross-compiler, or executes foreign binaries through a registered emulator such as QEMU.
QEMU User-Mode Emulation
Linux can register QEMU user-mode handlers for foreign executable formats. Build commands may then run foreign-architecture binaries through emulation, which is convenient but often slower and can expose compatibility differences.
Slow compared to native cross-compilation but requires no special setup. Works by registering binfmt_misc handlers on the host.
Docker Bake (HCL Definition Files)
Build Secrets & SSH Forwarding
BuildKit allows secure secret injection during build. Secrets never appear in final image layers.
Build Caching
mode=max pushes all layers (not just final). Next build pulls cached layers for much faster incremental builds.
Cross-Platform Push & Pull Flow
Full Build & Push Lifecycle
Developer runs: docker buildx bake --push
BuildKit spawns build container per platform
Each platform builds in parallel
[linux/amd64 builder] → manifest M1
[linux/arm64 builder via QEMU] → manifest M2
[linux/arm/v7 builder via QEMU] → manifest M3
Buildx generates Manifest List ML
ML = [M1(amd64), M2(arm64), M3(arm/v7)]
All objects pushed to registry in parallel
blobs/, manifests/, manifest-list/
Registry Structure After Push
Tag myrepo/app:latest → points to sha256:ML (manifest list digest)
Pull Flow (Client-Side Resolution)
docker pull myrepo/app:latest
Docker client fetches manifest list for sha256:ML
Client reads os/arch from kernel
Uses `uname -s`, `uname -m` to determine native platform
Client searches manifest list for matching platform
Found: sha256:M2 (arm64) for Apple Silicon
Docker fetches manifest M2 (arm64-specific)
Docker fetches layers referenced by M2
Note: shared base layers downloaded once
Image ready to run
Layer Sharing Optimization
Shared Base Layers
A registry stores one copy when two manifests reference the exact same blob digest. Platform-specific base layers usually contain different binaries and therefore have different digests, while architecture-independent content can still be shared.
Cross-Architecture Layer Sharing Caveats
Base image layers differ in actual bytes (x86 glibc vs ARM musl). The same logical layer may compile to different files. Shared digests only when content is identical — which for base images, it usually isn't.
Windows Containers
Windows containers run on Windows hosts only. Cannot run on Linux hosts and vice versa.
Windows Container Basics
Nano Server
Minimal Windows Server for cloud-native scenarios. No GUI, no cmd.exe, minimal PowerShell. .NET Core only (not .NET Framework). ~350 MB compressed.
Server Core
Full Windows Server with GUI tools and all traditional server roles. Full cmd.exe, PowerShell with all modules. .NET Framework 4.x supported. MSI installers work. ~5 GB compressed.
Windows Container Isolation Modes
Process Isolation
- Container shares the host's Windows Server kernel (default)
- More efficient — similar to Linux containers
- Required for Windows Server containers
Hyper-V Isolation
- Container runs inside a lightweight Hyper-V VM
- Stronger tenant isolation
- Required for untrusted workloads
- Azure Container Instances use by default
Windows Version Compatibility
| Host Version | Can Run |
|---|---|
| Windows Server 2022 | Windows Server 2022, 2019, 2004 containers |
| Windows Server 2019 | Windows Server 2019, 1809 containers |
| Windows 10 Pro/Enterprise 1909+ | Windows 10 or Windows Server containers |
Version Strictness
Cannot run a newer Windows image on an older host — version mismatch error. The manifest list for Windows images includes entries for multiple Windows versions, and Docker selects the most compatible one.
Building Windows Containers with Buildx
Windows container image stages require a compatible Windows build environment. A Linux builder can cross-compile an application for Windows, but it cannot generally execute Windows container build steps through Linux user-mode QEMU.
Real-World Examples & Manifest Inspector
nginx Official Image
Typical platforms: linux/amd64, linux/arm64, linux/arm/v7. All share the same NGINX binary compiled per platform. Same config defaults, same entrypoint — only binaries differ.
python Official Image
Typical platforms: linux/amd64, linux/arm64, linux/arm/v7, linux/s390x. Python interpreter compiled per platform. Many standard library layers are identical across platforms — different Python interpreter binary per platform.
Microsoft .NET Image
Typical platforms: linux/amd64, linux/arm64, linux/arm/v7, windows/amd64. Runtime images ship for both Linux and Windows. Windows version for .NET Framework 4.8 apps; Linux version for .NET Core/5+.
Interactive Manifest Inspector
Paste any public image reference to inspect its manifest list.
Size Differences Across Architectures
For most images, layer sizes are nearly identical across architectures. The largest layer is typically the language runtime (Python interpreter, Node.js, JVM) — typically within 5–10% of each other in compressed size.
Windows images can be dramatically different in size because:
- Windows Server Core is ~5 GB compressed vs Alpine at ~3 MB
- Different Windows cumulative update patches create different layer content
- Different base Windows versions have different feature sets
Cached Layer Analysis
When you run docker history <image>, you see each layer and its size. Layers marked <missing> are layers that exist in the remote image but haven't been downloaded locally.
docker images --digests shows the full content-addressable digest per image, letting you verify you're running exactly the bits you think you are.