Skip to content
SRE & DevOpsDeep Dive Published Updated 8 min readViews unavailable

OCI Registry Blob Uploads: Chunking, Resume, and Digest Verification

Resume OCI blob uploads with exact registry HTTP semantics, safe recovery, digest and size checks, optional mounts, and capability limits your CI must test.

An OCI image build can contain blobs large enough that restarting a transfer from byte zero wastes substantial time and registry bandwidth. The Distribution Specification defines a resumable upload protocol, but its request sequence is a state machine, not a generic “retry the upload” switch. Correct clients track the registry-issued upload location, byte offsets, complete-blob digest, and the final session-closing request.

This guide pins protocol claims to the released OCI Distribution Specification v1.1.1 and descriptor claims to OCI Image Specification v1.1.1. The Distribution Specification’s release page identifies v1.1.1 as its latest release as of this review. Registry products can differ in which optional upload paths they support, so validate behavior against the exact service and version used by CI rather than extrapolating from one successful push.

Treat the digest and byte count as one upload contract

An OCI blob is addressed by a digest of its content. A descriptor also records the content’s byte size. For an image layer, the upload is the layer blob representation referenced by the manifest. If a build compresses the layer, hash and count the compressed bytes that will actually be sent, not the uncompressed directory tree or a different intermediate archive.

The following Python 3 snippet streams a file once to calculate both SHA-256 and its byte count without loading a large layer into memory:

from pathlib import Path
import hashlib

path = Path("layer.tar")
digest = hashlib.sha256()
size = 0

with path.open("rb") as stream:
    for chunk in iter(lambda: stream.read(1024 * 1024), b""):
        digest.update(chunk)
        size += len(chunk)

print(f"sha256:{digest.hexdigest()} {size}")

Use the resulting digest and size consistently in the upload and the manifest descriptor. Do not mutate or replace the staged file after hashing; otherwise the bytes sent may no longer match the expected digest. A client can also verify the final stored blob by retrieving it and recalculating the digest, especially when it is consuming content through an untrusted path.

Digest verification establishes that bytes match an identifier. It does not prove who produced them, whether they are safe, or whether a release is approved. Keep signature, provenance, SBOM, and admission-policy verification as separate controls.

The monolithic path is the compatibility baseline

The specification defines two monolithic choices. The more explicit path first creates an upload session:

POST /v2/team/api/blobs/uploads/

HTTP/1.1 202 Accepted
Location: https://registry.example/v2/team/api/blobs/uploads/<session-id>

The client then sends the entire blob to the returned upload location, supplying the required digest query parameter, Content-Length, and Content-Type: application/octet-stream. A successful final request returns 201 Created and a Location from which the blob can be pulled. The registry-issued upload location can be relative or absolute and can contain important query parameters. Preserve the latest location as returned; do not rebuild its path or discard its query string by hand.

Some registries may also support a single POST carrying the whole body and a digest. This is optional. If that request receives 202 Accepted with an upload location, the client should continue through the POST-then-PUT flow instead of assuming the blob was committed. In either monolithic form, the submitted length and digest must match the complete blob.

For a small config blob, a monolithic transfer is straightforward. For a multi-gigabyte layer over an unreliable link, chunks allow the client to continue from a server-reported offset. Chunking does not change the digest: the final digest always covers the entire blob, not an individual part.

Follow the chunked upload state machine

Chunked upload has three phases: create a session with POST, transfer ordered chunks with PATCH, and close the session with PUT. The initial POST for this flow must have Content-Length: 0. If the response supplies OCI-Chunk-Min-Length, each non-final chunk should be at least that many bytes. The final chunk may be shorter.

For a 10,000-byte blob and a 4,096-byte chunk size, the byte ranges are inclusive:

Chunk Content-Range Content-Length Expected acknowledged range
1 0-4095 4,096 0-4095
2 4096-8191 4,096 0-8191
3 8192-9999 1,808 0-9999

A first chunk request therefore has this shape:

PATCH <latest-upload-location>
Content-Type: application/octet-stream
Content-Range: 0-4095
Content-Length: 4096

