iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🪅

Working with OCI Artifacts using ORAS

に公開

What is ORAS

The OCI Registry as Storage (ORAS) is a client tool for handling OCI Artifacts and is a CNCF Sandbox project.

https://oras.land/

Just as you can push/pull container images between a local environment and a remote container registry using the Docker CLI, ORAS is a client tool (CLI) designed to push/pull OCI artifacts between a local environment and a remote registry.

While the documentation includes explanations of OCI artifacts and several examples of ORAS commands, it can be difficult to understand how to use it just by reading if you aren't familiar with OCI artifacts. Therefore, here we will explore what you can do with ORAS while mixing in an explanation of OCI artifacts.
This also serves as a memo for myself, as I am still getting used to OCI artifacts.

OCI Artifact and OCI Artifact Registry

A brief summary of what's written in the ORAS Introduction.
The Introduction briefly describes the necessity and background of OCI artifacts.

Before the OCI artifact specification was finalized, you could include arbitrary files—such as PDFs, text, or binaries—in an image by placing them inside the image via a Dockerfile and pushing that image to a container registry, regardless of whether they were related to the image itself (you can still do this).
However, this method was hardly considered a legitimate approach and had various problems, such as difficulty in managing the files themselves and bloated image sizes due to the inclusion of unnecessary files. Therefore, a specification was established to allow sharing files and other data in a more formal way according to specific guidelines. This specification, defined by the Open Container Initiative (OCI), is called the OCI Image Manifest Specification. A collection of files and metadata managed in compliance with the OCI image manifest specification is called an OCI Artifact. Furthermore, a container registry configured to manage OCI manifests is called an OCI Artifact Registry. While there are already other tools that can handle OCI artifacts, ORAS was developed with a focus on OCI artifacts to make managing them easier.

What ORAS does differently is shift the focus from container images to other types of artifacts.

As shown in the examples in Registries supporting OCI Artifacts, major cloud providers and OSS container registries currently support OCI artifacts. For now, it's fine to understand that "OCI Artifact Registry refers to a container registry extended to manage OCI artifacts."

Investigating the OCI Layout of Docker Images

Before handling OCI artifacts with ORAS, let's take a brief look at the structure of OCI artifacts. There is a diagram illustrating the structure of OCI artifacts in Understanding OCI artifacts, which is cited below.


Structure of an OCI Artifact (Cited from Understanding OCI artifacts). It consists of a nested structure of index, manifest, and blob.

An OCI artifact has a nested configuration of Index, Manifest, and Blob as shown above. The Index and Manifest are metadata in JSON format that describe what kind of artifacts are included, and the Blobs are the actual files stored within the artifact.
However, since it's hard to get a concrete image from just this, let's look at the actual structure of an OCI artifact using the nginx image on Docker Hub as an example.

Since the nginx image supports multiple architectures such as amd64, arm, and s390, inspecting the manifest with docker manifest inspect nginx first retrieves information containing a manifest list for multiple architectures. This corresponds to the Index in an OCI artifact, where individual manifests for each architecture are listed under manifests[].

  • mediaType: The manifest format. Since the manifest itself is in JSON format, it is denoted as +json. A list of available values can be found in the OCI Image Media Types.
  • digest: The digest of the manifest.
  • size: The manifest size (bytes).
  • platform: OS and architecture information.
Result of `docker manifest inspect nginx`
index
schemaVersion: 2
mediaType: application/vnd.oci.image.index.v1+json
manifests:
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2295
    digest: sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
    platform:
      architecture: amd64
      os: linux
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:2b7d6ce2be05018291aaa1979ccf1e744fa7082eea89142faac3dd067607875e
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2297
    digest: sha256:6289301f3b918b98577fe0f80c13761aae5791b0059ed9e1d554edeae7c54b82
    platform:
      architecture: arm
      os: linux
      variant: v5
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:3e576b43defa219d279961a042477c8df1b2e46d15ec8cd98827eae750e33fa6
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2297
    digest: sha256:4bcb64ff3494e929c732a97eacef3fddc8ab27347f23e12d7e6ffc2e2cecb0a2
    platform:
      architecture: arm
      os: linux
      variant: v7
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:1fb32bf81384775941104a4d95d6303586ff0699b31182de738c2157b211ef1b
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2297
    digest: sha256:bab0713884fed8a137ba5bd2d67d218c6192bd79b5a3526d3eb15567e035eb55
    platform:
      architecture: arm64
      os: linux
      variant: v8
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:9b1e294be5c53a9edbb775540981332a842602564ab184cf6c775f9a2b0f7688
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2294
    digest: sha256:09279664808b1f74e3698a901d94d9640f11af5986b85c3b7ca7944a7dded1be
    platform:
      architecture: "386"
      os: linux
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:a46d7c4bd192f2e0ee360619ea657b141a5667d9daee98d6ff5accc193a868e2
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2298
    digest: sha256:41852102bec70d119236a95437f8a98e11d8404e50d3b2eaf6942d725f037df7
    platform:
      architecture: mips64le
      os: linux
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 567
    digest: sha256:f2917769e890c8c9faf9c22ef264fec8d5a0daf27b4777fb7f2fba7e35d0173c
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2297
    digest: sha256:822ae767e1045bf2818edf6929c8b1fb89687b9a8d33e809287842ed443b77d7
    platform:
      architecture: ppc64le
      os: linux
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:0ccc36a3515f0d50aa41079d38c75ead02799c48caa58a4117968c9a74a46e69
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2295
    digest: sha256:612962b8456a7aff0fe6b18d7313bb90e44b7b34aced6ce432543884886cc3a4
    platform:
      architecture: s390x
      os: linux
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:cff2127d747263db959a262b1fe407cee4b1881946cb6dd0287e35739e36ead4
    platform:
      architecture: unknown
      os: unknown

