What Are OCI Specifications?

What Are OCI Specifications?

The three quiet rulebooks that let Docker, Kubernetes, Podman, AWS, and Google all agree on what a "container" actually is — and why that agreement is the reason your container never cares which company built the tool that runs it.

Picture a shipping port where every crane, every ship, and every truck was built by a different company, in a different country, following its own private idea of what a “container” should look like. Nothing would fit together. Cranes would drop boxes that trucks couldn’t carry. Ships would refuse boxes shaped for a different dock. Global trade, as we know it, would be impossible. The reason it works today is a boring but essential agreement: a steel shipping container must be exactly 8 feet wide, a specific height, with locking mechanisms in the same place, no matter who manufactures it. That single agreement — not the container itself — is what unlocked global commerce. The Open Container Initiative, or OCI, exists to do the exact same thing for software containers, and this article walks through what its specifications actually say, how they work internally, and why almost every container tool you have ever used quietly obeys them.

WWhy Container Standards Had to Exist

Before there was one shared definition of a container, there were several competing ones — and that was a real problem for the entire industry.

In 2013, Docker popularized a way of packaging software so it could run identically on a developer’s laptop, a testing server, and a production data center. The idea itself — isolating a process using Linux kernel features like namespaces and cgroups — had existed for years inside technologies like LXC (Linux Containers) and Solaris Zones. What Docker added was a friendly image format, a simple command-line tool, and a public registry (Docker Hub) where people could share these packaged applications. It exploded in popularity almost overnight.

The trouble started once other companies wanted a piece of the same idea. CoreOS, a company that competed directly with Docker, released its own container engine called rkt (pronounced “rocket”) in 2014, along with a competing image format called appc. Suddenly there were two incompatible ways to package the same kind of software. If this had continued, the container ecosystem would have fractured the way home video formats once did with VHS and Betamax, or the way early smartphone charging cables once did before USB-C became mandatory. Every cloud provider, every orchestration tool, and every company adopting containers would have had to pick a side, and applications built for one side would not run on the other.

Everyday Analogy

Think about electrical wall sockets. In the United States, a plug has two flat prongs. In the United Kingdom, it has three rectangular prongs. In much of Europe, it has two round prongs. None of these are “wrong,” but a device built for one socket is useless in a country using another, unless you carry an adapter. Before OCI, containers were heading toward a world where every cloud provider had a different “socket,” and every application needed its own adapter just to run somewhere else.

In June 2015, Docker, CoreOS, and a group of other companies — including Google, Red Hat, Microsoft, IBM, Amazon, VMware, and HP — came together under the governance of the Linux Foundation to form the Open Container Initiative. Docker donated its container image format and runtime implementation as the starting point. In exchange, every founding member agreed to stop building competing, incompatible formats, and instead work together on a single, vendor-neutral specification that anyone could implement.

i
Key Distinction

OCI does not build software. It writes specifications — precise, plain-text documents describing exactly what bytes, fields, and behaviors a compliant container tool must produce. Docker, containerd, Podman, CRI-O, and dozens of other tools are all separate software projects that choose to follow these OCI rules.

This distinction between a specification and an implementation is the single most important idea in this entire topic, and it will come up again and again as we go deeper.

CCore Concepts You Need Before Going Further

Before looking inside the specifications themselves, it helps to pin down a handful of terms that get used loosely in everyday conversation but have very precise meanings inside OCI documents.

Term

Container Image

A packaged, read-only snapshot of a filesystem plus metadata describing how to run it. An image is not running — it is a file, like a zipped folder, sitting in storage or a registry.

Term

Container

A running instance created from an image, with an additional writable layer on top and an isolated process environment. The same image can spawn many separate containers.

Term

Layer

A single filesystem change-set, usually a compressed tarball. Images are built by stacking layers, similar to how transparent sheets stack to form one picture.

Term

Manifest

A JSON document listing an image’s layers, its configuration, and metadata like architecture and OS — the “packing list” for the image.

Term

Registry

A server that stores and serves images, such as Docker Hub, Amazon ECR, or Google Artifact Registry, similar to how an app store stores and serves mobile apps.

Term

Runtime

The low-level program that actually creates and starts a container process on the operating system, such as runc or crun.

With this vocabulary in place, the three OCI specifications map cleanly onto three separate stages of a container’s life: how it is packaged (the Image Specification), how it is run (the Runtime Specification), and how it is shared (the Distribution Specification). Everything in the rest of this guide is really an elaboration of those three stages.

AArchitecture and Components

