The open specs are why images built by Docker run on Kubernetes, Podman and any registry.
The Open Container Initiative (OCI) defines three specifications that make containers portable between tools.
The image spec describes an image as content-addressed blobs, each referenced by a descriptor (media type, sha256 digest, size):
- An image index lists manifests per platform (
linux/amd64,linux/arm64), plus, in images built by buildx, attestation manifests such as SBOMs and provenance. - An image manifest points to one config blob and an ordered list of layer blobs.
- The config JSON holds the runtime defaults (
Entrypoint,Cmd,Env,User,WorkingDir), the history, and therootfs.diff_ids: digests of the uncompressed layers. - Layers are tar archives of filesystem changes (with whiteouts), usually gzip- or zstd-compressed; their digest in the manifest is of the compressed bytes.
The image's digest is the hash of its manifest (or index). Since that manifest names every blob by hash, one digest pins every byte. Docker's own v2 manifest format is a near twin and both are accepted everywhere.
The distribution spec is the registry HTTP API: GET /v2/<name>/manifests/<reference> and GET /v2/<name>/blobs/<digest>, with uploads in chunks. Registries now also store non-image artifacts such as Helm charts, signatures and SBOMs, linked through the subject field (the referrers API).
The runtime spec defines a bundle: a root filesystem directory plus config.json describing the process, mounts, namespaces, cgroups, capabilities and seccomp profile, and the lifecycle operations (create, start, kill, delete). runc is the reference implementation; the engine translates image config plus docker run flags into that config.json.
Example
# Index -> per-platform manifests
docker buildx imagetools inspect --raw nginx:1.27 | head -20
# {"mediaType":"application/vnd.oci.image.index.v1+json",
# "manifests":[{"digest":"sha256:...","platform":{"architecture":"amd64","os":"linux"}}, ...]}
# One manifest: config + layers (crane is a small registry CLI)
crane manifest --platform linux/arm64 nginx:1.27
# {"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:..."},
# "layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:...","size":29136913}, ...]}
crane config nginx:1.27 | jq '.config.Cmd, .rootfs.diff_ids[0]'
# Generate a runtime-spec config.json
mkdir bundle && cd bundle && runc spec && jq '.process.args, .linux.namespaces' config.jsonRemember
- Index -> manifest -> config + layers, all addressed by sha256 digest
- The image digest is the manifest's hash, pinning every byte
- Distribution spec = registry HTTP API; registries also store artifacts
- Runtime spec = rootfs + config.json, executed by runc
Watch out for
- Comparing layer digests from the manifest with config diff_ids — one is of compressed bytes, the other uncompressed, so they differ.