iTranslated by AI
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.
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`
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.
.
├── 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.
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.
- 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:
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.
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.
$ 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....
$ 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.
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.
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.
{
"$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.
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.
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.
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
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.
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
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
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.
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.
.
├── 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
Helm
Helm has supported OCI functionality since v3.8.0, allowing you to push/pull Helm charts to/from OCI artifact 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.
$ 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.
$ 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.
Links
- OCI Image Index Specification
- OCI Image Manifest Specification
- OCI Image Layout Specification
- OCI Distribution Specification
-
OCI Image Media Types
- List of values specified for
mediaTypein OCI manifests
- List of values specified for
Discussion