OCI is not one document. It is a family of three independent specifications, each maintained by its own working group, each with its own version number, and each solving one narrow problem extremely well.
graph TB
    A["Developer builds application"] --> B["Image Specification\n(defines packaging format)"]
    B --> C["Built OCI Image\n(manifest + config + layers)"]
    C --> D["Distribution Specification\n(defines push/pull API)"]
    D --> E["Container Registry\n(Docker Hub, ECR, GAR, Harbor)"]
    E --> D
    D --> F["Container Engine\n(Docker, Podman, containerd, CRI-O)"]
    F --> G["Runtime Specification\n(defines execution format)"]
    G --> H["Low-Level Runtime\n(runc, crun, gVisor, Kata)"]
    H --> I["Running Container Process\nisolated via namespaces + cgroups"]
    
Fig. 1 — How the three OCI specifications connect a built image to a running, isolated process

1. The Image Specification (image-spec)

This specification defines the exact on-disk and in-registry format of a container image: how layers are structured, how they are compressed, how the manifest lists them, and how a config file describes runtime defaults like environment variables, the entrypoint command, and exposed ports. Any tool that builds images — Docker’s build engine, Google’s Jib, Red Hat’s Buildah, or the increasingly popular BuildKit — must output something matching this format for the image to be considered portable.

2. The Runtime Specification (runtime-spec)

This specification defines what a “bundle” looks like on disk right before a container starts, and precisely how a compliant runtime must behave when asked to create, start, stop, and delete that container. It is deliberately low-level and operating-system-aware, because this is the layer where actual process isolation happens using Linux namespaces, cgroups, and (on other platforms) their equivalents.

3. The Distribution Specification (distribution-spec)

This specification defines the HTTP API that a registry must expose so that any client can push and pull images using the same set of URL patterns, headers, and response codes — regardless of whether that registry is Docker Hub, Amazon Elastic Container Registry, Google Artifact Registry, or a private on-premises registry like Harbor or JFrog Artifactory.

image-spec
defines the box
runtime-spec
defines the engine room
distribution-spec
defines the mail service

How the Layers Fit Together

A build tool reads instructions (like a Dockerfile) and produces an OCI-compliant image. A registry stores that image and serves it according to the distribution API. A container engine downloads the image, unpacks it into a bundle on disk, and hands that bundle to a low-level runtime, which reads it according to the runtime specification and actually starts the isolated process.

IInternal Working of Each Specification

Inside the Image Specification

An OCI image is not a single file — it is a small tree of JSON documents and compressed archives, tied together by cryptographic content hashes. At the top sits the image index (optional, used for multi-architecture images), which points to one or more image manifests. Each manifest lists exactly two important things: a reference to a config blob and an ordered list of references to layer blobs.

The config blob is a JSON file describing the image’s “personality” — the default command to run, environment variables, the working directory, exposed ports, and the target OS and CPU architecture. The layer blobs are gzip-compressed tarballs, each representing a filesystem diff. When a container engine assembles the final filesystem, it stacks these layers in order using a union filesystem (such as overlayfs on Linux), so that later layers can add, modify, or delete files from earlier ones without physically copying the whole filesystem each time.

Everyday Analogy

Layers work like a stack of transparency sheets on an old overhead projector. The first sheet might draw a base map. The next sheet adds roads. The next adds building labels. Individually each sheet is small and simple, and several different final pictures can reuse the same base map sheet without redrawing it. Container layers are cached and reused the same way — if ten images all start from the same Ubuntu base layer, that layer is downloaded and stored only once.

Crucially, every blob — the config, every layer, every manifest — is addressed by its SHA-256 digest, a fixed-length cryptographic fingerprint of its exact contents. This is called content-addressable storage. If even a single byte of a layer changes, its digest changes completely, which means images are inherently tamper-evident: you cannot quietly swap one layer for another without the manifest’s reference no longer matching.

Inside the Runtime Specification

Before a container can start, a container engine must produce what the runtime specification calls a filesystem bundle: a directory containing an unpacked root filesystem (the merged result of all the image layers) and a single JSON file named config.json. This config file is far more detailed than the image’s config blob — it specifies precise Linux namespace settings, cgroup resource limits (CPU shares, memory ceilings), mount points, user and group IDs, Linux capabilities to grant or drop, and seccomp system-call filtering rules.

A compliant low-level runtime — the most widely used implementation is runc, itself extracted from Docker’s original codebase and donated to OCI — reads this bundle and performs a strict lifecycle: create (set up namespaces and cgroups but do not start the user process yet), start (execute the actual entrypoint process), state (report status), and delete (tear everything down and release resources). This lifecycle is deliberately split into discrete steps so that higher-level tools like Docker or Kubernetes’ kubelet can insert their own logic — like attaching networking — between “create” and “start.”