By adding -v when executing the command, you can also obtain the content of the manifest for each architecture at the same time.
For example, in the following content, the section under OCIManifest is the manifest for the amd64 architecture, which corresponds to Manifest in the diagram.

Part of the result of `docker manifest inspect nginx -v`
- Ref: docker.io/library/nginx:latest@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
  Descriptor:
    mediaType: application/vnd.oci.image.manifest.v1+json
    digest: sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
    size: 2295
    platform:
      architecture: amd64
      os: linux
  Raw: ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1Njo1ZWY3OTE0OWUwZWM4NGE3YTlmOTI4NGMzZjkxYWEzYzIwNjA4ZjgzOTFmNTQ0NWVhYmU5MmVmMDdkYmRhMDNjIiwKICAgICJzaXplIjogNzQ4NgogIH0sCiAgImxheWVycyI6IFsKICAgIHsKICAgICAgIm1lZGlhVHlwZSI6ICJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLmxheWVyLnYxLnRhcitnemlwIiwKICAgICAgImRpZ2VzdCI6ICJzaGEyNTY6ZTRmZmYwNzc5ZTZkZGQyMjM2NjQ2OWYwODYyNmMzYWIxODg0YjVjYmUxNzE5YjI2ZGEyMzhjOTVmMjQ3YjMwNSIsCiAgICAgICJzaXplIjogMjkxMjYyMzIKICAgIH0sCiAgICB7CiAgICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5sYXllci52MS50YXIrZ3ppcCIsCiAgICAgICJkaWdlc3QiOiAic2hhMjU2OjJhMGNiMjc4ZmQ5Zjc3MzdlZjVkZGM1MmI0MTk4ODIxZGQwMmU4N2VkMjA0Zjc0ZDdlNDkxMDE2Yjk2ZWJlN2YiLAogICAgICAic2l6ZSI6IDQxODc1NzgyCiAgICB9LAogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1Njo3MDQ1ZDZjMzJhZTJkM2RjMDAyZjMzYmViMGMxY2RkN2Y2OWIyNjYzYTk3MjAxMTdhYzliODJlYzI4ODY1ZTMwIiwKICAgICAgInNpemUiOiA2MjcKICAgIH0sCiAgICB7CiAgICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5sYXllci52MS50YXIrZ3ppcCIsCiAgICAgICJkaWdlc3QiOiAic2hhMjU2OjAzZGUzMWFmYjAzNTczZTBmYTY3OWQ2Nzc3YmEzMjY3YzJiOGVjMDg3Y2JjMGVmYTQ2NTI0YzFkZTA4ZjQzZWMiLAogICAgICAic2l6ZSI6IDk1NgogICAgfSwKICAgIHsKICAgICAgIm1lZGlhVHlwZSI6ICJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLmxheWVyLnYxLnRhcitnemlwIiwKICAgICAgImRpZ2VzdCI6ICJzaGEyNTY6MGYxN2JlOGRjZmYyZTJjMjdlZTZhMzNjMWJhY2M1ODJlNzFmNzZmODU1YzJkNjlkNTEwZjJhOTNkZjg5NzMwMyIsCiAgICAgICJzaXplIjogMzk0CiAgICB9LAogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxNGI3ZTVlOGYzOTQ2ZGEwZjkxMjBkYWIzYjBlMDVlZjI0YTVjYTg3NGJhNDg0MzI3ZGI4YjMzMDhhOTJiNTMyIiwKICAgICAgInNpemUiOiAxMjEwCiAgICB9LAogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoyM2ZhNWE3Yjk5YTY4NTI1ODg4NTkxOGM0NjhkZWQwNDJiOTViNWE3YzU2Y2VlNzU4YTY4OWY0ZjdlNTk3MWUwIiwKICAgICAgInNpemUiOiAxMzk4CiAgICB9CiAgXSwKICAiYW5ub3RhdGlvbnMiOiB7CiAgICAiY29tLmRvY2tlci5vZmZpY2lhbC1pbWFnZXMuYmFzaGJyZXcuYXJjaCI6ICJhbWQ2NCIsCiAgICAib3JnLm9wZW5jb250YWluZXJzLmltYWdlLmJhc2UuZGlnZXN0IjogInNoYTI1Njo3MGQ0YzA0MzAyYmRjZDcxYzRmYTIxYjZjMTJlOTk4ODgzODBhMDdmMDRlM2Q0NDQ1MmI5NjFiY2EwNDY0ODlkIiwKICAgICJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYmFzZS5uYW1lIjogImRlYmlhbjpib29rd29ybS1zbGltIiwKICAgICJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuY3JlYXRlZCI6ICIyMDI0LTA4LTE0VDIxOjMxOjEyWiIsCiAgICAib3JnLm9wZW5jb250YWluZXJzLmltYWdlLnJldmlzaW9uIjogImU3OGNmNzBjZTdiNzNhMGM5ZWE3MzRjOWNmOGFhYWEyODNjMWNjNWEiLAogICAgIm9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5zb3VyY2UiOiAiaHR0cHM6Ly9naXRodWIuY29tL25naW54aW5jL2RvY2tlci1uZ2lueC5naXQjZTc4Y2Y3MGNlN2I3M2EwYzllYTczNGM5Y2Y4YWFhYTI4M2MxY2M1YTptYWlubGluZS9kZWJpYW4iLAogICAgIm9yZy5vcGVuY29udGFpbmVycy5pbWFnZS51cmwiOiAiaHR0cHM6Ly9odWIuZG9ja2VyLmNvbS9fL25naW54IiwKICAgICJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVyc2lvbiI6ICIxLjI3LjEiCiAgfQp9
  OCIManifest:
    schemaVersion: 2
    mediaType: application/vnd.oci.image.manifest.v1+json
    config:
      mediaType: application/vnd.oci.image.config.v1+json
      digest: sha256:5ef79149e0ec84a7a9f9284c3f91aa3c20608f8391f5445eabe92ef07dbda03c
      size: 7486
    layers:
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:e4fff0779e6ddd22366469f08626c3ab1884b5cbe1719b26da238c95f247b305
        size: 29126232
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:2a0cb278fd9f7737ef5ddc52b4198821dd02e87ed204f74d7e491016b96ebe7f
        size: 41875782
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:7045d6c32ae2d3dc002f33beb0c1cdd7f69b2663a9720117ac9b82ec28865e30
        size: 627
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:03de31afb03573e0fa679d6777ba3267c2b8ec087cbc0efa46524c1de08f43ec
        size: 956
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:0f17be8dcff2e2c27ee6a33c1bacc582e71f76f855c2d69d510f2a93df897303
        size: 394
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:14b7e5e8f3946da0f9120dab3b0e05ef24a5ca874ba484327db8b3308a92b532
        size: 1210
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:23fa5a7b99a685258885918c468ded042b95b5a7c56cee758a689f4f7e5971e0
        size: 1398
    annotations:
      com.docker.official-images.bashbrew.arch: amd64
      org.opencontainers.image.base.digest: sha256:70d4c04302bdcd71c4fa21b6c12e99888380a07f04e3d44452b961bca046489d
      org.opencontainers.image.base.name: debian:bookworm-slim
      org.opencontainers.image.created: "2024-08-14T21:31:12Z"
      org.opencontainers.image.revision: e78cf70ce7b73a0c9ea734c9cf8aaaa283c1cc5a
      org.opencontainers.image.source: https://github.com/nginxinc/docker-nginx.git#e78cf70ce7b73a0c9ea734c9cf8aaaa283c1cc5a:mainline/debian
      org.opencontainers.image.url: https://hub.docker.com/_/nginx
      org.opencontainers.image.version: 1.27.1
