In 2010, Docker introduced containers to the world using a proprietary format that only Docker could work with. By 2015, it became clear that the container ecosystem needed an open, vendor-neutral standard for images and runtimes. Docker took a strategic decision and created the Open Container Initiative (OCI) as a Linux Foundation project and donated the container format specifications to it. Today, because of that standard, you can build container images with Docker on your development laptop, push them to Artifactory or Docker Hub, pull them with Podman on a different machine, and run them in Kubernetes—all using the exact same image format without any conversion or compatibility layers.
This guide explains the three OCI specifications, what each one does, and why standardization is critical for a portable container ecosystem.
graph TB OCI["Open Container<br/>Initiative"] OCI --> Image["OCI Image<br/>Specification<br/>Image format<br/>Layers & Config"] OCI --> Runtime["OCI Runtime<br/>Specification<br/>How to run<br/>Bundles & specs"] OCI --> Distrib["OCI Distribution<br/>Specification<br/>How to push/pull<br/>Registry protocol"] Docker["Docker<br/>Builds & Pushes"] Podman["Podman<br/>Builds & Runs"] Kubernetes["Kubernetes<br/>Runs Containers"] Image --> Docker Image --> Podman Runtime --> Kubernetes Distrib --> Docker Distrib --> Podman style Docker fill:#e3f2fd style Podman fill:#e3f2fd style Kubernetes fill:#e3f2fdTable of Contents
- Brief History: Docker to OCI
- The Three OCI Specifications
- OCI Image Specification Deep Dive
- OCI Runtime Specification Deep Dive
- OCI Distribution Specification Deep Dive
- What OCI Compliance Means
- OCI Adoption in the Wild
- The Value of Portability
- Implementation Differences Within the Standard
- Next Steps
Brief History: Docker to OCI
Docker's introduction to the world in 2013 was transformative, fundamentally changing how developers build and ship applications. However, the initial container format was proprietary, controlled entirely by Docker Inc. Building containers required Docker's own tooling, pushing to registries required Docker's tools, and running containers required Docker's runtime. No other company could create compatible tools without Docker's permission. Between 2014 and 2015, a critical industry problem emerged. Other companies wanted to build container tooling and participate in the rapidly growing container ecosystem, but couldn't create compatible tools. Cloud providers wanted to run containers but were uncomfortable with the business risk of depending solely on a single company (Docker Inc.) for the container format specification. The entire industry recognized that the ecosystem needed an open, vendor-neutral standard for containers that was not controlled by any single company.
In June 2015, Docker Inc. took a strategic decision that fundamentally shaped the container industry. The company established the Open Container Initiative (OCI) as a Linux Foundation project and donated the image and runtime specifications to the initiative. The goal was explicit: create an open, vendor-neutral standard for containers that could be implemented by any company or project. The OCI released the Image Specification in July 2017, followed by the Runtime Specification in July 2017. The Distribution Specification, which defines how to push and pull images from registries, was released in May 2020. Today, all major container tools and platforms conform to the OCI specifications.
This historical development has profound implications. You are no longer locked into Docker's proprietary implementation. Podman, containerd, CRI-O, and dozens of other container runtimes can all consume OCI images and run OCI-compliant containers because the format is standardized and publicly specified. This standardization created unprecedented portability and flexibility in the container ecosystem.
The Three OCI Specifications
The Open Container Initiative publishes three complementary standards that work together to create a complete, portable, and interoperable container ecosystem. The Image Specification defines what a container image is structurally, including how it is organized into layers, what configuration metadata it carries, and how these components are referenced and combined. This specification is used by all build tools that create images, by container registries that store and distribute images, and by container runtimes that execute them. The specification defines exactly how image contents are represented as tar files and registry blobs, enabling perfect compatibility across tools. The Runtime Specification defines how to execute a container from an OCI-compliant image, using a standardized config.json file to specify runtime parameters such as namespaces, cgroups, and process configuration. Container runtimes like Docker, Podman, containerd, and CRI-O all implement the Runtime Specification to create and manage containers from images in a standardized way. The Distribution Specification defines how to push and pull images between registries using standardized HTTP and HTTPS APIs. Container registries like Docker Hub, Artifactory, Harbor, and cloud provider registries implement this specification to enable the common protocol for moving images securely across systems.
Each of the three specifications is independent and can be used separately if needed. An organization might use the Image Spec to define an image format without using the Distribution Spec (by storing images locally). They might use the Runtime Spec to execute containers without using the Distribution Spec. However, the true power of OCI standardization emerges when all three specifications are used together as a complete system, creating an end-to-end standardized container ecosystem.
OCI Image Specification Deep Dive
The Image Specification defines the structure of a container image by specifying the key components that make up every OCI-compliant image. Layers are read-only filesystem snapshots that are ordered and compressed, with each layer representing a set of changes to the filesystem. The config blob is a JSON metadata file containing environment variables, entrypoint specifications, working directory, labels, and other configuration parameters. The manifest is a JSON document that serves as a pointer to the layers and config blob, specifying their location and digest. The image index, also known as a manifest list, provides multi-architecture support by allowing one tag to correspond to multiple architecture-specific manifests. MediaTypes are content type labels that identify different blob types within the image. Annotations are key-value metadata pairs that you can attach to images for additional context and organization.
Image Spec: Layer Format
Each layer in an OCI image is stored as a compressed tar file (tar.gz) containing filesystem changes. An image layer is represented as a compressed tar file such as layer-1.tar.gz, containing the filesystem contents specific to that layer, including files like /usr/bin/python3, /lib/libc.so.6, /etc/os-release, and other files. When decompressed, the tar archive contains a flat filesystem representation. The container runtime then stacks multiple layers using copy-on-write mechanisms such as UnionFS or OverlayFS to create the complete container filesystem.
The OCI specification is precise about what must be stored and verified. The specification defines layer_blobs as the actual compressed tarballs stored in registries, and diff_ids as the sha256 hash of each layer when decompressed, stored in the image configuration. This dual-hash approach enables verification at multiple points. A registry communicates "this layer has digest sha256:abc", the runtime decompresses the layer, verifies that the decompressed content has the matching diff_id hash, and only then mounts and uses the layer in the container filesystem.
Image Spec: Config Blob Structure
{ "architecture": "amd64", "config": { "Hostname": "", "Domainname": "", "User": "appuser", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "LANG=C.UTF-8" ], "Cmd": ["/bin/sh"], "Entrypoint": ["/docker-entrypoint.sh"], "Image": "", "Volumes": { "/data": {} }, "WorkingDir": "/app", "Labels": { "version": "1.0", "maintainer": "team@example.com" }, "StopSignal": "SIGTERM" }, "container_config": { "hostname": "builder", "cmd": ["/bin/sh", "-c", "go build ..."] }, "created": "2023-10-15T10:30:45.123Z", "history": [ { "created": "2023-10-15T10:25:00Z", "created_by": "/bin/sh -c apt-get update" } ], "os": "linux", "rootfs": { "type": "layers", "diff_ids": [ "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "sha256:2c26b46911685ce267458b7d1d37d5c8a5e1e6c5aca34a02e6...", "sha256:a64507fcb4b7f5e7d7c6b8f4e1a2c3d4e5f6g7h8i9j0k1l2m3..." ] }}The config blob contains several critical fields that define how the container will execute. The architecture and os fields specify the build target, such as amd64 or arm64 for the architecture and linux for the operating system. The Env field contains environment variables that will be passed to the container process. The Entrypoint field specifies the primary command to run, which cannot be easily overridden at runtime. The Cmd field provides arguments that are passed to the entrypoint and can be overridden when running the container. The WorkingDir field specifies the current directory when the container process starts. The Volumes field indicates which paths within the container should be mounted as volumes. The Labels field contains metadata as key-value pairs. Finally, the diff_ids field contains the uncompressed hash of each layer, enabling verification that layers have not been tampered with.
Image Spec: Manifest Structure
The manifest serves as the "recipe" or blueprint for constructing a container image. The manifest is a JSON document that contains a schema version number, a media type identifier, a reference to the configuration blob, and a list of all the image layers. The config section of the manifest references the config blob by its digest (sha256 hash), along with the blob's size and media type. The layers section is an ordered list of all image layers, with each layer specified by its digest, size, and media type.
When a container runtime retrieves an image, it uses the manifest as a guide. First, it downloads the config blob by requesting the digest specified in the manifest. Then, it downloads each of the layers in order, as listed in the manifest. As each blob is downloaded, the runtime verifies that the downloaded content matches the digest specified in the manifest, ensuring that no tampering or corruption has occurred. Finally, the runtime stacks the layers in order (typically using copy-on-write techniques) and reads the config blob to determine how the container should be executed.
Image Spec: Image Index (Multi-Architecture)
For images that must support multiple architectures or platforms, the OCI specification defines an image index, also known as a manifest list. The image index is itself a JSON document that contains a list of manifests, each corresponding to a specific architecture and operating system combination. Each entry in the manifest list includes the digest of a platform-specific manifest, along with metadata indicating the architecture (such as amd64, arm64, or ppc64le) and operating system (such as linux) for that manifest.
This multi-architecture support creates a seamless user experience. When a user pulls myapp:latest on an amd64 machine, the registry examines the image index, identifies the platform information, and returns the amd64-specific manifest to the client. When the same user (or a different user) pulls the same tag on an arm64 machine, the registry returns the arm64-specific manifest. To the user pulling the image, the tag myapp:latest works identically regardless of their architecture, because the registry automatically resolves the correct manifest based on the client's platform.
OCI Runtime Specification Deep Dive
The Runtime Specification defines how a container runtime takes an image and executes it by taking an OCI-compliant image and a config.json file, then creating and running an isolated process.
Runtime Spec: What is config.json?
config.json is a configuration file that tells the runtime which image or rootfs to use, what process to run, what namespaces to create, what cgroups to apply, and what mounts to set up.
Example:
{ "ociVersion": "1.0.0", "process": { "terminal": false, "user": { "uid": 0, "gid": 0 }, "args": ["/bin/sh"], "env": [ "PATH=/usr/sbin:/usr/bin:/sbin:/bin", "TERM=xterm" ], "cwd": "/", "capabilities": { "bounding": ["CAP_CHOWN", "CAP_DAC_OVERRIDE", ...], "permitted": ["CAP_CHOWN", "CAP_DAC_OVERRIDE", ...], "inheritable": [], "effective": ["CAP_CHOWN", "CAP_DAC_OVERRIDE", ...], "ambient": [] } }, "root": { "path": "/rootfs", "readonly": false }, "hostname": "mycontainer", "mounts": [ { "destination": "/proc", "type": "proc", "source": "proc" }, { "destination": "/sys", "type": "sysfs", "source": "sysfs" }, { "destination": "/dev", "type": "tmpfs", "source": "tmpfs" } ], "linux": { "uid_mappings": [{"containerID": 0, "hostID": 1000, "size": 1000}], "gid_mappings": [{"containerID": 0, "hostID": 1000, "size": 1000}], "resources": { "devices": [ { "allow": false, "access": "rwm" }, { "allow": true, "type": "c", "major": 1, "minor": 3, "access": "rw" } ], "memory": { "limit": 1073741824 }, "cpu": { "shares": 512 } }, "namespaces": [ {"type": "pid"}, {"type": "ipc"}, {"type": "uts"}, {"type": "mount"}, {"type": "network"}, {"type": "user"}, {"type": "cgroup"} ] }}The config.json file contains several key sections that together specify how the container should be executed. The process section specifies what process to run and how to run it, including the command arguments, environment variables, working directory, and Linux capabilities available to the process. The root section specifies the path to the rootfs (root filesystem), which is the location where the container filesystem layers are stacked and mounted. The hostname field specifies the hostname that the container will perceive. The mounts section specifies additional filesystems that should be mounted into the container, such as proc, sysfs, and tmpfs. The linux section contains Linux-specific settings such as namespace configuration, cgroup resource limits, and user ID mapping between the container and the host.
Runtime Spec: Lifecycle
The OCI Runtime Specification defines a standardized container lifecycle with specific states and transitions. When a container is created, the create phase sets up all the isolation mechanisms (namespaces, cgroups, mount points) but does not start the main process. The start phase runs the process specified in config.json as PID 1 inside the container. The pause phase freezes all processes in the container using cgroup freezer mechanisms, without actually terminating them. The resume phase unfreezes paused processes. The stop phase sends a SIGTERM signal to the main process, allowing it to shut down gracefully. The kill phase sends a SIGKILL signal, forcefully terminating the process. The delete phase cleans up all resources associated with the container, including cgroups and mounted filesystems.
A typical docker run command orchestrates several of these lifecycle phases in sequence: first create sets up all isolation, then start runs the process, the command then waits for the process to exit, and finally delete cleans up all resources.
Runtime Spec: Hooks
The OCI Runtime Specification allows custom scripts or programs to be executed at specific points in the container lifecycle, providing opportunities for additional setup, monitoring, and cleanup operations. The prestart hook runs before the container process starts, allowing setup operations such as network configuration or volume mounting to occur with the container's namespace environment already established. The poststart hook runs after the container process starts, making it suitable for operations like recording the container's process ID, initializing monitoring systems, or logging that the container has begun execution. The poststop hook runs after the container process has exited and before resources are fully cleaned up, providing an opportunity for cleanup operations, archiving logs, or notifying external systems that the container has terminated.
OCI Distribution Specification Deep Dive
The Distribution Specification defines how to push and pull images from registries using a standard HTTP API for uploading and downloading image content including layers, manifests, and configs.
Distribution Spec: Push and Pull Protocol
The Distribution Specification defines standardized protocols for pushing and pulling images between clients and registries. The push flow describes how a client uploads an image to a registry. The client begins by computing the sha256 digest of each layer and the config blob in the image. For each blob, the client queries the registry with a request like "Do you have blob sha256:abc123?" The registry responds indicating whether it already possesses that blob. For any missing blobs, the client uploads them to the registry. Once all blobs are uploaded, the client uploads the manifest, which is a JSON document that references the layers by their digest. Finally, the client uploads a tag that points to the manifest, allowing other clients to retrieve the image by name and tag.
The pull flow describes how a client retrieves an image from a registry. The client asks the registry "What is the manifest for image myapp:latest?" The registry returns the manifest as JSON, which contains a list of layer digests. The client then checks whether it already has those layers cached locally. For any missing layers, the client downloads them from the registry. As each layer is downloaded, the client verifies that the downloaded content matches the digest specified in the manifest, ensuring integrity. Finally, the client stacks the downloaded and cached layers and mounts the combined filesystem for use by the container runtime.
Distribution Spec: HTTP API Endpoints
The spec defines the HTTP API that registries implement:
GET /v2/ Check if registry is compatible HEAD /v2/<name>/blobs/<digest> Check if a blob exists GET /v2/<name>/blobs/<digest> Download a blob POST /v2/<name>/blobs/uploads/ Initiate blob upload PATCH /v2/<name>/blobs/uploads/<uuid> Upload chunk of blob PUT /v2/<name>/blobs/uploads/<uuid> Finalize blob upload PUT /v2/<name>/manifests/<reference> Upload manifest GET /v2/<name>/manifests/<reference> Download manifestDistribution Spec: Content-Addressable Storage
The OCI Distribution Specification requires registries to use content-addressable storage, a paradigm where each blob is stored and retrieved by its sha256 digest rather than by name. This design choice has profound consequences throughout the container ecosystem. Deduplication becomes possible because one blob can be referenced by multiple images. If two images share a common layer, the registry stores it once, and both images reference the same blob by digest, saving storage space. Immutability is guaranteed by the digest-based addressing scheme: if you download the same blob twice from the same registry, the cryptographic digest ensures that you receive the same content both times, with any modification or tampering being immediately detectable. Efficiency improves dramatically because images sharing layers only require uploading and downloading the shared layers once. When you push a new image that layers on top of existing layers, the registry recognizes the existing layers by their digest and doesn't require you to upload them again.
Distribution Spec: Conformance
A registry is considered OCI Distribution Spec compliant if it implements several key requirements. The registry must implement all required HTTP endpoints defined by the specification, including endpoints for blob uploads, downloads, and manifest operations. The registry must use content-addressable storage where blobs are identified and retrieved by their digest. The registry must support manifest versioning using the v2 schema. The registry must properly handle image indexes to support multi-architecture images. Finally, the registry must support authentication mechanisms such as Bearer token authentication or basic HTTP authentication to control access to images.
What OCI Compliance Means
An OCI-compliant runtime or registry must meet specific requirements. A compliant runtime understands the OCI Image Specification including how layers are organized, what configuration metadata means, and how manifests point to layers and configs. It can read and properly interpret config.json files as defined by the Runtime Specification, extracting the process arguments, environment variables, namespaces, cgroups, and other execution parameters. It can pull images from registries using the Distribution Specification HTTP API, handling authentication, blob downloads, and digest verification. Critically, a compliant runtime can execute any OCI-compliant image from any source—whether it came from Docker Hub, Artifactory, a private registry, or any other OCI-compliant registry.
Docker is OCI compliant, as it originated the specifications. Podman implements all three OCI specifications and is completely OCI compliant. containerd serves as the industry standard container runtime and is OCI compliant. CRI-O is Kubernetes's preferred container runtime and is OCI compliant. Buildkit, which is Docker's image builder, builds images in OCI-compliant format. Docker Hub operates as an OCI-compliant registry. Artifactory, Harbor, AWS ECR, and Google GCR all implement the OCI Distribution Specification and are compliant registries.
The practical consequence of OCI compliance is unprecedented interoperability. You can build images with Docker, push them to Artifactory, pull them with Podman, run them with containerd in Kubernetes, sign them with Cosign, and scan them with Trivy, all working with the same image format without any conversion or compatibility layer.
OCI Adoption in the Wild
The container industry has achieved near-universal adoption of OCI standards across every major tool and platform. Docker, which originated the specifications and donated them to OCI, maintains full compliance with all three specifications. Podman provides 100% compatibility with OCI specifications across all three dimensions—building images, pushing to registries, and running containers. containerd has become the industry standard container runtime and implements all OCI specifications completely. CRI-O was designed from inception with OCI compliance as a core architectural principle and has become Kubernetes's preferred container runtime. Buildkit, Docker's primary image builder, produces OCI-compliant images. Kubernetes itself, the industry's dominant container orchestration platform, exclusively runs OCI-compliant images. On the registry side, Docker Hub is OCI compliant, as are major enterprise registries like Artifactory and Harbor. Cloud provider registries—AWS ECR, Google GCR, and Azure ACR—all implement OCI Distribution Specification compliance. Supporting tools in the ecosystem are also compliant: Cosign signs and verifies OCI images, and Trivy scans them for vulnerabilities.
The remarkable portability of containers across all these different tools, cloud providers, and environments exists directly because of OCI standardization. Without this standardization, each tool would require its own format and conversion layers, creating a fragmented ecosystem.
The Value of Portability
Before OCI standardization, if an organization wanted to build container images on developers' laptops using Docker, push those images to a private registry, run them in Kubernetes, sign them for security, scan them for vulnerabilities, and potentially use alternative container runtimes, they had to ensure that every single tool in that workflow supported Docker's proprietary format. This created significant lock-in risk: Docker's implementation became the de facto standard, and switching to alternative tools required finding tools that could work with Docker's format or rebuilding images from scratch.
Today, because the container ecosystem has standardized on OCI, organizations enjoy unprecedented flexibility. Build tool flexibility means you can choose among Docker, Podman, Buildkit, nix, Bazel, and other tools, all producing OCI-compliant images. Registry flexibility allows images to be hosted on Docker Hub, Artifactory, Harbor, AWS ECR, Google GCR, or your own private registry, with all registries understanding OCI standards. Runtime flexibility means you can use Docker during development, switch to containerd for production, and run with CRI-O in Kubernetes, all executing the same images. Tool flexibility enables signing with Cosign, scanning with Trivy, and inspecting with Skopeo, with all tools working seamlessly with OCI images. Migration flexibility permits switching container runtimes without rebuilding images—a significant operational advantage. This flexibility in every dimension of the container workflow is the entire value proposition of OCI standardization.
Implementation Differences Within the Standard
OCI specifications define what must be done and what the API boundaries look like, but they do not mandate how implementations must accomplish those goals. As a result, different tools implement OCI standards differently while remaining compliant.
Image Spec: All OCI-compliant images have the same underlying structure, but Docker includes additional vendor-specific fields in manifests using the MediaType application/vnd.docker.distribution.manifest.v2+json, while OCI-native implementations use application/vnd.oci.image.manifest.v1+json. Both are valid according to the specification, and registries accept both formats. A tool can understand both formats without any loss of compatibility.
Runtime Spec: All OCI-compliant runtimes create Linux namespaces and cgroups according to the specification, but their internal implementations differ significantly. Docker uses containerd as its runtime engine. Podman implements its own custom runtime. CRI-O implements its own runtime optimized for Kubernetes. Each runtime has different performance characteristics, feature sets, and debugging tools, yet all can execute the same OCI-compliant images.
Distribution Spec: All OCI-compliant registries implement the HTTP API as specified, but registries extend the API with proprietary features. Docker Hub implements proprietary deduplication mechanisms beyond the specification. Artifactory extends the REST API with additional features for enterprise use cases. Harbor adds access control layers for security and multi-tenancy. Despite these extensions, the core push and pull operations work identically across all registries.
The key insight from these implementation differences is that OCI standardization does not require all tools to be identical. Instead, it means they all speak the same language at the API boundary, enabling interoperability while preserving the freedom for implementations to optimize and extend for their specific use cases.
Next Steps
Understanding OCI standards is critical for anyone working with modern container technology. OCI knowledge enables you to confidently use different tools with the assurance that they are fundamentally compatible. Understanding what "OCI compliance" means helps you evaluate container tools and platforms objectively. Knowing how OCI standards work demystifies why container portability is possible across such a diverse ecosystem. Finally, understanding the standardized interfaces allows you to debug issues by knowing which layer of the stack is responsible for which functionality.
Practical next steps to deepen your understanding include inspecting actual container manifests using tools like skopeo inspect docker://ubuntu:22.04 to see the real JSON structures behind image tags. Test container portability by pulling an image with Docker and running it with Podman, verifying that the same image works across different tools. Review the actual OCI specifications by reading the official specifications at https://github.com/opencontainers/specs, which are well-documented and readable. Explore which container runtime is used by your container orchestration platform, and understand the implications of that choice. Study how different registries like Docker Hub, Google GCR, and Artifactory organize and serve images, noting both their common compliance and their unique extensions.
Key Concepts Summary
The Open Container Initiative defines three complementary specifications that work together to create a portable container ecosystem. The Image Specification defines the structural format of container images, including how images are organized into layers, what configuration metadata they contain, and how manifests reference these components. The Runtime Specification defines how container runtimes execute images, including the config.json format, the container lifecycle operations, and how namespaces and cgroups are configured. The Distribution Specification defines the push and pull protocol for moving images between registries and the content-addressable storage model that enables efficient deduplication. OCI compliance means adherence to these standards at the API boundaries, allowing tools to implement their own optimizations and extensions while maintaining interoperability. All major container tools and registries in production today are OCI compliant, a situation that was enabled by Docker's donation of the specifications to a vendor-neutral foundation. This standardization enables unprecedented portability and flexibility, allowing organizations to mix and match tools across building, pushing, pulling, signing, scanning, and running containers. Despite their compliance, implementations differ in their internal architecture and extensions, demonstrating that standardization improves interoperability without eliminating healthy competition and innovation.