?
What an interviewer may ask

“Why does the runtime lifecycle separate ‘create’ from ‘start’ instead of just launching the process immediately?” A strong answer: splitting the steps gives orchestration tools a window to configure networking, mount volumes, or register the container with a monitoring system after the isolated environment exists but before the application code begins executing — avoiding race conditions where the app starts before its network is ready.

Inside the Distribution Specification

The distribution specification defines a REST API built around a small set of predictable URL patterns, all rooted at /v2/. For example, pulling a manifest uses a GET request to a path shaped like /v2/<name>/manifests/<reference>, where the reference can be either a human-friendly tag (like “latest” or “3.2.1”) or a content digest. Pushing a large layer is done through a resumable, chunked upload process using a sequence of POST and PATCH requests, which matters enormously for images that can be hundreds of megabytes or several gigabytes in size — a dropped connection halfway through does not mean starting over from zero.

The specification also standardizes authentication flow (typically token-based, following a pattern popularized by Docker’s registry v2 API), content negotiation using specific media types so a client can tell a registry exactly which manifest schema version it understands, and a conformance test suite that any registry vendor can run against their own server to prove compliance.

DData Flow and Lifecycle, End to End

Tracing one image from a developer’s keyboard to a running production process shows all three specifications working together in sequence.
1

Build

A build tool (Docker Build, Buildah, or BuildKit) executes build instructions layer by layer, producing gzip-compressed tarballs and a config blob, then assembles them into an OCI-compliant manifest. This is where the Image Specification applies.

2

Push

The client authenticates against the target registry, checks which layer digests the registry already has (to avoid re-uploading shared base layers), and uploads only the missing blobs plus the new manifest, using the Distribution Specification’s chunked-upload API.

3

Pull

On another machine — a CI server, a Kubernetes node, a teammate’s laptop — the container engine requests the manifest by tag or digest, resolves it to the list of layer digests, and downloads any layers not already cached locally, again via the Distribution Specification.

4

Unpack

The engine extracts and stacks the downloaded layers into a merged root filesystem using a union filesystem driver, and writes a config.json describing namespaces, cgroups, and mounts — assembling the bundle the Runtime Specification requires.

5

Run

The engine invokes a low-level runtime (typically runc) with that bundle. The runtime creates isolated namespaces, applies cgroup limits, and executes the entrypoint process — the container is now genuinely running.

6

Teardown

When the process exits or is stopped, the runtime deletes the namespaces and cgroup, and the engine discards the container’s writable layer (unless the caller explicitly asked to keep it, e.g. for debugging).

What makes this flow remarkable is that steps 1 and 2 can be performed by an entirely different vendor’s tool than steps 3 through 6, on a completely different cloud provider, months apart, and it still works — because every step only ever depended on the specification, never on a specific vendor’s private format.

AAdvantages, Disadvantages and Trade-offs

Advantages

  • True portability: an image built once can run on any OCI-compliant engine, on any cloud, without modification
  • Vendor competition without ecosystem fragmentation — companies compete on speed, security, and features, not on incompatible formats
  • Massive layer reuse across images, saving storage and bandwidth industry-wide
  • A stable foundation that lets an entire ecosystem (Kubernetes, service meshes, CI/CD tools) build on top with confidence
  • Content-addressable storage gives strong tamper-evidence and reproducibility guarantees for free

Disadvantages / Trade-offs

  • Specifications move conservatively and slowly by design, so genuinely new ideas take years to become standard (image signing took roughly this long)
  • Being a “lowest common denominator” standard means some vendor-specific optimizations can’t be expressed portably
  • Compliance does not equal quality — a runtime can pass conformance tests and still be slower or less secure than a competitor
  • The specifications describe format and behavior, not security posture, so misconfigured OCI-compliant containers are just as insecure as any other misconfigured software
“OCI did not make containers secure or fast. It made them interchangeable — and interchangeability is what let the rest of the ecosystem compete on security and speed.”

SSecurity

The specifications themselves are security-neutral, but they are the foundation on which the entire container supply-chain security ecosystem is built.

Because an OCI image is fully described by content-addressable digests, it becomes possible to build a chain of cryptographic trust on top of it. Projects like Notary and its successor Sigstore/cosign let a publisher digitally sign an image’s manifest digest. Anyone pulling that image later can verify the signature matches, proving the image has not been altered since the publisher signed it and genuinely came from that publisher — the same principle behind signed software updates on a phone.

The image specification’s structured, machine-readable layer list also enabled the rise of automated vulnerability scanning. Tools like Trivy, Grype, and cloud-native scanners built into Amazon ECR or Google Artifact Registry can walk every layer of a manifest, extract the package inventory, and cross-reference it against known vulnerability databases — something that would be far harder if every vendor packaged images differently.

