Documentation

Registry behavior

This page states the wire behavior of the OCI conformance points peryx implements on the /v2/ surface: which digest algorithms it accepts where, the statuses and headers for cancelling and resuming an upload, and how the referrers API validates its subject digest. For the full route list see HTTP endpoints; for the specifications, see standards; for the reasoning, see why peryx serves the registry the way it does.

Content digest algorithms

Every object peryx stores is addressed by the sha256 of its exact bytes. A request, or an upstream, can still name a manifest with a digest in another algorithm the image-spec digest grammar permits. This section states what peryx accepts, how it validates a digest, and where the answer differs between manifests and blobs. For the reason behind the manifest behavior, see why peryx accepts a non-sha256 content digest.

The grammar peryx parses

A <reference> that contains a : is a digest, algorithm:encoded; otherwise it is a tag. peryx accepts a digest reference when both halves are well formed:

  • algorithm: a non-empty run of lowercase letters, digits, and the separators + . _ -. sha256, sha512, and a custom token like multihash+base58 all pass.
  • encoded: a non-empty run of lowercase letters, digits, and = _ -. An uppercase letter in the encoded half is rejected, because a digest is a cache and storage key and sha256:AB… would key a second copy of the same content.

Routing checks the shape only, not the length, and hands the digest on verbatim. A reference that fails the shape does not route: a manifest or blob request with a malformed digest is not a recognized route and answers 404.

Manifest reads

peryx addresses every manifest it stores by the sha256 of its bytes, so a stored manifest's Docker-Content-Digest is always a sha256: value. The integrity check that a pull-through runs, whether the bytes hash to what was advertised, only means something for an algorithm peryx can recompute, which is sha256. It is scoped to a sha256: advertisement; a digest in another algorithm is content-addressed under peryx's own sha256 instead of compared.

Readperyx does
by tag, upstream advertises a matching sha256: digeststores and serves it; Docker-Content-Digest is that sha256
by tag, upstream advertises a sha256: digest that does not hash the bytes502 gateway error; nothing cached
by tag, upstream advertises a non-sha256 digest (e.g. sha512:)stores under the canonical sha256; serves with that sha256, not the sha512
by tag, upstream advertises no digeststores and serves under the canonical sha256
by sha256: digest that hashes the bytesserves it
by sha256: digest that does not hash the bytes400 MANIFEST_INVALID
by non-sha256 digest (e.g. sha512:)serves the bytes under the requested digest, which it echoes back

A pull by a non-sha256 digest can never equal the sha256 canonical, so peryx cannot verify the request against the bytes the way it does for sha256:. The upstream content-addressed the manifest under that digest; peryx serves those bytes under the digest the client asked for, and stores them under its own sha256 for the cache.

Blobs are sha256 only

A blob digest on a pull, a mount, or the PUT that commits an upload must be sha256:. Any other algorithm answers 400 DIGEST_INVALID with only sha256 blob digests are supported. peryx streams a blob into a content-addressed store and verifies it against its sha256 on commit, so it has no store keyed by another algorithm to serve one from.

What content-addressing does not do

  • It never stores or keys an object under a non-sha256 digest. Everything on disk is addressed by sha256; a non-sha256 digest is a value peryx echoes on a read, not a second content address.
  • It does not verify a non-sha256 upstream advertisement. It cannot recompute a sha512, so it trusts that header field and relies on its own sha256 over the exact bytes for integrity.
  • The offline mirror still pins a by-digest reference to sha256. A mirror entry written as repo@sha512:… fails, because the mirror compares the reference against the sha256 it computes. The relaxation is on the online pull-through path, not the mirror pin.

Upload sessions

An upload session belongs to a hosted index and lives in the serving process. Writing to one needs the target index's upload_token as the Basic-auth password, like every other write. Sessions are held in memory, so a restart drops every open session and a subsequent request against it is 404.

Cancel an upload session

DELETE /v2/<name>/blobs/uploads/<session> cancels an open upload (distribution-spec end-14).

ConditionStatus
<session> is an open session this index opened204 No Content
<session> is unknown, already committed, or already gone404 BLOB_UPLOAD_UNKNOWN
Missing or wrong upload_token401 UNAUTHORIZED
The resolved index is read-only or has uploads disabled403 DENIED

A 204 drops the session and unlinks its staged temp file. An open session that is neither finished nor cancelled is reaped on its own after one hour of inactivity.

The 416 resume response

PATCH /v2/<name>/blobs/uploads/<session> appends a chunk only when its Content-Range begins exactly where the last chunk ended. A chunk that starts anywhere else, or whose Content-Range cannot be parsed, is 416 Range Not Satisfiable, and the session keeps the bytes it already holds so the client can resend rather than restart. The 416 carries the session coordinates:

HeaderValueMeaning
Location/v2/<name>/blobs/uploads/<session>the URL to resume against
Docker-Upload-UUID<session>the session id
Range0-<end>the bytes already received; resume at <end> + 1

These are the same coordinates the opening 202, a chunk 202, and the progress GET (204) return, so a client that overshoots has everything it needs to continue. A PUT whose trailing body starts at the wrong offset returns the same 416.

Referrers subject-digest validation

GET /v2/<name>/referrers/<digest> validates <digest> against the image-spec digest grammar before it looks anything up. A malformed digest is 400 DIGEST_INVALID (referrers digest is malformed); a well-formed one that names no subject is 200 with an empty manifests list, not an error. This is the one place a digest route answers 400 DIGEST_INVALID for a malformed value where a manifest or blob route simply 404s.

The grammar is algorithm:encoded. For the two registered algorithms peryx enforces the fixed lowercase-hex length; an unregistered algorithm is held only to the general grammar, since peryx cannot know its encoding.

AlgorithmEncoded lengthCharacter set
sha25664lowercase hex
sha512128lowercase hex
any othernon-empty[a-z0-9=_-] grammar
<digest>ResultWhy
sha256: + 64 lowercase-hex chars200registered, correct length
sha512: + 128 lowercase-hex chars200registered, correct length
sha256:bad400 DIGEST_INVALIDregistered but not 64 hex chars
sha256: + 64 non-hex chars400 DIGEST_INVALIDregistered but the encoding is not hex
sha256: + uppercase hex400 DIGEST_INVALIDa digest keys the store, which is lowercase-only
sha512: + 64 hex chars400 DIGEST_INVALIDregistered but the wrong length for sha512
multihash:<non-empty encoding>200unregistered algorithm, accepted by the general grammar
sha256: (empty encoding), nocolon400 DIGEST_INVALIDnot algorithm:encoded

A 200 with an unknown-but-valid subject returns the image-index shape (application/vnd.oci.image.index.v1+json, schemaVersion: 2) with manifests: []. Before this validation a malformed subject fell through to an empty lookup and answered 200 with an empty index, hiding the client's mistake.

What the referrers grammar does not do

The lenient referrers-subject grammar covers sha512 and unregistered algorithms because a subject is only a lookup key. Stored content is stricter: peryx addresses and serves sha256 blobs and manifests only. A blob or manifest GET/PUT/DELETE whose <digest> is not sha256:<64 hex> is 400 DIGEST_INVALID, and a PUT whose bytes do not hash to the claimed sha256 digest is rejected on commit. peryx does not persist a sha512 object; the algorithm is accepted on the referrers path as a syntactically valid subject, nothing more.

On this page