Adetayo Akinsanya unkletayo.dev

The Open Container Initiative (OCI): Image Specification and Runtime Specification

Understanding vendor-neutral container standards, OCI image manifests, and the filesystem bundle layout.

Part 3 in Series — Catch up on the previous article: The Docker Toolchain Deep Dive: CLI to Daemon to containerd to runc (Part 2) before diving into this post.

In 2015, the container ecosystem faced a major risk of fragmentation.

Docker was the dominant container engine, but competing vendors and cloud providers began creating rival container formats and execution runtimes (such as CoreOS’s rkt and appc specification).

If every cloud vendor required a different image format or runtime engine, developers would be trapped rebuilding separate container packages for every cloud infrastructure target.

To prevent vendor lock-in and standardize container primitives, tech leaders formed the Open Container Initiative (OCI) under the Linux Foundation.

The OCI established two fundamental open standards:

  1. The OCI Image Specification (Image-Spec): Defines how container images are formatted, stored, content-addressed, and archived on disk or in registries.
  2. The OCI Runtime Specification (Runtime-Spec): Defines how an unpacked container bundle is executed on a host system by a low-level runtime (such as runc).

Understanding these two specifications explains how any OCI-compliant runtime (like runc, crun, or gVisor) can execute an image built by Docker, Podman, or BuildKit seamlessly.


1. The OCI Image Specification

An OCI container image is not a virtual machine disk image. It is an archived tarball containing a set of cryptographic, content-addressable JSON files and tar compressed layer diffs.

When you inspect an OCI image layout on disk, you find four main elements:

[ OCI Image Layout Structure ]
├── index.json               <-- Image Index (List of manifests for multi-arch support)
├── oci-layout               <-- Layout version identifier ({"imageLayoutVersion": "1.0.0"})
└── blobs/
    └── sha256/
        ├── e3b0c44298fc1c... <-- Image Manifest JSON
        ├── 7a8b9c1d2e3f4a... <-- Image Configuration JSON
        ├── 1a2b3c4d5e6f7a... <-- Layer 1 tar.gz payload
        └── 9f8e7d6c5b4a3f... <-- Layer 2 tar.gz payload

A. The Image Manifest (index.json / Manifest Blob)

The Image Manifest acts as the entry point for an image version tag (e.g., nginx:latest). It links the image configuration JSON file to its underlying filesystem layers:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:7a8b9c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b",
    "size": 1472
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
      "size": 2814021
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e",
      "size": 1420102
    }
  ]
}

Notice that every referenced file is identified by its SHA-256 Digest. If a layer’s contents change by a single byte, its SHA-256 digest changes, enforcing immutable content-addressable storage.


B. The Image Configuration JSON

While layers store the filesystem files, the Image Config JSON defines execution metadata for the target runtime:

{
  "architecture": "amd64",
  "os": "linux",
  "config": {
    "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", "NGINX_VERSION=1.25.1"],
    "Cmd": ["nginx", "-g", "daemon off;"],
    "WorkingDir": "/",
    "ExposedPorts": { "80/tcp": {} }
  },
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:c2ad4e0... (Uncompressed Layer 1 SHA)",
      "sha256:e8f1b2a... (Uncompressed Layer 2 SHA)"
    ]
  }
}

2. The OCI Runtime Specification & Filesystem Bundle

While the Image Spec governs static image archives, the OCI Runtime Specification governs how a container is executed at runtime.

To execute a container, an OCI-compliant runtime like runc requires an OCI Filesystem Bundle.

An OCI Bundle is a simple directory on disk containing exactly two things:

[ OCI Bundle Directory ]
├── config.json         <-- Execution configuration required by runc
└── rootfs/             <-- Unpacked root filesystem directory

The config.json Runtime Contract

When containerd unpacks an OCI image for runc, it reads the Image Config JSON and translates it into an OCI Runtime config.json.

This file instructs runc on which Linux kernel system calls to execute:

{
  "ociVersion": "1.0.2",
  "process": {
    "terminal": false,
    "user": { "uid": 0, "gid": 0 },
    "args": ["nginx", "-g", "daemon off;"],
    "env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"]
  },
  "root": {
    "path": "rootfs",
    "readonly": true
  },
  "linux": {
    "namespaces": [
      { "type": "pid" },
      { "type": "network" },
      { "type": "ipc" },
      { "type": "uts" },
      { "type": "mount" }
    ],
    "resources": {
      "memory": { "limit": 536870912 }
    }
  }
}

When you invoke runc run my-container, runc parses this config.json file, configures the requested Linux namespaces and cgroup memory limits (536870912 bytes = 512MB), enters rootfs, and executes execve("nginx").


OCI Architectural Decoupling Matrix

ComponentStandard SpecificationReference ImplementationResponsibilities
Container Image FormatOCI Image SpecificationBuildKit / Docker EngineDefines image layers, digests, manifests, and config JSON
Container ExecutionOCI Runtime Specificationrunc / crun / gVisorConverts OCI bundle (config.json + rootfs/) into isolated Linux process
Registry ProtocolOCI Distribution SpecificationCNCF Distribution / Docker RegistryDefines HTTP API for pushing and pulling content-addressed blobs

Summary & Next Steps

The Open Container Initiative standardizes containerization across vendor ecosystems:

  • OCI Image Spec defines content-addressable manifests, configuration JSON, and tarball layer diffs.
  • OCI Runtime Spec defines the filesystem bundle (config.json + rootfs/) passed to low-level runtimes.
  • runc reads the OCI bundle configuration to configure namespaces and cgroups before executing the target binary.

In the next article, we build a container from scratch using low-level Linux tools: unshare, chroot, pivot_root, and cgroups.

References & Further Reading

  1. Linux Kernel Organization. pivot_root(2) — Change the root mount point. Linux Man Pages.
  2. Linux Man-Pages Project. mount_namespaces(7) — Overview of Mount Namespaces. Linux Man Pages.
  3. Solomon, J. (2013). The Early Architectural Sketches of Docker and Linux Containers. DotScale Conference.

Up Next in Series →

Part 4: Building a Container From Scratch in Linux: unshare, chroot, pivot_root, and cgroups

Continue to Part 4 →