skip to content

The Open Container Initiative (OCI) publishes three specifications — image-spec, runtime-spec and distribution-spec. What does each one define, and where does each apply in the life of a container?

level: juniorimportance: must knowfreq 55%

answer

  1. image = packaging, distribution = transport, runtime = execution
  2. index → manifest → config + layers, all by digest
  3. bundle = rootfs/ + config.json
  4. runc never sees a registry
  5. create · start · kill · delete

basics

~20 s

image-spec defines the image format on disk and on the wire: layers, an image config, a manifest, and an index, all addressed by digest. distribution-spec defines the registry HTTP API used to push and pull those blobs. runtime-spec defines the unpacked bundle — a rootfs directory plus config.json — that a runtime such as runc executes.

solid answer

~50 s

The OCI standardizes three separate things. - **image-spec** — the *packaging* format. Layer blobs (tar archives, usually compressed), an image **config** JSON (env, entrypoint, user, layer diff IDs), a **manifest** pointing at the config plus its layers, and optionally an **image index** listing per-platform manifests. Everything is referenced by a `sha256:` digest, so images are content-addressed and immutable. - **distribution-spec** — the *transport*. The `/v2/` HTTP API a registry exposes: fetch a manifest by tag or digest, fetch blobs by digest, chunked blob uploads on push. - **runtime-spec** — the *execution* format. A **filesystem bundle**: a directory holding `config.json` and a `rootfs/`. `config.json` describes the process to run, mounts, namespaces, cgroup limits, capabilities and seccomp. The flow chains them: pull over distribution-spec → an image-spec artifact → unpack layers and translate the image config into a runtime-spec bundle → runc runs it. Because each boundary is specified, registries, builders and runtimes are interchangeable.

code

json · 18 lines
json
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:aaa...",
      "size": 1042,
      "platform": { "architecture": "amd64", "os": "linux" }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:bbb...",
      "size": 1042,
      "platform": { "architecture": "arm64", "os": "linux" }
    }
  ]
}

go deeper

for a junior

Be able to name the three specs and say in one line what each covers: image format, registry API, execution bundle.

for a middle

Add the anatomy — index, manifest, config, layer blobs, digests on one side; rootfs plus config.json on the other — and explain where the translation between them happens.

for a senior

Frame the specs as the boundaries that make builders, registries and runtimes interchangeable, and know which concerns (networking, snapshotting, image pull policy) deliberately fall outside them.

for a principal

Talk about the specs as the compatibility contract you build a platform on: what you can standardize on, where vendor extensions and annotations creep in, and the migration cost when a component drifts from spec.