Security Principle · SBOM Widely Adopted
Context

Organizations increasingly need to prove exactly what software components exist inside a running container, both for their own risk management and for regulatory compliance.

Approach

A Software Bill of Materials (SBOM) is generated from an image’s layers and attached to the image as a separate, OCI-compliant artifact, referencing the original image’s digest — leaning entirely on the content-addressable design the Image Specification already provides.

Consequence

Because the attachment mechanism is itself standardized (through the OCI “referrers” concept), any compliant registry can store and serve SBOMs and signatures without needing custom, vendor-specific extensions.

On the runtime side, security responsibility shifts to correctly configuring the fields inside config.json: dropping unnecessary Linux capabilities, enabling seccomp filters to block dangerous system calls, running as a non-root user wherever possible, and using read-only root filesystems. None of this is automatic — the runtime specification defines the levers, but a poorly configured bundle is still an insecure container, regardless of how compliant the underlying format is.

MMonitoring, Logging and Metrics

Although the OCI specifications do not define a logging or metrics format directly, the runtime lifecycle they standardize (create, start, state, delete) gives monitoring tools a predictable, universal set of hooks. A monitoring agent — such as cAdvisor, which Kubernetes uses internally — can query the “state” operation on any OCI-compliant runtime and receive a consistent JSON structure describing whether the container is running, its process ID, and its resource usage, regardless of whether the underlying runtime is runc, crun, or gVisor.

Resource metrics themselves — CPU throttling, memory pressure, I/O bandwidth — come from the Linux cgroups that the runtime specification instructs the runtime to configure. Because every OCI-compliant runtime must set up cgroups in a standard way, tools like Prometheus’s node exporter or Datadog’s agent can read the same cgroup filesystem paths across completely different container platforms without platform-specific code.

cgroups
source of CPU / memory metrics
state op
standardized health check hook
stdout/stderr
standard log capture stream

Logging follows a similarly consistent pattern: an OCI-compliant runtime exposes a container’s standard output and standard error streams in a predictable way, which is why the same logging pipeline — Fluentd, Fluent Bit, or a cloud provider’s native log collector — can be pointed at containers from wildly different tooling stacks and just work.

DDesign Patterns and Anti-patterns

Pattern: Multi-Architecture Images via the Image Index

Rather than publishing separate images for different CPU architectures (x86-64, ARM64) under different names, publishers use the image specification’s optional image index — a manifest of manifests — so a single tag like myapp:1.0 automatically resolves to the correct architecture-specific manifest for whichever machine is pulling it. This is precisely how Docker Desktop on an Apple Silicon Mac transparently pulls ARM64 images while a cloud server pulls x86-64 images of the exact same published tag.

Pattern: Thin, Cached Base Layers

Structuring a Dockerfile (or equivalent) so that rarely-changing layers (installing an operating system’s packages) come before frequently-changing layers (copying application source code) lets the content-addressable layer cache do its job — unchanged layers are never rebuilt or re-uploaded, dramatically speeding up both builds and deployments across an organization.

Anti-pattern: Mutable Tags in Production

Deploying against a tag like latest, which a publisher can silently repoint to a completely different manifest digest at any time, undermines the very tamper-evidence the specification was designed to provide. The corrective pattern is pinning production deployments to an exact content digest, which cannot be silently changed without the reference itself changing.

Anti-pattern: Treating Runtime Config as an Afterthought

Because the runtime specification’s config.json defaults are permissive for compatibility reasons, teams that never touch capability-dropping, seccomp, or user settings end up running containers with far more privilege than their application needs — a common, avoidable source of security incidents.

BBest Practices and Common Mistakes

Best Practice

Pin base images by digest

Reference a base image by its SHA-256 digest, not just a tag, in build pipelines that need reproducible, auditable results.

Best Practice

Sign what you publish

Use cosign or Notary to sign manifest digests, so consumers can verify authenticity before pulling into production.

Best Practice

Minimize the runtime bundle

Drop unused Linux capabilities and enable seccomp filtering explicitly in the runtime config rather than relying on defaults.

Best Practice

Keep layers logically grouped

Order build steps from least to most frequently changing to maximize layer cache hits across builds.

Common Mistake

Assuming “container” means “OCI-compliant”

Some older or niche tools predate or diverge from OCI; always verify a tool’s conformance rather than assuming it.

Common Mistake

Confusing image size with layer count

A single enormous layer can be worse for caching than several smaller, well-ordered ones — total byte size matters less than reuse patterns.

