OCI 101: The Common Language of the Container World

For most engineers, Docker is their first introduction to containers. As a result, “Docker image” has become nearly synonymous with container images in everyday conversations, internal documentation, and even technical interviews. Yet standards documents use another term as well: OCI image. The two are not exactly the same format, but their core structures are highly compatible, and Docker can build and run OCI images.

The shared history of these two terms dates back to 2017. When the Open Container Initiative (OCI) released Image Spec v1.0, Docker engineer Stephen Day noted:

“It is exciting to see Docker’s image format incorporated into the OCI image specification.”

With this transition, Docker donated its own image format to the community, establishing it as an open standard shared across the industry.

It is much like how “Wite-Out” began as a specific trademark and later became a generic term for correction fluid. The difference is that the container world habitually uses the brand name, while the actual name of the underlying standard is mentioned far less often. Once you understand the compatibility between the two formats, you will no longer assume that an image built with Docker can only run on Docker.


Why Containers Need an Open Standard

The importance of standards becomes clear if we compare modern containers to intermodal shipping containers. Their dimensions, steel corner fittings, and locking mechanisms all follow strict international standards. As a result, cranes can handle them precisely at any port, and cargo ships operated by any shipping company can stack them securely.

Once the cargo reaches shore, any standards-compliant truck can transport it directly. Ports, shipping fleets, and overland carriers do not need to negotiate with one another in advance, nor do they need to know whether a container holds textiles or precision chips.

Software containers follow the same logic. Without a shared standard, every tool would develop its own packaging format. A service created with tool A could not start in tool B, leaving developers trapped in severe vendor lock-in. When OCI v1.0 was released, SUSE’s Aleksa Sarai and Red Hat’s Mrunal Patel each described the standard’s core value:

“Standards let users freely combine different components without worrying about vendor lock-in.” — Aleksa Sarai, SUSE

“Standardization ensures that applications inside containers remain portable across different runtimes.” — Mrunal Patel, Red Hat

A broadly accepted standard provides engineering teams with concrete practical benefits:

A standard does not directly make any individual tool run faster. What it does is enable deep specialization across the ecosystem: build, execution, and storage can each focus on their own responsibilities, then connect through consistent formats and interfaces.


The Birth and Governance of OCI

In June 2015, Docker, CoreOS, and major cloud vendors co-founded OCI with support from the Linux Foundation to develop open standards for container formats and runtimes. Docker contributed its container format and low-level runtime tool, runc, as the core foundation of this open architecture.

At the organizational level, OCI is a neutral, openly governed project under the Linux Foundation. It does not belong to any single cloud giant or commercial company, and every specification change must undergo open discussion and review by multiple parties.

Standards usually evolve more slowly than commercial products innovate. Two years passed between the organization’s founding and the first specification release, while the Distribution Spec—which governs transport and registry APIs—took nearly six years to be finalized.

That deliberate pace, however, helps ensure architectural stability. Designs incorporated into OCI specifications have already undergone rigorous validation in production environments around the world and offer long-term backward compatibility.


Three Core Specifications, Three Distinct Roles

OCI currently consists primarily of three interconnected core specifications:

SpecificationScopeCore responsibility and analogy
Image SpecImage storage format and structureDefines the container’s dimensions and cargo manifest
Runtime SpecContainer lifecycle and execution environmentDefines how a container is unpacked, powered up, and started after delivery
Distribution SpecImage transfer and registry APIsDefines port loading, cargo registration, and collection procedures

The Image Spec defines how a container image is assembled, including its manifest, optional Image Index for multiple architectures, layered filesystems, and runtime config. Its purpose is to ensure that images produced by different tools can be correctly interpreted and unpacked by any compatible tool.

The Runtime Spec defines how a container is actually created and run. It covers the config.json format, isolation through Linux namespaces and cgroups, and lifecycle operations such as create, start, kill, and delete, ensuring that the same container behaves consistently across different runtimes.

The Distribution Spec, based on Docker Registry HTTP API V2, defines the standard RESTful API that an image registry must implement. For example, GET /v2/<name>/manifests/<tag|digest> retrieves a manifest.