...

Next, to examine the structure below the manifest, pull the image on an amd64 PC and extract it locally.

# Download the image
$ docker pull nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
docker.io/library/nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f: Pulling from library/nginx
e4fff0779e6d: Already exists
2a0cb278fd9f: Pull complete
7045d6c32ae2: Pull complete
03de31afb035: Pull complete
0f17be8dcff2: Pull complete
14b7e5e8f394: Pull complete
23fa5a7b99a6: Pull complete
Digest: sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
Status: Downloaded newer image for nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
docker.io/library/nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f


# Output the image to nginx.tar
$ docker save nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f > nginx.tar

# Extract
$ mkdir test
$ tar xvf nginx.tar -C test

Looking at the files extracted in the test directory, you can see that files such as the blobs directory and oci-layout exist.
This directory structure containing multiple blobs and various JSON formats is called OCI Layout or OCI Image Layout. The criteria that oci-layout must meet and descriptions of each file are defined in the OCI Image Layout Specification.

Execution result of tree
.
├── blobs
│   └── sha256
│       ├── 228a13938b2b1dd85f37d629df6e0cff088dd83b31bc9e9b7f19bdc2a8bc8e2d
│       ├── 23f842574fa2f03da3f7ac8d8f3c6d94c66d76cc00fc982dd9470c83f0049fa2
│       ├── 55e54df86207fa772302a6fc1e78eb60bd7e3ebd4f913ef7f5ad668ad69ab64d
│       ├── 5ba9eb7201206bb0a7bef734333205a60a5466b0badf49d2a8841448c44d939b
│       ├── 5ef79149e0ec84a7a9f9284c3f91aa3c20608f8391f5445eabe92ef07dbda03c
│       ├── 5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a
│       ├── 72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
│       ├── 7d2ee22b1699d37a35181e47d42a275803f3acf547c22a41bb8a53891c7b4f2c
│       ├── 8b87c0c6652495401acfbe029ede84a5f327664770561ba5f8b7fe9149f52dd0
│       ├── 97cfa79367882fde35f0089339609799c3e98fb79d7a4b241d1c2b6ed58b7d56
│       ├── 9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
│       ├── c20af9497622696cbac2919bd19bcf67672d012b74cc0408acb178b688ac8f0d
│       ├── dd8a2508f00668508e4fe4bd8030fec6e4262103a239404daa8386f1c6537797
│       ├── ec1a2ca4ac8784def146544fc7068db06a188d2da4fd7c4e134a76415b8bc1a8
│       ├── f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
│       └── f5e50abe9b0f6d2fe7532dc473c0639d9f1729455dd2c9b3bce575f705d20e80
├── index.json
├── manifest.json
└── oci-layout

index.json contains the index content. It is defined in the OCI Image Index Specification.

index.json
schemaVersion: 2
mediaType: application/vnd.oci.image.index.v1+json
manifests:
  - mediaType: application/vnd.oci.image.manifest.v1+json
    digest: sha256:7d2ee22b1699d37a35181e47d42a275803f3acf547c22a41bb8a53891c7b4f2c
    size: 1307

manifest.json describes what information is stored in this amd64 manifest, and LayerSources contains information such as the size of each blob. In the context of a Dockerfile, the topic of layers often comes up, and these correspond to the Layers below.