<exact bytes 0 through 4095 of the staged blob>

Each successful PATCH returns 202 Accepted, a Range header for the accepted prefix, and a Location. Use the location from that latest response for the next request. Compute the next start as the last acknowledged byte plus one; for inclusive ranges, the chunk length must equal end - start + 1. A gap or out-of-order chunk is not a harmless retry: the specification requires the registry to respond with 416 Requested Range Not Satisfiable for an out-of-order upload.

After the third chunk is acknowledged, close the session with a PUT to the latest upload location and the digest of all 10,000 bytes. If the last chunk was already sent with PATCH, the closing PUT has no chunk body. The successful close returns 201 Created and a pullable blob location. The final PUT is required even when the final bytes were sent in a PATCH.

Resume from server state, not a guessed retry offset

A connection can fail at an awkward point: the registry might have stored a chunk even though the client never received its response. Blindly resending the same bytes can produce a range conflict or obscure which prefix the registry accepted. The safe recovery path is to query the active upload using GET at the latest known upload location. For an active session, the protocol returns 204 No Content, a current Location, and a Range ending at the last accepted byte. Continue at the following byte and use that returned location.

The same status query is the prescribed recovery step after a 416. Do not infer server state only from the local file offset, a timeout, or an error string. Persist enough client state to associate the registry, repository, digest, expected size, upload location, and local staged content; protect the location because it may carry operationally sensitive query data. The specification does not promise a universal session lifetime, so clients must handle a session that the selected registry no longer recognizes and restart only after checking whether the target blob already exists.

Before creating an upload, a client can issue HEAD /v2/<name>/blobs/<digest> to check whether the blob is present. An existing blob must return 200 OK; an absent blob must return 404 Not Found. A 401 or 403 is an authorization failure, not evidence that the blob is missing. The digest and size response headers are recommended, not guaranteed by the specification, so do not make an optional header the only proof of presence.

Use cross-repository mounting as an optimization, not a dependency

If the blob is already available in another repository in the same registry, the client can request a cross-repository mount with a POST using the source repository and digest. A successful mount returns 201 Created and a blob location. Mounting across different registries is not what this operation describes.

Mount support and behavior are not universal. For an unsupported or unsuccessful mount, the OCI Distribution Specification says a registry SHOULD return 202 Accepted with an upload session; SHOULD is a recommendation, not an unconditional guarantee. A robust client must handle the registry’s actual response and retain the regular upload path. Do not treat the mount as a prerequisite or infer support from a shared vendor or hostname family.

Make CI verify the service it actually uses

A practical client workflow is:

  1. Produce an immutable staged blob, then compute its digest and size from the exact bytes to upload.
  2. Check for an existing blob using HEAD; distinguish 404 from authentication, rate-limit, and server failures.
  3. Where useful, attempt a same-registry mount and handle both 201 success and 202 upload-session fallback.
  4. Select a transfer method supported by the target registry. For chunked transfers, persist and advance the latest Location and Range after each successful response.
  5. After ambiguous network failures or 416, query session status before sending more bytes.
  6. Close with PUT and the complete-blob digest, require the expected 201, and record the resulting blob location.
  7. Push the manifest after its referenced blobs are available, then verify the digest-addressed manifest and release evidence through the approved policy path.

Use a maintained OCI client for production transfers. Hand-written HTTP code must correctly handle relative locations, absolute offload locations, authentication challenges, query parameters, redirects, retries, and session state. Do not blindly forward a bearer token to an arbitrary absolute upload URL. Apply an explicit registry/redirect trust policy, let the authentication flow select credentials for each approved origin, and avoid logging full upload locations because signed query values can act like temporary credentials.

Test the exact registry version with interrupted transfers, an acknowledged chunk whose response is dropped, a rejected out-of-order range, a final chunk smaller than the announced minimum, unsupported single-POST behavior, mount fallback, expired sessions, and authorization failures. Confirm that a transfer cannot be marked complete before the closing PUT returns success. The OCI conformance model requires pull support from conforming registries and describes push as a separate workflow category; individual optional methods still need explicit compatibility testing.

Related:

Sources:

Comments