## Why the OCI exists Before 2015 the container image format and the container runtime were whatever Docker happened to implement. As other vendors shipped runtimes and registries, the industry needed written contracts so a tool built by one vendor could consume artifacts produced by another. Docker donated its image format and its runtime (`runc`) to the **Open Container Initiative** under the Linux Foundation, and the OCI turned them into specifications. There are three, and interviewers ask about them because confusing their scopes is the single most common source of muddled answers about how containers work. ## image-spec — how an image is laid out An OCI image is not a single file. It is a small graph of **content-addressed blobs** — every object is named by the SHA-256 digest of its own bytes, written `sha256:<hex>`. Change one byte and you get a different name, so images are immutable and identical blobs are stored and transferred once. The pieces: - **Layer blobs** — tar archives of filesystem changes, usually gzip- or zstd-compressed. A layer records added and modified files, and deletions via `.wh.<name>` whiteout entries. - **Image config** — a JSON document with the runtime defaults baked in by the build (`Env`, `Entrypoint`, `Cmd`, `User`, `WorkingDir`, `Labels`, exposed ports), the build `history`, and `rootfs.diff_ids`, the ordered digests of the *uncompressed* layer tars. - **Manifest** — the per-platform entry point: a descriptor for the config blob plus an ordered array of layer descriptors. A **descriptor** is the recurring building block: `mediaType`, `digest`, `size`, and optional annotations. - **Image index** (a.k.a. manifest list) — an optional top-level object listing several manifests, each tagged with `platform.os` and `platform.architecture`. This is how one tag serves both `linux/amd64` and `linux/arm64`. Note the two kinds of layer digest: the descriptor in the manifest holds the digest of the *compressed* blob (what gets transferred), while `diff_ids` in the config hold digests of the *uncompressed* tar (what identifies the filesystem content after unpacking). ## distribution-spec — how images move The distribution-spec standardizes the registry's HTTP API, the one historically called "Registry API v2". The endpoints that matter: - `GET /v2/` — capability and auth probe. - `GET|PUT /v2/<name>/manifests/<tag-or-digest>` — fetch or publish a manifest or index. - `GET /v2/<name>/blobs/<digest>` — fetch a config or layer blob. - `POST` / `PATCH` / `PUT /v2/<name>/blobs/uploads/` — start, stream and finalize a blob upload; a client can first `HEAD` the blob digest and skip the upload entirely if the registry already has it. A pull is therefore: resolve tag → manifest or index; if an index, pick the manifest matching your platform; fetch the config; fetch each layer blob you don't already have locally. Authentication (a `401` carrying `WWW-Authenticate`, followed by a token fetch) is layered on top but is registry-operations territory rather than spec anatomy. ## runtime-spec — how a container is executed The runtime-spec deliberately knows nothing about images or registries. Its input is a **filesystem bundle**: a directory containing - `rootfs/` — the already-unpacked root filesystem, and - `config.json` — the full description of the container to create. `config.json` covers `ociVersion`; `process` (argv, env, cwd, user, capabilities, rlimits, `noNewPrivileges`); `root` (path, readonly); `hostname`; `mounts` (including `/proc`, `/sys`, `/dev` and any bind mounts); and a platform section — on Linux, `linux` — carrying the namespaces to create, cgroup `resources` limits, seccomp profile, AppArmor/SELinux labels, masked and read-only paths, and UID/GID mappings for user namespaces. There are also lifecycle `hooks`. The spec also fixes the runtime's **lifecycle verbs**: `create`, `start`, `state`, `kill`, `delete`. That is the entire contract a runtime must honour. ## How the three compose The crucial insight is that something must *translate* between image-spec and runtime-spec, and it is not the runtime. A container manager (containerd, CRI-O, Podman) pulls via distribution-spec, unpacks the image-spec layers into a rootfs through a snapshotter, then merges the image config's defaults with the caller's runtime options — port publishing, memory limits, mounts, user overrides — into a `config.json`. Only then does it hand a bundle to `runc`. That separation is what makes the ecosystem pluggable: any builder can emit image-spec artifacts, any registry can serve them over distribution-spec, and any runtime that understands runtime-spec can execute them.

  • Which of the three specs does runc implement, and what does that imply about what runc can and cannot do?
    runc implements only the runtime-spec. It takes a bundle directory that already contains an unpacked rootfs and a config.json, and creates, starts, kills or deletes the container from it. It cannot pull an image, cannot talk to a registry, and does not understand layers — all of that is the container manager's job before runc is invoked.
  • Who converts an image config into a runtime config.json?
    The container manager — containerd, CRI-O or Podman. It unpacks the image layers into a rootfs via a snapshotter, then merges the image-spec config defaults (Entrypoint, Cmd, Env, User, WorkingDir) with the caller's runtime options (memory and CPU limits, mounts, extra env, user override, network setup) into a single runtime-spec config.json. That merged document, not the image config, is what the runtime executes.

image-spec is the shipping container's packing standard, distribution-spec is the port and crane API for moving it, runtime-spec is the unpacked crate plus the assembly instructions the worker follows.

saying these in an interview costs you the question

  • Saying runc pulls images or talks to a registry — it only executes an already-prepared bundle.
  • Claiming the image config JSON *is* the runtime config.json; they are different documents and one is translated into the other.
  • Describing an image as "a tarball" or a single file rather than a manifest referencing separate blobs.
  • Assuming a tag identifies exactly one manifest, ignoring the image index that makes tags multi-platform.
  • Thinking the OCI specs cover networking or volumes end to end — networking is left to CNI and to the container manager.

context