RReal-World and Industry Examples

Kubernetes and the Container Runtime Interface

Kubernetes does not talk to container engines directly. Instead, it defines its own interface (CRI) that any engine can implement, and every major implementation — containerd (used by Docker and standalone) and CRI-O (built specifically for Kubernetes by Red Hat and others) — ultimately delegates the actual process-starting work to an OCI-compliant low-level runtime such as runc. This is precisely why a cluster can mix worker nodes running different container engines without application manifests ever needing to change.

Amazon ECR and Google Artifact Registry

Both of these major cloud registries implement the OCI Distribution Specification, which is why the exact same docker push and docker pull commands work against Amazon’s, Google’s, or Docker’s own registry with nothing more than a different URL — none of these providers needed to convince Docker to add custom, proprietary support for their service.

Red Hat’s Podman and Buildah

Red Hat built an entirely separate set of tools — Podman for running containers and Buildah for building them — from a different codebase than Docker’s, with a different security model (rootless by default). Because both output and consume standard OCI images, applications packaged with one toolchain move freely to environments using the other.

Sandboxed Runtimes: gVisor and Kata Containers

Google’s gVisor and the Kata Containers project (backed by Intel, Huawei, and others) both implement the OCI Runtime Specification’s interface while using radically different isolation techniques internally — gVisor intercepts system calls in user space, Kata runs each container inside a lightweight virtual machine. Because both still speak the same runtime interface, they can be swapped in as a drop-in replacement for runc wherever an organization needs stronger isolation for untrusted workloads.

FFrequently Asked Questions

Q1Is Docker the same thing as OCI?
No. Docker is a company and a set of tools. OCI is an independent, vendor-neutral standards body under the Linux Foundation. Docker donated its original image format and runtime code as the starting point for the standard, and Docker’s tools remain OCI-compliant, but they are not the same thing, and Docker does not control OCI.
Q2Does every container tool have to be OCI-compliant?
No, compliance is voluntary. However, because the entire modern ecosystem — Kubernetes, every major cloud registry, and most CI/CD platforms — assumes OCI compliance, a non-compliant tool would struggle to interoperate with almost anything else, so in practice nearly all serious container tooling complies.
Q3What is the difference between the Docker image format and the OCI image format?
Historically Docker had its own format (Docker Image Manifest V2, Schema 2), which became the direct basis for the OCI Image Specification. The two formats are extremely close and largely interoperable today, with the OCI format standardizing and formalizing what Docker had already built.
Q4What is runc, and do I need to know about it?
runc is the most widely used reference implementation of the OCI Runtime Specification — the actual program that creates Linux namespaces and cgroups and starts the container process. Most engineers never interact with it directly, since Docker, containerd, and Podman all call it automatically, but nearly every container running today ultimately passes through runc or a compatible alternative.
Q5Can an OCI image run on Windows or macOS?
The image format itself is platform-agnostic, but the runtime specification’s process-isolation techniques are rooted in Linux kernel features. Windows containers use a parallel OCI-inspired runtime that maps the same lifecycle concepts onto Windows isolation primitives, and macOS runs Linux containers inside a lightweight virtual machine, since macOS itself lacks the needed kernel features.
Q6Why does image portability matter if most companies only use one cloud provider anyway?
Even within a single cloud provider, portability protects against vendor lock-in risk, enables hybrid and multi-cloud strategies later, and — just as importantly — lets a company freely choose the best tool for each stage of the pipeline (one company’s build tool, another’s scanner, a third’s registry) instead of being forced into one vendor’s full, proprietary stack.

SSummary and Key Takeaways

Key Takeaways

  • OCI is a standards body, not a company or a product — it publishes specifications that other companies’ tools choose to implement.
  • There are three separate specifications: the Image Specification (how a container is packaged), the Runtime Specification (how a container is executed), and the Distribution Specification (how a container is shared through a registry).
  • Content-addressable digests underpin the whole system, giving images built-in tamper-evidence and enabling widespread layer reuse and caching.
  • The runtime lifecycle (create, start, state, delete) gives orchestrators like Kubernetes a predictable, universal interface regardless of which low-level runtime is actually installed.
  • Security and monitoring are built on top of, not defined by, OCI — signing, scanning, and metrics tooling all lean on the specifications’ predictability rather than being part of the specifications themselves.
  • OCI compliance is why the container ecosystem never fractured the way early competing formats threatened to, letting Docker, Podman, Kubernetes, and every major cloud provider interoperate freely.
  • Understanding these specs pays off in interviews and production alike, since nearly every modern deployment pipeline is really just a sequence of Image, Runtime, and Distribution operations happening in order.