These three specifications intentionally leave one subject undefined: they place no restrictions on how images are built. A Dockerfile is not part of the OCI standard. This is precisely what allowed the community to create a diverse range of build engines, such as BuildKit and Buildah. As long as the final output conforms to the Image Spec, it integrates seamlessly with the wider container ecosystem.


Image Internals and the Layering Model

Many beginners intuitively picture a container image as one enormous binary file. In reality, it consists of a JSON manifest and several compressed filesystem layers.

A simplified OCI manifest looks like this:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:b5b2b2c5...",
    "size": 7023
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:9834876d...",
      "size": 32654
    }
  ]
}

You only need to understand four key fields to grasp the structure of an image:

  1. mediaType: Declares the file’s MIME type so tools can determine whether it is an OCI manifest, a config, or another object.
  2. config: Points to a JSON object containing runtime details, including environment variables, the default working directory, startup commands such as Entrypoint and Cmd, and other metadata.
  3. layers: An ordered set of compressed archives in tar+gzip format. Unpacking and overlaying them in sequence produces the complete root filesystem visible to the container.
  4. digest: A cryptographic hash of the content, usually SHA-256, that serves as the file’s globally unique fingerprint.

How Manifests, Blobs, and Tags Form an Image

A manifest is like a bill of lading. It does not contain the files themselves; instead, it uses digests to point to the actual data objects. After a client retrieves the manifest, it uses those digests to download the config and corresponding layers individually.

Conversely, pushing an image does not mean uploading one enormous file. When you run docker push, Docker first checks which blobs the registry already has by digest. It uploads only the missing layers and config, then uploads the manifest that describes the relationships among those objects.

A registry therefore does not store an image as a single packaged file. It stores the manifest, config, and layers separately. Together, these objects form the logical collection known as an image. A tag such as myapp:v1 ultimately points to the manifest.

The same layer blob can also be referenced by multiple manifests. As long as the digest is identical, neither the registry nor the client needs to store or transfer that layer again. Conversely, even when two layers appear to contain the same files, any difference in content or metadata produces a different digest, so they are treated as distinct layers.

 +------------------------------------------------------+
 | OCI Manifest                                         |
 |                                                      |
 | config -> sha256:b5b2b2c5...                         |
 | layers -> [ sha256:9834876d..., sha256:4a3f12c8... ] |
 +------------------------------------------------------+
             |                             |
             v                             v
 +------------------------+    +------------------------+
 | Image Config           |    | Image Layers (tar.gz)  |
 |                        |    |                        |
 | - ENV                  |    | - Layer 1 (OS Base)    |
 | - WORKDIR              |    | - Layer 2 (Packages)   |
 | - CMD                  |    |                        |
 +------------------------+    +------------------------+

How Layer Sharing and Caching Work

Container layering relies on two core technologies: content-addressable storage and a union filesystem, most commonly OverlayFS on Linux. Each layer records only the additions, changes, or deletions relative to the layer beneath it.

Every layer in an image is read-only. When a container starts, the runtime adds a writable layer above the image layers. Files created or modified during execution are written only to this layer and do not alter the original image. This writable layer is not part of the OCI image and is not uploaded by docker push.

Consider a typical Python application. Its image might contain five layers: a base operating system, the Python runtime, a package manifest, installed dependencies, and application source code. When another Python service is deployed on the same host, the digests of the first four layers may be identical. The host can reuse the existing files directly and download and store only the fifth layer containing the source code.

This mechanism explains several common experiences in everyday development:


Tag vs. Digest: Why Production Should Never Use the latest Tag

There are two main ways to identify an image when pulling it: by tag or by digest. In the Distribution Spec, they have fundamentally different semantics:

Put simply, a tag is a sticky note that can be moved at any time, while a digest is an unforgeable fingerprint. latest is merely a conventional default tag name. It does not mean “the newest and most stable version.” Whenever someone pushes another image under that tag, latest moves to point to it.

The official Kubernetes documentation strongly recommends avoiding :latest in production because it makes version tracking difficult and precise rollbacks unreliable. Teams should use a meaningful version tag such as v1.42.0, or pin deployments directly to a digest:

# Recommended: use a specific version tag
image: myapp:v1.42.0

# Best practice: pin the digest to guarantee identical content
image: myapp@sha256:45b23dee08af8d1a3c7b8e1f0e2d4c6b8a0...

WARNING

Different nodes may run entirely different versions: suppose multiple nodes pull myapp:latest at different times. If a new image is pushed to the registry in between, those nodes will run completely different versions of the code even though their deployment files look identical. Using a digest eliminates this uncertainty entirely.

In practice, engineers often use version tags when communicating with one another because they are easier to recognize, while automated deployment scripts use digests to keep production stable and reproducible.


Multi-Architecture Images: Seamless Compatibility Across CPU Platforms

Many people notice that the official alpine image runs successfully on x86 laptops, ARM-based Apple Silicon Macs, and Raspberry Pis alike. This is not because the image dynamically translates machine code. It is because the tag points to an Image Index, also known as a Manifest List.

An Image Index is a “list of lists.” It contains multiple platform targets, such as linux/amd64 and linux/arm64, along with the corresponding manifest digest for each one.

When a client pulls an image, the registry first returns this index. The container tool then automatically selects and downloads the manifest compatible with the host’s CPU architecture. The tools coordinate the entire process behind the scenes; you only need to run the same command.

 +------------------------------------------------------+
 | Image Index (Manifest List)                          |
 |                                                      |
 |   Platform: linux/amd64  -->  Manifest (Digest A)    |
 |   Platform: linux/arm64  -->  Manifest (Digest B)    |
 +------------------------------------------------------+
              |                             |
              v                             v
 +------------------------+    +------------------------+
 | Manifest (amd64)       |    | Manifest (arm64)       |
 |                        |    |                        |
 |   Config: amd64 bin    |    |   Config: arm64 bin    |
 |   Layers: amd64 rootfs |    |   Layers: arm64 rootfs |
 +------------------------+    +------------------------+

By contrast, if an image is compiled for only one architecture during packaging, starting it on an incompatible platform causes the operating system to report an exec format error.


The Runtime Relay: From docker run to a Running Container

How High-Level and Low-Level Runtimes Divide the Work

“High-level” and “low-level” are not formal classifications in the OCI specifications. They are common architectural terms in the container ecosystem that describe levels of abstraction—not performance or quality. A high-level runtime decides what to run and prepares the required resources. A low-level runtime then creates the isolated environment and starts the actual container process.

 Docker / Kubernetes
         |
         v
 containerd / CRI-O
 (High-level runtime: manages images and
  container lifecycles)
         |
         v
 runc / crun / runsc
 (Low-level runtime: creates isolation and
  starts processes)
         |
         v
 Linux kernel and container processes

NOTE

Further reading: Why Kubernetes Removed Docker

The Relay Across All Three Specifications

When we type docker run alpine into a terminal, the three OCI specifications connect behind the scenes in sequence, with high-level and low-level runtimes handing the work from one stage to the next:

 1. Distribution Spec (download)
    Registry ---> [Image Index / Manifest]
       ---> [Config + Layers]

 2. Image Spec (interpretation)
    Unpack each Layer
       ---> Assemble the complete Root Filesystem

 3. Format conversion (Bundle generation)
    Image Config + Rootfs
       ---> Filesystem Bundle (config.json + rootfs/)

 4. Runtime Spec (execution)
    runc / crun reads the bundle
       ---> Creates Namespaces / Cgroups
       ---> Starts the process

The relay proceeds as follows:

  1. Distribution Spec takes the baton (download): A high-level runtime, such as containerd or CRI-O, sends a request to the registry. If the response is an Image Index, it selects the manifest matching the host CPU, then downloads the config and any layers that are not already cached.
  2. Image Spec takes the baton (interpretation): Working from the manifest, the high-level runtime unpacks each layer in order and uses OverlayFS to combine them into the container’s root filesystem.
  3. Generate the Filesystem Bundle: Following the Image Spec, the high-level runtime converts the image into the Filesystem Bundle directory required by the Runtime Spec. This directory contains the unpacked rootfs/ folder and a config.json translated from the image config, including the command array, environment variables, and working directory.
  4. Runtime Spec takes the baton (execution): A low-level runtime, such as runc, reads the bundle directory, uses Linux kernel system calls to create namespaces, cgroups, and a Seccomp security sandbox, and ultimately starts the container’s main process.

