Docker 71 🐳 Open Container Initiative (OCI) Specifications: Image Spec, Runtime Spec, and Distribution Spec
The Open Container Initiative (OCI) is the governance body that turned containers from a Docker-specific technology into an open industry standard. Before OCI, the container ecosystem was locked into Docker’s proprietary formats: an image built by Docker ran on Docker, a registry built by Docker served Docker, and an alternative runtime had to reverse-engineer the formats to be compatible. OCI was founded in June 2015 by Docker and other industry leaders to create open standards around container formats and runtimes, and it now maintains three specifications that cover the entire lifecycle of a container: the Image Specification (how an image is built and packaged), the Runtime Specification (how a container is configured and executed), and the Distribution Specification (how images are pushed to and pulled from registries) .
The three specifications are not independent standards that happen to share a name. They form a pipeline. A build tool produces an OCI Image according to the Image Spec. A registry stores and serves that image according to the Distribution Spec. A runtime downloads the image, unpacks it into an OCI Runtime Bundle, and executes it according to the Runtime Spec . This separation is what allows interoperability: an image built by Buildah can be pushed to a registry that implements the Distribution Spec, pulled by containerd, and run by runc, because all four tools speak the same specifications rather than a single vendor’s implementation .
Key point: The OCI Image Spec defines the structure of a container image (manifest, configuration, and filesystem layers); the Runtime Spec defines how to configure and run a container from an unpacked bundle (config.json plus root filesystem); the Distribution Spec defines the HTTP API for pushing and pulling images from a registry. Together they standardize the build, distribution, and execution of containers.
Why OCI exists and what it replaced
The vendor lock-in problem. Docker’s early dominance meant that the container ecosystem was effectively a single-vendor ecosystem. The Docker image format, the Docker Registry API, and the Docker runtime (via containerd and runc) were de facto standards, but they were controlled by one company. Alternative tools like rkt and LXC had to either reverse-engineer Docker’s formats or build their own incompatible ecosystems. OCI was established to break this lock-in by codifying the formats and APIs as open standards under the Linux Foundation’s governance .
The three-spec pipeline. The OCI explicitly describes the relationship between the specifications: “At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle. At this point the OCI Runtime Bundle would be run by an OCI Runtime” . This is not an implementation detail; it is the architectural model. The Image Spec is about the artifact that is distributed. The Distribution Spec is about how the artifact moves between machines. The Runtime Spec is about what happens when the artifact is executed. Each can evolve independently as long as the interfaces between them remain stable.
The conformance problem. A specification without conformance testing is a suggestion. OCI maintains conformance suites for the Image and Distribution specifications, and registry providers can self-certify by submitting conformance results . The conformance suite has been rewritten to detect issues that previous versions missed . This is what makes the specifications enforceable rather than aspirational: a registry that claims OCI conformance can be verified against the suite, and an image that claims OCI conformance can be checked with the same tools.
a. The Image Specification: the artifact
An OCI Image is not a single file. It is a collection of content-addressable blobs: an image manifest, an image configuration, and one or more filesystem layers . The manifest is the entry point. It describes the image by referencing the configuration and the layers, each identified by a cryptographic digest. The configuration contains the runtime settings that will be used when the image is executed: the entrypoint, the command arguments, the environment variables, the working directory, and the user . The layers are the filesystem changesets that are applied in order to produce the final root filesystem.
The content-addressable design is what makes the format verifiable and deduplicatable. Every blob is identified by the hash of its content. If two images share a base layer, the registry stores that layer once, and both manifests reference the same digest . When an image is pulled, each layer’s digest is verified against the manifest. If the content has been tampered with, the digest does not match, and the pull fails. This is the foundation of the supply chain security that tools like cosign and notation build on.
The image index (also called a manifest list in Docker terminology) is an optional higher-level manifest that points to multiple platform-specific manifests. A single tag like myapp:latest can resolve to an index that contains a Linux amd64 manifest, a Linux arm64 manifest, and a Windows manifest. The runtime selects the manifest that matches its platform . This is how multi-architecture images work: one tag, multiple manifests, and the client picks the right one.
The OCI Image Spec v1.1, released in February 2024, added the subject field and the artifactType field. The subject field allows a manifest to reference another manifest, which is how signatures and attestations are associated with an image. The artifactType field allows non-container artifacts—Helm charts, OPA policies, WebAssembly modules—to be stored in an OCI registry using the same manifest structure . This is what turned OCI registries into general-purpose artifact stores, not just container image stores.
b. The Runtime Specification: the execution
The Runtime Spec defines the interface between a container runtime (like runc or crun) and the rest of the system. The central concept is the bundle: a directory containing a config.json file and a root filesystem directory . The config.json is the complete specification of how the container should be created: the process to run, the environment, the mounts, the namespaces, the cgroup limits, and the seccomp profile. The runtime reads this file and creates the container environment accordingly.
The config.json has several key sections. The process section defines the program to execute, the arguments, the environment variables, the working directory, and the user. The root section points to the root filesystem. The mounts section lists the filesystems to mount inside the container (proc, sysfs, tmpfs, and so on). The linux.namespaces section specifies which namespaces to create for isolation: pid, network, mount, ipc, uts, user, and cgroup . The linux.resources section maps to cgroup controls for CPU, memory, and I/O limits.
The lifecycle is precisely defined. The runtime’s create command creates the environment but does not run the user process. The start command runs the prestart hooks, executes the user process, and runs the poststart hooks. The container states are creating, created, running, and stopped . The separation between create and start is deliberate: it allows the runtime to set up the environment (namespaces, cgroups, mounts) before the application starts, and it allows tools like runc exec to enter the container after it is running. This lifecycle is what makes runc suitable as a low-level building block for higher-level runtimes like containerd and CRI-O.
The Runtime Spec does not define how the root filesystem is produced. It assumes the bundle already exists on disk. The Image Spec defines how to produce the bundle: unpack the layers in order, apply the configuration, and the result is the root filesystem and the config.json. The Distribution Spec defines how to get the image to the machine in the first place. The runtime is the last step in the pipeline, and it is deliberately unaware of the steps that came before it.
c. The Distribution Specification: the transport
The Distribution Spec defines the HTTP API that registries implement for pushing and pulling content. It is based on the Docker Registry HTTP API V2 protocol, which Docker contributed to OCI in 2018 . The API is organized around the /v2/ endpoint prefix, and the core operations are pull, push, content discovery, and content management .
The pull path is the most fundamental. A client retrieves a manifest by sending a GET request to /v2/<name>/manifests/<tag-or-digest>, where <name> is the repository name and <tag-or-digest> is either a tag or a digest . The registry returns the manifest, and the client then retrieves each layer and the configuration by digest. The push path is the reverse: the client uploads blobs, then uploads the manifest that references them. The registry verifies the digests and stores the content.
The API is content-addressable at every level. Manifests, configurations, and layers are all addressed by digest. This is what enables the layer deduplication that makes container registries efficient: if two images share a layer, the registry stores it once, and both manifests reference the same digest . It also enables resumable pulls and pushes: a client that loses its connection can use HTTP Range requests to resume from where it stopped.
The Distribution Spec v1.1, released alongside the Image Spec v1.1, added the GET /v2/<name>/referrers/<digest> endpoint. This endpoint returns an image index listing all the manifests that have the specified digest in their subject field. This is how a client discovers the signatures and attestations associated with an image: it queries the referrers endpoint for the image’s digest, and the registry returns the list of associated artifacts . This is the mechanism that makes OCI registries suitable for storing supply chain metadata alongside the images themselves.
Complete Example Session
The following session traces an image from build to execution, showing the role of each specification at each step.
// ============================================
// PART 1: BUILD (Image Spec)
// ============================================
// A build tool (Buildah, Docker, ko) produces an OCI Image:
// - image manifest (references config + layers by digest)
// - image configuration (entrypoint, env, args)
// - filesystem layers (tar archives of changesets)
//
// The manifest is stored in a local OCI layout:
// oci-layout
// index.json
// blobs/sha256/<digest>
// ============================================
// PART 2: PUSH (Distribution Spec)
// ============================================
// The tool pushes to a registry:
// POST /v2/myapp/blobs/uploads/ → initiate upload
// PUT /v2/myapp/blobs/uploads/<id> → upload layer
// PUT /v2/myapp/manifests/v1 → upload manifest
//
// The registry stores blobs by digest.
// If a blob already exists, the upload is skipped (deduplication).
// ============================================
// PART 3: PULL (Distribution Spec)
// ============================================
// A runtime (containerd) pulls the image:
// GET /v2/myapp/manifests/v1 → retrieve manifest
// GET /v2/myapp/blobs/<config-digest> → retrieve config
// GET /v2/myapp/blobs/<layer-digest> → retrieve each layer
//
// Each blob is verified against its digest.
// The layers are stored locally in the content store.
// ============================================
// PART 4: UNPACK (Image Spec → Runtime Spec)
// ============================================
// containerd unpacks the layers into a bundle:
// - apply each layer in order to a root filesystem directory
// - write the config.json from the image configuration
//
// The bundle is now:
// /run/containerd/io.containerd.runtime.v2.task/myapp/
// config.json
// rootfs/
// ============================================
// PART 5: RUN (Runtime Spec)
// ============================================
// containerd invokes runc with the bundle path:
// runc create myapp --bundle /run/.../myapp
// runc start myapp
//
// runc reads config.json:
// - creates namespaces
// - applies cgroup limits
// - mounts the rootfs
// - executes the process
//
// The container is running.
// ============================================
// PART 6: THE THREE SPECS IN ONE PIPELINE
// ============================================
// Image Spec: defines the artifact (manifest, config, layers)
// Distribution Spec: moves the artifact (pull/push API)
// Runtime Spec: executes the artifact (config.json, bundle, lifecycle)
//
// Each spec is independent, but they compose into a complete pipeline.
The six parts trace the full lifecycle: build produces an OCI Image, push and pull move it through a registry, unpack converts it to a Runtime Bundle, and run executes it. What the session demonstrates is that the three specifications are not competing standards—they are sequential stages in a single pipeline, each with a well-defined interface to the next.
Quick Reference
The Three Specifications
| Spec | Purpose | Key Artifact |
|---|---|---|
| Image Spec | How images are structured | Manifest, config, layers |
| Runtime Spec | How containers are executed | config.json, bundle |
| Distribution Spec | How images are moved | Registry HTTP API |
Image Spec Components
| Component | Purpose |
|---|---|
| Manifest | References config and layers by digest |
| Configuration | Runtime settings (entrypoint, env, args) |
| Layers | Filesystem changesets |
| Image Index | Multi-platform manifest list |
| Descriptor | Type, digest, and size of referenced content |
Runtime Spec Lifecycle
| State | Meaning |
|---|---|
creating | Runtime is setting up the environment |
created | Environment exists, user process not started |
running | User process is executing |
stopped | Process has exited |
Distribution Spec Endpoints
| Operation | Endpoint |
|---|---|
| Pull manifest | GET /v2/<name>/manifests/<ref> |
| Pull blob | GET /v2/<name>/blobs/<digest> |
| Push blob | POST /v2/<name>/blobs/uploads/ |
| Push manifest | PUT /v2/<name>/manifests/<ref> |
| Referrers | GET /v2/<name>/referrers/<digest> |
Version History
| Version | Date | Key Addition |
|---|---|---|
| Image Spec 1.0 | 2017 | Initial release |
| Runtime Spec 1.0 | 2017 | Initial release |
| Distribution Spec 1.0 | 2021 | Registry API standard |
| Image + Distribution 1.1 | Feb 2024 | subject, artifactType, referrers API |
Best Practices
✅ Do This:
// Build images that conform to the OCI Image Spec
// (Buildah, Docker, ko, and kaniko all produce OCI images)
// Use an OCI-conformant registry (Harbor, ECR, GCR, GHCR)
// that passes the Distribution Spec conformance suite
// Use runc or crun as the runtime, both implement the Runtime Spec
// Use digests, not tags, in production manifests
// Tags are mutable; digests are content-addressed
❌ Don’t Do This:
// Assume Docker-specific formats are the only option
// (Podman, containerd, and CRI-O all use OCI formats)
// Use a registry that does not pass conformance
// (pull/push may fail with OCI-compliant clients)
// Modify blobs after pushing
// (the digest would no longer match, and the pull would fail)
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Image pull fails on digest mismatch | Blob was modified after push | Re-push the image; do not modify blobs |
| Multi-arch image runs wrong architecture | Image index not used | Build and push an image index |
| Registry rejects OCI manifest | Registry implements only Docker V2 schema | Use an OCI-conformant registry |
| Runtime cannot find rootfs | Bundle not unpacked correctly | Verify layers applied in order |
| Referrers endpoint returns 404 | Registry does not support v1.1 | Upgrade registry or use tags |
Real-World Examples
1. OCI image layout on disk
oci-layout
index.json
blobs/sha256/<digest>
2. Image manifest structure
{
"schemaVersion": 2,
"config": { "digest": "sha256:abc...", "size": 1234 },
"layers": [{ "digest": "sha256:def...", "size": 5678 }]
}
3. Runtime bundle
bundle/
config.json
rootfs/
4. config.json process section
{
"process": {
"args": ["/app/server"],
"env": ["PATH=/usr/bin"],
"cwd": "/app"
}
}
5. Registry pull
GET /v2/myapp/manifests/latest
6. Registry push
PUT /v2/myapp/manifests/latest
7. Referrers query
GET /v2/myapp/referrers/sha256:abc...
8. subject field in a manifest
{
"subject": { "digest": "sha256:abc...", "size": 1234 }
}
9. OCI-conformant tools
Build: Buildah, Docker, ko, kaniko
Run: runc, crun, containerd, CRI-O
Registry: Harbor, ECR, GCR, GHCR
10. Conformance test
# Run the OCI Distribution Spec conformance suite
# against a registry to verify compliance
Visual
The OCI Pipeline
┌──────────────────────────────────────────────┐
│ IMAGE SPEC │
│ manifest + config + layers │
│ │ │
│ ▼ │
│ DISTRIBUTION SPEC │
│ push to registry / pull from registry │
│ │ │
│ ▼ │
│ RUNTIME SPEC │
│ unpack into bundle / run with config.json │
└──────────────────────────────────────────────┘
Image Spec Components
┌──────────────────────────────────────────────┐
│ MANIFEST │
│ ├── config (digest, size) │
│ ├── layers[0] (digest, size) │
│ ├── layers[1] (digest, size) │
│ └── subject (optional, v1.1) │
│ │
│ CONFIGURATION │
│ ├── entrypoint, cmd │
│ ├── env, workdir, user │
│ └── history │
│ │
│ LAYERS │
│ ├── base layer (tar) │
│ └── app layer (tar) │
└──────────────────────────────────────────────┘
Runtime Lifecycle
┌──────────────────────────────────────────────┐
│ create │
│ → environment created (namespaces, cgroups) │
│ → user process NOT started │
│ │ │
│ ▼ │
│ start │
│ → prestart hooks │
│ → user process runs │
│ → poststart hooks │
│ │ │
│ ▼ │
│ running │
│ │ │
│ ▼ │
│ stopped │
│ → process exits │
└──────────────────────────────────────────────┘
Distribution Spec API
┌──────────────────────────────────────────────┐
│ PULL │
│ GET /v2/<name>/manifests/<ref> │
│ GET /v2/<name>/blobs/<digest> │
│ │
│ PUSH │
│ POST /v2/<name>/blobs/uploads/ │
│ PUT /v2/<name>/manifests/<ref> │
│ │
│ REFERRERS (v1.1) │
│ GET /v2/<name>/referrers/<digest> │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| OCI founded | June 2015 |
| Image Spec | Manifest, config, layers |
| Runtime Spec | config.json, bundle, lifecycle |
| Distribution Spec | Registry HTTP API |
| Image Spec 1.0 | 2017 |
| Runtime Spec 1.0 | 2017 |
| Distribution Spec 1.0 | 2021 |
| Image + Distribution 1.1 | February 2024 |
| Multi-arch | Image index |
| Artifacts (v1.1) | subject, artifactType, referrers API |
Key takeaways:
- The three specifications form a pipeline, not three independent standards. The Image Spec defines the artifact, the Distribution Spec moves it, and the Runtime Spec executes it. Each has a well-defined interface to the next .
- The Image Spec is content-addressable. The manifest references the config and layers by digest. The registry stores blobs by digest, and the pull verifies each digest. This is what makes image distribution verifiable and deduplicatable .
- The Runtime Spec is about configuration, not about the image. The runtime receives a bundle: a
config.jsonand a root filesystem. It does not know or care how the bundle was produced. This separation is what allows any OCI runtime to run any OCI image . - The Distribution Spec standardizes the registry API. It is based on the Docker Registry HTTP API V2, which Docker contributed to OCI in 2018. The pull path is mandatory; push and content discovery are recommended .
- OCI 1.1 added artifacts and referrers. The
subjectfield allows manifests to reference other manifests, and the referrers API allows clients to discover signatures and attestations associated with an image. This is what made OCI registries suitable for storing non-container artifacts . - Conformance testing is what makes the specifications enforceable. A registry that claims OCI conformance can be verified against the conformance suite. Without the suite, the specification would be a suggestion rather than a standard .
Remember: The OCI specifications are the reason container images are portable. Before OCI, an image built by Docker ran on Docker, and an alternative runtime had to reverse-engineer the format. After OCI, an image built by Buildah can be pushed to Harbor, pulled by containerd, and run by runc, because all four tools implement the same three specifications. The Image Spec defines what an image is, the Distribution Spec defines how it moves, and the Runtime Spec defines how it runs. Each specification is independent, and each can evolve without breaking the others, as long as the interfaces between them remain stable. The 1.1 releases in 2024 extended the model from container images to arbitrary artifacts, which is why OCI registries now store Helm charts, WebAssembly modules, and supply chain attestations alongside the images they describe.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!