manifest.json
- Config: blobs/sha256/5ef79149e0ec84a7a9f9284c3f91aa3c20608f8391f5445eabe92ef07dbda03c
  RepoTags: null
  Layers:
    - blobs/sha256/9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
    - blobs/sha256/72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
    - blobs/sha256/8b87c0c6652495401acfbe029ede84a5f327664770561ba5f8b7fe9149f52dd0
    - blobs/sha256/ec1a2ca4ac8784def146544fc7068db06a188d2da4fd7c4e134a76415b8bc1a8
    - blobs/sha256/55e54df86207fa772302a6fc1e78eb60bd7e3ebd4f913ef7f5ad668ad69ab64d
    - blobs/sha256/f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
    - blobs/sha256/5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a
  LayerSources:
    sha256:55e54df86207fa772302a6fc1e78eb60bd7e3ebd4f913ef7f5ad668ad69ab64d:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 2560
      digest: sha256:55e54df86207fa772302a6fc1e78eb60bd7e3ebd4f913ef7f5ad668ad69ab64d
    sha256:5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 7168
      digest: sha256:5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a
    sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 113963520
      digest: sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
    sha256:8b87c0c6652495401acfbe029ede84a5f327664770561ba5f8b7fe9149f52dd0:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 3584
      digest: sha256:8b87c0c6652495401acfbe029ede84a5f327664770561ba5f8b7fe9149f52dd0
    sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 77832704
      digest: sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
    sha256:ec1a2ca4ac8784def146544fc7068db06a188d2da4fd7c4e134a76415b8bc1a8:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 4608
      digest: sha256:ec1a2ca4ac8784def146544fc7068db06a188d2da4fd7c4e134a76415b8bc1a8
    sha256:f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 5120
      digest: sha256:f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344

Content of Blobs

Each digest in the blobs directory corresponds to the actual files and directories of the layers. The source code for the nginx Debian-based Dockerfile we are looking at can be found here:
https://github.com/nginxinc/docker-nginx/blob/master/mainline/debian/Dockerfile

In manifest.json, there are 7 Layers and LayerSources, and since the Dockerfile has a total of 6 RUN and COPY instructions, we can see that each one corresponds to them (the remaining one corresponds to the base image, debian:bookworm-slim).

For example, in the following part of the Dockerfile, each shell script is copied under /docker-entrypoint.d.

Dockerfile
COPY 20-envsubst-on-templates.sh /docker-entrypoint.d
COPY 30-tune-worker-processes.sh /docker-entrypoint.d

This process creates the 5th and 6th layers, so let's look at their contents.
Check the layer digests with docker image inspect nginx@[digest].

$ docker image inspect nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f | yq -P
- Id: sha256:5ef79149e0ec84a7a9f9284c3f91aa3c20608f8391f5445eabe92ef07dbda03c
  ...
  RootFS:
    Type: layers
    Layers:
      - sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
      - sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
      - sha256:8b87c0c6652495401acfbe029ede84a5f327664770561ba5f8b7fe9149f52dd0
      - sha256:ec1a2ca4ac8784def146544fc7068db06a188d2da4fd7c4e134a76415b8bc1a8
      - sha256:55e54df86207fa772302a6fc1e78eb60bd7e3ebd4f913ef7f5ad668ad69ab64d
      - sha256:f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
      - sha256:5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a

Since sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034 is the layer for the base image Debian, excluding it shows that the digest for the 5th layer is sha256:f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344.
Files included in the blob directory are in tar format. If you check them with cat or similar commands, you can confirm the contents of 20-envsubst-on-templates.sh, which is actually placed inside the container.

f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
$ file f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344: POSIX tar archive

$ cat f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
docker-entrypoint.d/0000755000000000000000000000000014657440731013161 5ustar0000000000000000docker-entrypoint.d/20-envsubst-on-templates.sh0000755000000000000000000000571214657440704020223 0ustar0000000000000000#!/bin/sh

set -e

ME=$(basename "$0")

entrypoint_log() {
    if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then
        echo "$@"
    fi
...

Similarly, you can check the contents of 30-tune-worker-processes.sh in the 6th layer sha256:5f027....

5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a
$ cat 5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a
docker-entrypoint.d/0000755000000000000000000000000014657440731013161 5ustar0000000000000000docker-entrypoint.d/30-tune-worker-processes.sh0000755000000000000000000001101314657440704020222 0ustar0000000000000000#!/bin/sh
# vim:sw=2:ts=2:sts=2:et

set -eu

LC_ALL=C
ME=$(basename "$0")
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

[ "${NGINX_ENTRYPOINT_WORKER_PROCESSES_AUTOTUNE:-}" ] || exit 0
...

Also, by extracting the tar, you can confirm that the shell scripts are actually placed under /docker-entrypoint.d.

$ mkdir test
$ tar xvf 5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a -C test
docker-entrypoint.d/
docker-entrypoint.d/30-tune-worker-processes.sh

$ tree test
test
└── docker-entrypoint.d
    └── 30-tune-worker-processes.sh

The layer with the largest size in the manifest, 72db5..., might at first glance seem to correspond to the base Debian layer, but since the top of the layers is 98535..., that is the Debian layer, and 72db5... is the layer corresponding to the first RUN in the nginx Dockerfile.

  Layers:
    - blobs/sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
    - blobs/sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
    - ...
  LayerSources:
    sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 113963520
      digest: sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
    sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 77832704
      digest: sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
    sha256:ec1a2ca4ac87

Looking at the Dockerfile, it appears the size increased because the RUN command performs various operations such as installing packages. Also, while there are digests in the blobs directory that do not appear in the manifest's Layers, these are layers associated with the base Debian image.

Pushing and pulling OCI Artifacts with ORAS

Now that we have a better understanding of the internal structure of OCI artifacts by looking at the inner workings of the Docker images we usually use, let's return to the main topic and look at the content of OCI artifacts handled by ORAS.

First, we create an artifact following Quick start or How-to Guides Pushing and Pulling. Since an artifact registry is required to push, pull, and store artifacts, we will start zot using Docker.

docker run -d -p 5000:5000 --name zot ghcr.io/project-zot/zot-linux-amd64:latest

As described in the quickstart, we create a simple artifact containing only a text file named artifact.txt and push it to zot.

$ echo "hello world" > artifact.txt
$ oras push localhost:5000/hello-artifact:v1 \
./artifact.txt
 Exists    application/vnd.oci.empty.v1+json                                                                                                                                                                                                   2/2  B 100.00%     0s
  └─ sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
 Exists    artifact.txt                                                                                                                                                                                                                      12/12  B 100.00%     0s
  └─ sha256:a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
 Uploaded  application/vnd.oci.image.manifest.v1+json                                                                                                                                                                                      591/591  B 100.00%   17ms
  └─ sha256:a91334e84d5371e2a5add8baf8c5052c21effcf352df39e4a44840d84d9c9bee
Pushed [registry] localhost:5000/hello-artifact:v1
ArtifactType: application/vnd.unknown.artifact.v1
Digest: sha256:a91334e84d5371e2a5add8baf8c5052c21effcf352df39e4a44840d84d9c9bee

Next, we retrieve the content of the artifact pushed to zot using the oras manifest fetch localhost:5000/hello-artifact:v1 command.

schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
artifactType: application/vnd.unknown.artifact.v1
config:
  mediaType: application/vnd.oci.empty.v1+json
  digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
  size: 2
  data: e30=
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar
    digest: sha256:a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
    size: 12
    annotations:
      org.opencontainers.image.title: artifact.txt
annotations:
  org.opencontainers.image.created: "2024-08-27T14:33:13Z"

Comparing it with the Docker image manifest (OCIManifest) we checked earlier, we can confirm that the content above corresponds to an image manifest.
Looking at config, it is set to mediaType: application/vnd.oci.empty.v1+json. This follows the Guidance for an Empty Descriptor in the Manifest Specification, confirming that the metadata is empty. The data: e30= is the Base64-encoded version of {}, which consists of two characters, so the size is 2.

$ echo "e30=" | base64 -d
{}

Information about files included in the artifact is added under layers[].
In this case, since we pushed only a single file, artifact.txt, it is included there. As the file size is 12 bytes, the size is 12. It seems ORAS automatically adds the mediaType and annotations based on the attached file or directory.

To retrieve an artifact from an artifact registry, you can download it locally with oras pull localhost:5000/hello-artifact:v1, similar to the Docker CLI. In this example, the attached artifact.txt is retrieved directly.

$ oras pull localhost:5000/hello-artifact:v1
 Pulled      artifact.txt                                                                                                                                                                                                   12/12  B 100.00%  113µs
  └─ sha256:a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
 Pulled      application/vnd.oci.image.manifest.v1+json                                                                                                                                                                   591/591  B 100.00%     0s
  └─ sha256:a91334e84d5371e2a5add8baf8c5052c21effcf352df39e4a44840d84d9c9bee
Pulled [registry] localhost:5000/hello-artifact:v1
Digest: sha256:a91334e84d5371e2a5add8baf8c5052c21effcf352df39e4a44840d84d9c9bee

$ ls
artifact.txt

When attaching multiple files, you can include them in the artifact by setting multiple layers or specifying a directory, as shown in the command examples in Pushing and Pulling.

mkdir docs
echo "Docs on this artifact" > ./docs/readme.md
echo "More content for this artifact" > ./docs/readme2.md
oras push localhost:5000/hello-artifact:v2 \
./docs
 Uploaded  docs                                                                                                                                                                                                                            193/193  B 100.00%    5ms
  └─ sha256:a549a5915fee8fa9eff01724373616df4f0e8663e8c0244a84bdb7d0c9a763ba
 Exists    application/vnd.oci.empty.v1+json                                                                                                                                                                                                   2/2  B 100.00%     0s
  └─ sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
 Uploaded  application/vnd.oci.image.manifest.v1+json                                                                                                                                                                                      730/730  B 100.00%    5ms
  └─ sha256:538eee55d7942b37ca99e563687a6ccd523c67a4657bdf30c600b3dbdacb0cd8
Pushed [registry] localhost:5000/hello-artifact:v2
ArtifactType: application/vnd.unknown.artifact.v1
Digest: sha256:538eee55d7942b37ca99e563687a6ccd523c67a4657bdf30c600b3dbdacb0cd8

When a directory is specified, the mediaType automatically becomes ...tar+gzip. While the documentation says "tar", it seems "gzip" was also appended when I actually tried it.

hello-artifact v2
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
artifactType: application/vnd.unknown.artifact.v1
config:
  mediaType: application/vnd.oci.empty.v1+json
  digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
  size: 2
  data: e30=
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
    digest: sha256:a549a5915fee8fa9eff01724373616df4f0e8663e8c0244a84bdb7d0c9a763ba
    size: 193
    annotations:
      io.deis.oras.content.digest: sha256:c1636d5e6d89031e478fc32fdb0e54a04a0bab70154def36652a46b0a83009b2
      io.deis.oras.content.unpack: "true"
      org.opencontainers.image.title: docs
annotations:
  org.opencontainers.image.created: "2024-08-27T15:34:25Z"

It is also possible to bundle files into tar.gz in your local environment before attaching them.

oras push localhost:5000/hello-artifact:v3 res.tar.gz
 Uploaded  res.tar.gz                                                                                                                                                                                                                      286/286  B 100.00%    2ms
  └─ sha256:bf93d6344cae0c4cab7b0927cb7d4e1ebbc2177d5f88699f752649e390800d6e
 Exists    application/vnd.oci.empty.v1+json                                                                                                                                                                                                   2/2  B 100.00%     0s
  └─ sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
 Uploaded  application/vnd.oci.image.manifest.v1+json                                                                                                                                                                                      590/590  B 100.00%     0s
  └─ sha256:0df32bf3ae06b0c6baae12af3e2dcefb9f087e6c5ec235e9aa71f179f83dc3f6
Pushed [registry] localhost:5000/hello-artifact:v3
ArtifactType: application/vnd.unknown.artifact.v1
Digest: sha256:0df32bf3ae06b0c6baae12af3e2dcefb9f087e6c5ec235e9aa71f179f83dc3f6

In this case, the mediaType becomes application/vnd.oci.image.layer.v1.tar, and the size matches the size of the tar.gz file.

hello-artifact v3
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
artifactType: application/vnd.unknown.artifact.v1
config:
  mediaType: application/vnd.oci.empty.v1+json
  digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
  size: 2
  data: e30=
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar
    digest: sha256:bf93d6344cae0c4cab7b0927cb7d4e1ebbc2177d5f88699f752649e390800d6e
    size: 286
    annotations:
      org.opencontainers.image.title: res.tar.gz
annotations:
  org.opencontainers.image.created: "2024-08-27T15:41:53Z"
$ du -b res.tar.gz
286     res.tar.gz

Adding Annotations

Annotations are optional metadata in Key-Value format. Although several rules and reserved words are defined in the OCI Image Specification, any value can be set as long as these rules are met. In ORAS, they can be set by specifying them in a key-value format when pushing artifacts with oras push, or by providing a file containing the annotations. The Using a JSON File example demonstrates adding annotations to the config, manifest, and layer, respectively.

annotation.json
{
  "$config": {
    "hello": "world"
  },
  "$manifest": {
    "foo": "bar"
  },
  "cake.txt": {
    "fun": "more cream"
  }
}

Specify the file with the --annotation-file flag during push.

oras push --annotation-file annotation.json localhost:5000/hello-artifact:ann

You can confirm that the annotations have been added.

$ oras manifest fetch localhost:5000/hello-artifact:ann | yq -P
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
artifactType: application/vnd.unknown.artifact.v1
config:
  mediaType: application/vnd.oci.empty.v1+json
  digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
  size: 2
  annotations:
    hello: world
  data: e30=
layers:
  - mediaType: application/vnd.oci.empty.v1+json
    digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
    size: 2
    data: e30=
annotations:
  foo: bar
  org.opencontainers.image.created: "2024-08-27T17:29:08Z"

Depending on the registry, for example in Harbor, the annotations set on the artifact are displayed in the overview.

In terms of usage, it seems appropriate to add information related to the artifact, similar to how docker label is used. For example, in the ingress-nginx helm chart artifact, information such as the source code GitHub URL, version, and author is set in the annotations.

$ oras manifest fetch harbor.centre.com/helm/ingress-nginx:4.11.2 | yq -P
schemaVersion: 2
config:
  mediaType: application/vnd.cncf.helm.config.v1+json
  digest: sha256:f1fbe187a6e6a03bc72f5cc77ccdb50cfec0c3d620812b4d4e76784d55ed0f15
  size: 728
layers:
  - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
    digest: sha256:df79d6acb94c4c7c6e06624722a9c131c8cbc9fb5eaa5f3c962e003702aa8ea8
    size: 58090
annotations:
  artifacthub.io/changes: |
    - Update Ingress-Nginx version controller-v1.11.2
  artifacthub.io/prerelease: "false"
  org.opencontainers.image.authors: cpanato, Gacko, puerco, rikatz, strongjz, tao12345666333
  org.opencontainers.image.created: "2024-08-27T00:00:53+09:00"
  org.opencontainers.image.description: Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer
  org.opencontainers.image.source: https://github.com/kubernetes/ingress-nginx
  org.opencontainers.image.title: ingress-nginx
  org.opencontainers.image.url: https://github.com/kubernetes/ingress-nginx
  org.opencontainers.image.version: 4.11.2

Distributing OCI Layouts

This section covers the content of Distributing OCI Layouts.

OCI Image Layout refers to the directory structure that OCI images must follow, as defined in the OCI Image Layout Specification, and it must contain the following files and directories:

  • oci-layout
  • index.json
  • blobs

As we have already seen, the contents of a Docker image exported to a tar file using the docker save command comply with this specification.
ORAS allows you to push the contents of a directory to a registry as an artifact if that directory complies with the oci-layout specification locally. Let's create an oci-layout artifact using docker buildx following the documentation.

Dockerfile
FROM alpine
CMD echo 'hello world!'

Build

$ docker buildx build . -f Dockerfile -o type=oci,dest=hello-world.tar -t hello-world:v4

This creates hello-world.tar in your local environment, so push it to the registry as hello-artifact:v4.

oras cp --from-oci-layout ./hello-world.tar localhost:5000/hello-artifact:v4

Fetch the manifest using oras manifest fetch localhost:5000/hello-artifact:v4.

hello-artifact v4
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
config:
  mediaType: application/vnd.oci.image.config.v1+json
  digest: sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
  size: 794
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
    digest: sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
    size: 3622892

Comparing this with the manifest of the artifact created earlier by adding a text file directly with oras push, we can see that the config size is 794, indicating that it contains some content. You can check the contents of the config using oras manifest fetch-config localhost:5000/hello-artifact:v4. You will see that it contains metadata such as architecture information (which is amd64 since it was built on an amd64 machine), commands within the image, and history.

hello-artifact v4 config
architecture: amd64
config:
  Env:
    - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  Cmd:
    - /bin/sh
    - -c
    - echo 'hello world!'
  ArgsEscaped: true
created: "2024-07-22T22:26:43.778747613Z"
history:
  - created: "2024-07-22T22:26:43.622487881Z"
    created_by: '/bin/sh -c #(nop) ADD file:99093095d62d0421541d882f9ceeddb2981fe701ec0aa9d2c08480712d5fed21 in / '
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: '/bin/sh -c #(nop)  CMD ["/bin/sh"]'
    empty_layer: true
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: CMD ["/bin/sh" "-c" "echo 'hello world!'"]
    comment: buildkit.dockerfile.v0
    empty_layer: true
os: linux
rootfs:
  type: layers
  diff_ids:
    - sha256:78561cef0761903dd2f7d09856150a6d4fb48967a8f113f3e33d79effbf59a07

This content corresponds to one of the blobs included in the original hello-world.tar.

.
├── blobs
│   └── sha256
│       ├── 31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
│       ├── 3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce
│       └── c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
├── index.json
└── oci-layout
31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
architecture: amd64
config:
  Env:
    - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  Cmd:
    - /bin/sh
    - -c
    - echo 'hello world!'
  ArgsEscaped: true
created: "2024-07-22T22:26:43.778747613Z"
history:
  - created: "2024-07-22T22:26:43.622487881Z"
    created_by: '/bin/sh -c #(nop) ADD file:99093095d62d0421541d882f9ceeddb2981fe701ec0aa9d2c08480712d5fed21 in / '
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: '/bin/sh -c #(nop)  CMD ["/bin/sh"]'
    empty_layer: true
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: CMD ["/bin/sh" "-c" "echo 'hello world!'"]
    comment: buildkit.dockerfile.v0
    empty_layer: true
os: linux
rootfs:
  type: layers
  diff_ids:
    - sha256:78561cef0761903dd2f7d09856150a6d4fb48967a8f113f3e33d79effbf59a07

The remaining blobs are 3aee..., which corresponds to the actual manifest JSON, and c6a83fe..., which corresponds to the Alpine layer specified as the base image.

3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
config:
  mediaType: application/vnd.oci.image.config.v1+json
  digest: sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
  size: 794
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
    digest: sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
    size: 3622892

ORAS Subcommands

ORAS discover

https://oras.land/docs/commands/oras_discover/

Although it is still in preview, the oras discover command allows you to retrieve the referrers of a manifest.
A referrer refers to a reference (like a pointer) used to identify images or artifacts, as explained in Understanding References in the Open Container Initiative (OCI) Standard.
In Docker, you can pull the image corresponding to the latest tag on Docker Hub using docker pull nginx. If you write the omitted parts explicitly, it becomes docker.io/library/nginx@[digest], where the image is uniquely identified by specifying the registry, repository, and digest before pulling.
While using a digest uniquely identifies the target artifact, tags are more commonly used because digests are long and difficult for humans to remember. Referrers are used to obtain the reference from a tag to a digest.
For example, the Zot registry used in the ORAS tutorials implements the referrer API, so specifying the v4 tag allows you to retrieve a reference that includes the corresponding artifact's digest.

$ oras discover localhost:5000/hello-artifact:v4
localhost:5000/hello-artifact@sha256:3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce

On the other hand, whether referrers can be used depends on whether the registry storing the artifact supports the referrer feature. For example, the container image registry Harbor supports OCI artifacts, so you can push/pull artifacts, but the referrer API has not been implemented yet, so attempting to retrieve it results in an error.

$ oras discover harbor.centre.com/helm/oras-test:latest
Error response from registry: unauthorized: un-recognized request: GET /v2/helm/oras-test/referrers/sha256:ac88e565f0f104bdd7189eaf658bc9973a6192f430a6f551635f7ef5d2fa7b2a: un-recognized request: GET /v2/helm/oras-test/referrers/sha256:ac88e565f0f104bdd7189eaf658bc9973a6192f430a6f551635f7ef5d2fa7b2a

ORAS blob

https://oras.land/docs/commands/oras_blob_fetch

The OCI layout of the Alpine base image we created earlier contains three blobs. The oras blob command allows you to directly retrieve the contents of these blobs.

Since you can get the digest for each blob using the oras manifest command, if you know the name and tag of the artifact stored in the registry, you can find the blob digests even without knowing them beforehand.
First, fetch the artifact manifest with oras manifest fetch localhost:5000/hello-artifact:v4.

manifest
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
config:
  mediaType: application/vnd.oci.image.config.v1+json
  digest: sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
  size: 794
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
    digest: sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
    size: 3622892

From this result, we can identify that the digest for the config is 31c.. and one of the layers is c6a.... Also, the digest corresponding to the image tag v4 can be retrieved with oras discover.

$ oras discover localhost:5000/hello-artifact:v4
localhost:5000/hello-artifact@sha256:3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce

Therefore, we can see that the following three blobs exist for the artifact localhost:5000/hello-artifact:v4.

Content digest
Config JSON sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
Layer sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
Index sha256:3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce

On the other hand, the blobs directory we extracted locally earlier is as follows, matching the above. Thus, you can check which blobs are included even without knowing the actual contents of the oci-layout.

Result of tree
.
├── blobs
   └── sha256
       ├── 31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
       ├── 3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce
       └── c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6

To fetch the content of a blob, specify it using oras blob -o - fetch [digest].
The -o option specifies the output destination, and -o - displays it to standard output.
Let's try fetching the blob corresponding to the config.

$ oras blob -o - fetch localhost:5000/hello-artifact@sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce | yq -P
architecture: amd64
config:
  Env:
    - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  Cmd:
    - /bin/sh
    - -c
    - echo 'hello world!'
  ArgsEscaped: true
created: "2024-07-22T22:26:43.778747613Z"
history:
  - created: "2024-07-22T22:26:43.622487881Z"
    created_by: '/bin/sh -c #(nop) ADD file:99093095d62d0421541d882f9ceeddb2981fe701ec0aa9d2c08480712d5fed21 in / '
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: '/bin/sh -c #(nop)  CMD ["/bin/sh"]'
    empty_layer: true
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: CMD ["/bin/sh" "-c" "echo 'hello world!'"]
    comment: buildkit.dockerfile.v0
    empty_layer: true
os: linux
rootfs:
  type: layers
  diff_ids:
    - sha256:78561cef0761903dd2f7d09856150a6d4fb48967a8f113f3e33d79effbf59a07

This content is identical to the result of oras manifest fetch-config localhost:5000/hello-artifact:v4. Since the actual config is a JSON file stored within the blob, you can confirm it by fetching the blob as well.
The blob for the layers corresponds to the base Alpine image filesystem (tar). This cannot be displayed in standard output, so you can check it by outputting it to a file and extracting it.

$ oras blob fetch localhost:5000/hello-artifact@sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6 -o output.tar.gz
 Downloaded  application/octet-stream                                                                                                                                                                                                    3.46/3.46 MB 100.00%     0s
  └─ sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
$ tar zxf output.tar.gz -C output
$ tree -L 1 output
output
├── bin
├── dev
├── etc
├── home
├── lib
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var

$ cat output/etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.20.2
PRETTY_NAME="Alpine Linux v3.20"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"

The actual blob for the final index digest 3ae... is the manifest JSON, so it has the same content as oras manifest fetch localhost:5000/hello-artifact:v4.

$ oras blob -o - fetch localhost:5000/hello-artifact@sha256:3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce | yq -P
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
config:
  mediaType: application/vnd.oci.image.config.v1+json
  digest: sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
  size: 794
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
    digest: sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
    size: 3622892

In this way, the oras blob command allows you to retrieve or download specific blobs contained within an artifact. However, it is unclear how often one would have the opportunity to view BLOBs directly using this command.

Projects using ORAS

Projects using ORAS

Helm

Helm has supported OCI functionality since v3.8.0, allowing you to push/pull Helm charts to/from OCI artifact registries.

https://v3.helm.sh/docs/topics/registries/

While ORAS isn't explicitly mentioned on this page, a GitHub search reveals that the ORAS Go library, oras-go, is being used under the hood.

Also, since a Helm chart itself is an OCI artifact, you can push/pull artifacts and retrieve information just as before. Let's try pushing the NGINX Ingress Controller Helm chart to a locally established Harbor registry and examine the artifact.

helm repo add nginx https://kubernetes.github.io/ingress-nginx
helm pull nginx/ingress-nginx

helm registry login harbor.centre.com
helm push ingress-nginx-4.11.2.tgz oci://harbor.centre.com/helm/helm-nginx

You can retrieve the chart manifest using oras manifest fetch. In a Helm chart, config.mediaType is set specifically for Helm, and details of the chart are included in the annotations.

manifest
$ oras manifest fetch harbor.centre.com/helm/ingress-nginx:4.11.2 | yq -P
schemaVersion: 2
config:
  mediaType: application/vnd.cncf.helm.config.v1+json
  digest: sha256:f1fbe187a6e6a03bc72f5cc77ccdb50cfec0c3d620812b4d4e76784d55ed0f15
  size: 728
layers:
  - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
    digest: sha256:df79d6acb94c4c7c6e06624722a9c131c8cbc9fb5eaa5f3c962e003702aa8ea8
    size: 58090
annotations:
  artifacthub.io/changes: |
    - Update Ingress-Nginx version controller-v1.11.2
  artifacthub.io/prerelease: "false"
  org.opencontainers.image.authors: cpanato, Gacko, puerco, rikatz, strongjz, tao12345666333
  org.opencontainers.image.created: "2024-08-27T00:00:53+09:00"
  org.opencontainers.image.description: Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer
  org.opencontainers.image.source: https://github.com/kubernetes/ingress-nginx
  org.opencontainers.image.title: ingress-nginx
  org.opencontainers.image.url: https://github.com/kubernetes/ingress-nginx
  org.opencontainers.image.version: 4.11.2

The contents of the config can be retrieved with oras manifest fetch-config.

config content
$ oras manifest fetch-config harbor.centre.com/helm/ingress-nginx:4.11.2 | yq -P
name: ingress-nginx
home: https://github.com/kubernetes/ingress-nginx
sources:
  - https://github.com/kubernetes/ingress-nginx
version: 4.11.2
description: Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer
keywords:
  - ingress
  - nginx
maintainers:
  - name: cpanato
  - name: Gacko
  - name: puerco
  - name: rikatz
  - name: strongjz
  - name: tao12345666333
icon: https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Nginx_logo.svg/500px-Nginx_logo.svg.png
apiVersion: v2
appVersion: 1.11.2
annotations:
  artifacthub.io/changes: |
    - Update Ingress-Nginx version controller-v1.11.2
  artifacthub.io/prerelease: "false"
kubeVersion: '>=1.21.0-0'

The artifact corresponding to the chart can be obtained using oras pull or oras blob fetch -o [output] [chart]:[layer digest].

$ oras blob -o chart.tar.gz fetch harbor.centre.com/helm/ingress-nginx:4.11.2@sha256:df79d6acb94c4c7c6e06624722a9c131c8cbc9fb5eaa5f3c962e003702aa8ea8
 Downloaded  application/octet-stream                                                                                                                                                                             56.7/56.7 kB 100.00%  421µs
  └─ sha256:df79d6acb94c4c7c6e06624722a9c131c8cbc9fb5eaa5f3c962e003702aa8ea8

The content matches the chart content on GitHub.

$ mkdir ingress-nginx
$ tar xvf -C ingress-nginx
$ tree ingress-nginx -L 2
ingress-nginx
└── ingress-nginx
    ├── Chart.yaml
    ├── OWNERS
    ├── README.md
    ├── README.md.gotmpl
    ├── changelog
    ├── ci
    ├── templates
    ├── tests
    └── values.yaml

Artifact Hub, which is frequently used for managing Helm charts, also provides examples of pushing OCI artifacts with the ORAS CLI.

https://artifacthub.io/docs/topics/repositories/helm-charts/#oci-support

Links

Discussion