This design reveals a key idea: the instructions in a Dockerfile are not proprietary magic tied to a particular vendor. They are packaged into the OCI config in a standardized form, then delivered to the low-level runtime through shared translation rules.


A Layered Container Ecosystem with Swappable Components

Once the OCI standards were established, the container technology stack became clearly divided into distinct layers, each of which can be swapped out independently:

 +--------------------------------------------------------+
 | User Tools                                             |
 | Docker CLI, Podman, kubectl                            |
 +--------------------------------------------------------+
                |
                v
 +--------------------------------------------------------+
 | High-Level Runtimes                                    |
 | containerd, CRI-O                                      |
 +--------------------------------------------------------+
              |                              |
              v                              v
 +-------------------------+    +-------------------------+
 | Low-Level OCI Runtime   |    | OCI Image Registry      |
 |                         |    |                         |
 | runc (standard default) |    | Docker Hub, GHCR,       |
 | crun (fast C impl.)     |    | Harbor, ECR             |
 | gVisor runsc (strong    |    |                         |
 | isolation)              |    |                         |
 +-------------------------+    +-------------------------+

Here is how each layer looks in practice—and what you can swap it for:

In security-sensitive multi-tenant environments, operations teams can replace the default runc with gVisor’s runsc to gain stronger kernel isolation without changing the Kubernetes deployment configuration or application images at all.


Beyond Image Registries: OCI as a General-Purpose Artifact Store

As the Distribution Spec matured, the community realized that its digest-addressed content storage model was highly extensible and well suited to distributing data beyond container images.

Beginning with v3.8.0, the well-known Kubernetes package manager Helm added native support for packaging Helm Charts in OCI format and pushing them to a standard registry:

# Package and push a Chart to an OCI Registry
helm push mychart-0.1.0.tgz oci://localhost:5000/helm-charts

# Install a Chart directly from an OCI Registry
helm install myrelease oci://localhost:5000/helm-charts/mychart --version 0.1.0

The OCI v1.1 specifications, released in 2024, expanded support for the Referrers API and Artifacts. This allows artifacts—digital signatures (such as Cosign verification data), SBOMs (software bills of materials), and even AI model files—to be associated with a specific image digest as metadata and stored alongside the image in the registry.

OCI has consistently emphasized backward compatibility as its specifications evolve. Registries that have not been upgraded simply ignore unrecognized new fields, allowing the broader cloud-native ecosystem to upgrade gradually. Enterprises can therefore maintain a single registry to centralize access control and audit trails for container images, Helm packages, and security compliance inventories.


Hands-On Verification: Inspecting an OCI Manifest Yourself

The most direct way to see OCI specifications at work is to use the Docker CLI to inspect raw manifest data from a remote registry:

# Inspect the platform list (Image Index) for the alpine image in the Registry
docker buildx imagetools inspect alpine

# Print the raw underlying JSON data
docker buildx imagetools inspect --raw alpine

Running the raw inspection command returns a JSON fragment that directly confirms the structure introduced earlier:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:c0537ff6101e8b40b64431842d9b92161d5...",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:39f60e94200dbf3e58dd2f2930263f1ecf3...",
      "platform": {
        "architecture": "arm64",
        "os": "linux"
      }
    }
  ]
}

The top-level mediaType clearly identifies the object as an Image Index, while each entry in the manifests array pairs a platform with the digest of that platform’s manifest.


Conclusion and Further Learning

Understanding OCI is about more than clarifying where a few terms came from. Once you see the division of responsibilities—Image defines the structure, Distribution handles transfer, and Runtime drives execution—you can more accurately identify the layer where a problem occurs, whether it involves cross-platform builds, swapping secure runtimes, or pinning deployment versions.

To explore the implementation details of each specification further, consult the following official documentation and specification sources: