Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Understanding OCI: tag, digest, manifest, multi-arch and artifacts

By updated

Improve this page

Have you noticed that docker pull nginx:1.25 does not always download the same layers? There are two main causes:

  1. A different platform (AMD64 vs ARM64) → different layers (multi-arch images)
  2. A mutable tag → the image was rebuilt/republished

To understand these behaviors, you need to know the OCI standard (Open Container Initiative). This guide explains the key concepts: tags, digests, manifests, multi-arch and artifacts.

What you will learn

  • OCI: the open standard that defines the format of images and registries
  • Tag vs digest: why a tag can change, but a digest cannot
  • Manifest: the JSON file that describes an image
  • Multi-arch: how a single reference serves several architectures
  • OCI artifacts: storing Helm charts, SBOMs and signatures in a registry

What is OCI?

The Open Container Initiative (OCI) is an organization that defines the open standards for containers:

SpecificationWhat it defines
OCI ImageImage format (layers, config, manifest)
OCI RuntimeHow to run a container (runc)
OCI DistributionRegistry API (push, pull, list)

Why does it matter? Thanks to OCI, an image built with Docker works with Podman, containerd, CRI-O. A registry such as Harbor, Quay or GHCR speaks the same language as Docker Hub.

Anatomy of an OCI image

An OCI image is made of 3 types of objects, all identified by their digest (SHA256 hash). It is a content-addressable system: the content determines the identifier, not the other way around.

In the diagram, follow the arrows from the tag: it points to the manifest, which in turn references the config and each layer by their digest.

In an OCI registry, the nginx:1.25 tag points to the manifest, which references by digest the config (ENV, CMD, USER, EXPOSE) and the layers (base, application); each object is identified by its SHA256.OCI RegistryTagnginx:1.25digest = sha256:…hash of the manifest, immutableManifestmediaType, config, layersConfigsha256:aaa…LayersContent-addressable:every object is identified by its SHA256Layer 1sha256:bbb… (base)Layer 2sha256:ccc… (app)Layer Nsha256:ddd…config: sha256:aaalayers: sha256:bbb, sha256:ccc
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %
ObjectContentIdentifier
ManifestReference to config + layerssha256:xxx...
ConfigMetadata (env, cmd, user, ports)sha256:yyy...
LayersFiles of each layer (tar.gz)sha256:zzz...

The digest of an image = the SHA256 hash of its manifest. If the manifest changes (even slightly), the digest changes.

Content-addressable vs location-addressable

Unlike classic URLs (location-addressable), OCI uses content addressing:

ApproachExampleCharacteristic
Location-addressablehttps://example.com/image.tarSame URL, content can change
Content-addressablesha256:abc123...Same hash = same content guaranteed

Advantages of content-addressable storage:

  • Deduplication: identical layers shared between images
  • Verification: integrity can be checked by recomputing the hash
  • Immutability: a digest always references the same content

Tag vs digest: the fundamental difference

The tag: a mutable alias

A tag is a readable name that points to a manifest. The same tag can point to different manifests over time.

Terminal window
# Today
nginx:1.25 → sha256:abc123...
# Tomorrow (after a rebuild)
nginx:1.25 → sha256:def456... # Different!

It is like a bookmark: it points to a page, but you can move it.

The digest: an immutable fingerprint

A digest is the SHA256 hash of the manifest. It identifies one exact version of the image, permanently.

Terminal window
# Always the same image, forever
nginx@sha256:6926dd802f40e5e7257fded83e0d8030039642e4e10c4a98a6478e9c6fe06153

It is like a fingerprint: it never changes for a given person.

When to use which?

ContextUseExample
Local developmentTagnginx:1.25
CI/CD (publishing)Versioned tag + capture the digestmon-app:1.2.3 → sha256:abc...
Production (deploy)Digestmon-app@sha256:abc...
Audit / complianceDigestExact traceability
Dockerfile FROMTag or digest depending on how critical it isFROM alpine:3.19 or FROM alpine@sha256:...

Golden rule: in production, pin by digest, not by tag. A versioned tag such as v1.2.3 is not immutable for all that: it can be republished, unless the registry forbids it through a policy.

Promotion by digest in CI/CD

The recommended workflow to guarantee traceability:

Terminal window
# 1. Build and push with a tag
docker buildx build --tag ghcr.io/mon-org/mon-app:v1.2.3 --push .
# 2. Capture the digest from the registry
DIGEST=$(crane digest ghcr.io/mon-org/mon-app:v1.2.3)
echo "Built: ghcr.io/mon-org/mon-app@${DIGEST}" >> release-metadata.txt
# 3. Deploy by digest (immutable)
kubectl set image deployment/mon-app \
app=ghcr.io/mon-org/mon-app@${DIGEST}

This pattern separates publishing (a tag for humans) from deployment (a digest for reproducibility).

The manifest: the heart of the image

The manifest is a JSON file that describes an image. It contains:

  • The digest of the config (metadata)
  • The list of layers with their digests and sizes
  • The media type (format)
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:aaa...",
"size": 1234
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:bbb...",
"size": 5678
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:ccc...",
"size": 9012
}
]
}

Media types: Docker vs OCI

Registries support two manifest formats. In practice, most tools handle both transparently.

FormatManifest media typeUsage
Docker v2application/vnd.docker.distribution.manifest.v2+jsonLegacy, historical Docker Hub
OCIapplication/vnd.oci.image.manifest.v1+jsonCurrent standard, recommended

See the manifest of an image:

Terminal window
# With crane
crane manifest nginx:1.25 | jq .
# With skopeo
skopeo inspect --raw docker://nginx:1.25 | jq .

Multi-arch: one reference, several architectures

How does docker pull nginx download the right version for your CPU (AMD64 or ARM64)? Thanks to the manifest list (or OCI image index).

The principle

A manifest list is a special manifest that contains references to several manifests, one per architecture.

The diagram follows a docker pull: the client reads the manifest list, keeps the manifest for its platform (the others stay dotted), then downloads the matching layers.

Multi-architecture resolution: the linux/amd64 client asks for nginx:1.25, receives the manifest list, picks the amd64 manifest among amd64, arm64 and arm/v7, downloads its layers and gets the local image; docker buildx creates the manifest list.① GET② platform match③ pull④ local imagecreates the manifest listClientlinux/amd64Manifest listnginx:1.25amd64 manifestspecific layersarm64 manifestspecific layersarm/v7 manifestspecific layersamd64 layerslayer1.tar.gz, layer2.tar.gzLocal imageready to rundocker buildx build--platform …
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

When you run docker pull nginx:1.25:

  1. Docker downloads the manifest list
  2. It identifies your architecture (e.g. linux/amd64)
  3. It downloads the manifest specific to your architecture
  4. It downloads the matching layers

Build multi-arch images with Buildx

Terminal window
# Create a multi-platform builder
docker buildx create --name multi-arch --driver docker-container --use
# Build and push for AMD64 + ARM64
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag mon-registry/mon-app:v1.0 \
--push .
# Check the available architectures
docker manifest inspect mon-registry/mon-app:v1.0

See the available architectures

Terminal window
# With docker manifest
docker manifest inspect nginx:1.25
# With crane
crane manifest nginx:1.25 | jq '.manifests[] | {platform, digest}'
# Result
{
"platform": { "architecture": "amd64", "os": "linux" },
"digest": "sha256:amd123..."
}
{
"platform": { "architecture": "arm64", "os": "linux" },
"digest": "sha256:arm123..."
}

OCI artifacts: beyond images

OCI registries can store other types of artifacts than container images:

ArtifactUse caseTool
Helm chartsKubernetes packageshelm push
SBOMList of software componentsORAS, Syft
SignaturesAttestation of originCosign, Notation
PoliciesOPA/Rego rulesConftest
WASM modulesWebAssemblyORAS

OCI 1.1 and the Referrers API

Since March 2024, the OCI 1.1 specs make it possible to associate artifacts with an image through the subject field. This solves a major problem: how do you know which SBOMs, signatures and attestations are linked to an image?

In the diagram, each artifact (signature, SBOM, attestation, vulnerability report) points to the image through its subject field, and the Referrers API lists them all from the image digest.

