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
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.
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.
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.
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.
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.
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.
Manifest
A JSON document listing an image’s layers, its configuration, and metadata like architecture and OS — the “packing list” for the image.
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.
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
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"]
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.
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.
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.”
“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
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.
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.
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.
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.
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.
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
SSecurity
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.
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.
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
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.
Sign what you publish
Use cosign or Notary to sign manifest digests, so consumers can verify authenticity before pulling into production.
Minimize the runtime bundle
Drop unused Linux capabilities and enable seccomp filtering explicitly in the runtime config rather than relying on defaults.
Keep layers logically grouped
Order build steps from least to most frequently changing to maximize layer cache hits across builds.
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.
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
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.