Working with OCI

Embed OCI image layouts in component versions and access them natively from OCI registries after transfer.

In this tutorial, you’ll learn how to embed OCI artifacts as local blobs inside component versions and transfer them to OCI registries where they become natively accessible — pullable with standard OCI tools like docker pull, oras pull, or crane.

What You’ll Learn

  • Create an OCI image layout using the ORAS CLI
  • Embed an OCI image layout as a local blob in a component version
  • Transfer a component version with --copy-resources so that external OCI image references are internalized as local blobs
  • Access local blobs natively from an OCI registry by their localReference digest and media type

Estimated time: ~20 minutes

Scenario

You’re packaging a microservice as an OCM component. The component includes a container image that must travel with the component — not just as a reference, but as an embedded artifact. When the component arrives at a target registry, you want the image to be pullable directly with standard OCI tooling, without needing the OCM CLI.

OCM v2 makes this possible through its OCI-compatible index representation. When a component version is stored in an OCI registry, native OCI artifacts (images, Helm charts, OCI image layouts) stored as local blobs are mapped to proper OCI manifests within the component version’s index. This means they can be accessed directly by digest using any OCI-compliant client.

How It Works

  flowchart LR
    subgraph create ["Create"]
        direction TB
        ORAS["ORAS CLI"] --> Layout["OCI Image Layout (.tar)"]
        Layout --> Constructor["component-constructor.yaml"]
        Constructor --> CTF["CTF Archive"]
    end

    CTF --> Transfer["ocm transfer cv<br/>--copy-resources"]

    subgraph registry ["OCI Registry"]
        direction TB
        Index["Component Version<br/>(OCI Index)"]
        Manifest["OCI Image Manifest<br/>(native access)"]
        Index --> Manifest
    end

    Transfer --> Index

    Manifest --> Pull["docker pull / oras pull<br/>by digest"]

    style Pull fill:#dcfce7,color:#166534

The component version is stored as an OCI index. Local blobs with OCI media types are stored as separate OCI manifests within that index, making them natively accessible by their digest.

Prerequisites

  • OCM CLI installed
  • ORAS CLI installed (for embedding OCI image layouts)
  • jq installed (for inspecting JSON)
  • Access to an OCI registry (e.g. a local registry via docker run -d -p 5001:5000 registry:2)

Tutorial

How Native Access Works

Under the hood, OCM v2 stores component versions as OCI Image Indexes. When a local blob has an OCI-native media type (image manifest, image index, or OCI image layout), it is stored as a separate OCI manifest referenced from the component version’s index — not as an opaque layer.

  flowchart TB
    subgraph index ["Component Version (OCI Index)"]
        Desc["Descriptor Manifest<br/>software.ocm.descriptor=true"]
        NativeImg["Image Manifest<br/>(native OCI artifact)"]
    end

    Desc -- "layer" --> NonOCI["config.yaml<br/>(non-OCI blob)"]
    Desc -- "ocm download resource" --> OCM["OCM CLI consumer"]
    NativeImg -- "docker pull / oras pull / crane pull" --> Pull["Any OCI client"]

    style Pull fill:#dcfce7,color:#166534
    style OCM fill:#dbeafe,color:#1e40af

This means:

  • Non-OCI blobs (plain files, config data) are stored as layers in the descriptor manifest, accessed only through OCM tooling
  • Native OCI artifacts (images, Helm charts) are stored as separate manifests in the index, accessible both through OCM and directly through any OCI client

Check Your Understanding

Why does the media type matter when embedding an OCI image layout?The media type (application/vnd.ocm.software.oci.layout.v1+tar) tells OCM that the blob contains a valid OCI image layout. During ocm add cv, OCM unpacks the tar, extracts the manifests and layers, and stores them as native OCI objects. The resulting local blob has the native OCI media type (e.g. application/vnd.oci.image.manifest.v1+json). Without the correct media type, OCM would store the tar as an opaque layer that cannot be accessed natively.
What is the difference between localReference and globalAccess?
  • localReference is a content-addressable digest that identifies the blob within the component version’s storage. It is stable across transfers — the same digest works regardless of which registry hosts the component, because it is derived from the blob content itself. It works with any OCM repository implementation (CTF archives, OCI registries). For native OCI artifacts stored in an OCI registry, you can access them directly using this digest combined with the component version’s repository path.
  • globalAccess is an optional, location-specific access specification that points to the artifact in a particular registry. It is not set by default — the globalAccessPolicy must be explicitly configured to enable it. Note that this reference becomes invalid after mirroring — when the component is transferred to a different registry, the globalAccess still points to the original registry. Always use localReference for stable, location-independent access.
How do I enable globalAccess references?

By default, globalAccess is not populated. To opt in, use the two-step transfer workflow with a transfer specification:

  1. Generate the transfer spec:

    ocm transfer cv --dry-run -o yaml --copy-resources \
      ./transport-archive//github.com/acme.org/native-oci-demo:1.0.0 \
      <your-registry> > spec.yaml
  2. Edit spec.yaml and add globalAccessPolicy: auto to each OCIAddLocalResource node’s spec field:

    - type: OCIAddLocalResource/v1alpha1
      spec:
        globalAccessPolicy: auto
        # ... other fields ...
  3. Execute the modified spec:

    ocm transfer cv --transfer-spec spec.yaml

This is an experimental feature carried over from OCM v1 for backwards compatibility. Its future availability is being evaluated by the community.

With globalAccessPolicy: auto, the descriptor looks like this after transfer:

resources:
  - access:
      localReference: sha256:abc123...
      mediaType: application/vnd.oci.image.index.v1+json
      referenceName: stefanprodan/podinfo:6.9.1
      type: LocalBlob/v1
      globalAccess:
        imageReference: <your-registry>/component-descriptors/github.com/acme.org/native-oci-demo@sha256:abc123...
        type: ociArtifact
    name: app-image
    relation: external
    type: ociArtifact
    version: 1.0.0

The globalAccess.imageReference provides a direct pullable reference. Note that this reference may become stale if the component is transferred to another registry using tooling that does not update the globalAccess field.

Without globalAccess, you can still access native OCI artifacts directly using the localReference digest and the component version’s repository path in the registry.

Can I use --upload-as to control how artifacts are stored?

Yes. The ocm transfer cv command supports --upload-as with two values:

  • --upload-as localBlob — stores OCI artifacts as local blobs within the component version (default behavior with --copy-resources)
  • --upload-as ociArtifact — uploads OCI artifacts as standalone OCI artifacts in the target registry, separate from the component version

Both options make the artifact natively accessible in OCI registries, but localBlob keeps the artifact within the component version’s index while ociArtifact stores it independently.

Cleanup

Remove the tutorial artifacts:

rm -rf /tmp/ocm-native-oci /tmp/ocm-transfer-native /tmp/ocm-fetch-oci

Next Steps