The Referrers API (OCI 1.1) finds, from the digest of an image, the artifacts that target it through their subject field: Cosign signature, SBOM, SLSA provenance attestation and vulnerability report.subjectsubjectsubjectsubjectImagemyapp:v1.0sha256:abc123…SignatureCosignSBOMSPDX / CycloneDXAttestationSLSA provenanceVulnerability reportTrivy / GrypeReferrers APIGET /v2/‹name›/referrers/‹digest›lists every linked artifact
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

How it works:

  1. The artifact (signature, SBOM...) contains a subject field with the digest of the target image
  2. The registry automatically indexes this relationship
  3. The GET /v2/<name>/referrers/<digest> API returns all the linked artifacts
Terminal window
# List the artifacts linked to an image (with ORAS)
oras discover ghcr.io/mon-org/mon-app:v1.0
# Typical result
ghcr.io/mon-org/mon-app@sha256:abc123
├── application/vnd.dev.cosign.simplesigning.v1+json (signature)
├── application/spdx+json (SBOM)
└── application/vnd.in-toto+json (SLSA attestation)

ORAS: OCI Registry As Storage

ORAS (OCI Registry As Storage) is a tool and a specification to push/pull any file to an OCI registry.

Terminal window
# Push any file
oras push ghcr.io/mon-org/configs:v1 config.yaml:application/yaml
# Pull the file
oras pull ghcr.io/mon-org/configs:v1
# Push an SBOM generated by Syft, linked to an image (OCI 1.1)
syft nginx:1.25 -o spdx-json > sbom.json
oras push ghcr.io/mon-org/sboms:nginx-1.25 \
--artifact-type application/spdx+json \
--subject ghcr.io/mon-org/nginx@sha256:abc... \
sbom.json:application/spdx+json

Why is it useful?

  • A single system to store images, configs, SBOMs, signatures
  • The same authentication as for images
  • The same replication between registries
  • Associate an SBOM or a signature with an image through its digest

CLI tools to handle OCI images

ToolStrengthsInstallation
craneLightweight, fast, reusable Go APIgo install github.com/google/go-containerregistry/cmd/crane@latest
skopeoCopy between registries, air-gapSystem package (apt, dnf)
regctlTag cleanup, garbage collectiongo install github.com/regclient/regclient/cmd/regctl@latest
orasGeneric artifacts, Referrers APIgo install oras.land/oras/cmd/oras@latest
Terminal window
# Copy an image between registries (without a Docker daemon)
skopeo copy docker://nginx:1.25 docker://mon-registry/nginx:1.25
# Delete a tag (careful: irreversible)
crane delete mon-registry/mon-app:old-tag
# Explore the layers of an image
crane export nginx:1.25 - | tar -tvf - | head -20

Each tool in the table has its own guide: ORAS (in French) to handle OCI artifacts, Skopeo (in French) to inspect and copy images without a daemon, and Crane (in French), Google's CLI to handle OCI images. The tag strategy, the cache, multi-arch and the promotion of an image from one environment to the next are covered in CI/CD for images (in French). Now that the OCI format is clear, the supply chain of images (in French) (SBOM, scan, signature) draws the DevSecOps basics from it for trusted images.

Key points

  1. OCI = open standard for images and registries (Docker/Podman/containerd interoperability)
  2. Tag = mutable alias to a manifest (can change) → development
  3. Digest = immutable SHA256 hash of the manifest → production, audit
  4. Content-addressable = the identifier is the hash of the content → deduplication, verification
  5. Manifest = JSON that lists config + layers with their digests
  6. Manifest list = multi-arch index that points to one manifest per architecture
  7. OCI 1.1 (March 2024) = Referrers API to link signatures, SBOMs, attestations to images
  8. ORAS = store any artifact (SBOM, signatures, configs) in an OCI registry
  9. Tools: crane (lightweight), skopeo (copy), regctl (cleanup), oras (artifacts)

Knowledge check

Check that you have absorbed the essential OCI concepts: tags versus digests, the manifest, multi-arch images and OCI artifacts.

Knowledge check

Check what you have learned with this interactive quiz

7 questions
5 min
80% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn