OCI / Docker registry
The OCI container registry implements the OCI Distribution
Specification (the
/v2 API) used by docker, podman, buildah, and compatible clients to pull
and push container images and other OCI artifacts. See
README.md for the conventions shared by every registry protocol.
Resource overview
All endpoints are served under /v2/ on the API host
(https://api.gitvetrix.com). The repository is named in the path as
{owner}/{repo}[/{image}]: the first two segments resolve to a repository, and
an optional trailing segment names an image within it. A reference (<ref>) is
either a tag (v1.2.3) or a content digest (sha256:<hex>); manifest and blob
deletes require a digest.
Auth & scopes
OCI uses token authentication. See Authentication for the credential types and Push scopes for how a pull/push grant is resolved.
- A client requests
GET /v2/. Without a valid credential the registry answers401with aWWW-Authenticate: Bearer realm="…/v2/token",service="…"challenge. - The client calls
GET /v2/tokento exchange its credential for a short-lived (five-minute), repository-scoped token.docker loginsupplies the underlying credential — a session JWT, a personal access token, or a CI job token — as the HTTP Basic password. - The client replays the original request with
Authorization: Bearer <token>.
The minted token's actions are derived from the caller's authorization, so it can never grant more than the underlying credential would on a direct request. A pull on a public repository needs no credential at all.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET |
/v2/ |
API version check / auth probe |
GET |
/v2/token |
Mint a repository-scoped bearer token |
GET |
/v2/{name}/tags/list |
List the tags of an image |
GET, HEAD |
/v2/{name}/manifests/{ref} |
Pull a manifest (or check existence) |
PUT |
/v2/{name}/manifests/{ref} |
Push a manifest |
DELETE |
/v2/{name}/manifests/{digest} |
Delete a manifest / untag |
GET, HEAD |
/v2/{name}/blobs/{digest} |
Pull a blob (or check existence) |
DELETE |
/v2/{name}/blobs/{digest} |
Delete a blob |
POST |
/v2/{name}/blobs/uploads/ |
Start a blob upload |
GET |
/v2/{name}/blobs/uploads/{uuid} |
Check upload progress |
PATCH |
/v2/{name}/blobs/uploads/{uuid} |
Append a chunk |
PUT |
/v2/{name}/blobs/uploads/{uuid}?digest={digest} |
Finalize an upload |
GET /v2/
The version-check endpoint, used by clients to discover whether the registry
requires authentication. With a valid credential it returns 200 OK, an empty
JSON body, and a Docker-Distribution-API-Version: registry/2.0 header. Without
one it returns 401 and the Bearer challenge so the client enters the auth
flow. Anonymous pulls of public images still succeed on the per-resource
endpoints even though this probe challenges.
GET /v2/token
Mints a short-lived bearer token scoped to a single repository. The requested
repository is passed in the scope query parameter; the granted actions are
derived from the caller's authorization, not from the request.
Query parameters
| Name | Type | Description |
|---|---|---|
scope |
string | repository:{owner}/{repo}:{actions}, for example repository:alice/app:pull,push. The repository is honored; the actions are re-derived. |
Response
{
"token": "<jwt>",
"access_token": "<jwt>",
"expires_in": 300,
"issued_at": "2026-01-01T00:00:00Z",
"scope": "repository:alice/app:pull,push"
}
The granted scope is …:pull,push for an authorized writer, …:pull for a
reader, or empty when the caller has no access (an honest zero-access token,
never a forged grant). access_token duplicates token for client
compatibility.
GET /v2/{name}/tags/list
Returns the tags of an image.
{ "name": "alice/app", "tags": ["v1.0.0", "v1.1.0", "latest"] }
GET, HEAD /v2/{name}/manifests/{ref}
Pulls the manifest identified by {ref} (a tag or a sha256: digest). The
response carries the manifest body with its Content-Type (for example
application/vnd.oci.image.manifest.v1+json or
application/vnd.docker.distribution.manifest.v2+json) and a
Docker-Content-Digest header. A HEAD returns the same headers with no body.
PUT /v2/{name}/manifests/{ref}
Pushes a manifest, tagging it as {ref}. Returns 201 Created with a
Location header (/v2/{name}/manifests/{digest}) and the
Docker-Content-Digest of the stored manifest.
DELETE /v2/{name}/manifests/{digest}
Deletes a manifest (untagging the image). The reference must be a sha256:
digest, not a tag. Returns 202 Accepted.
GET, HEAD /v2/{name}/blobs/{digest}
Pulls a blob (a layer or config) by its sha256: digest, with a
Docker-Content-Digest header. A HEAD returns headers only.
DELETE /v2/{name}/blobs/{digest}
Deletes a blob. Returns 202 Accepted.
Blob upload
A push uploads each layer through a session, then the manifest references them:
POST /v2/{name}/blobs/uploads/starts a session and returns202 Acceptedwith aLocation(/v2/{name}/blobs/uploads/{uuid}) and aDocker-Upload-UUIDheader.PATCHtheLocationwith chunk bytes to append data (202 Accepted).PUTtheLocationwith?digest={digest}to finalize. The registry verifies the content against the digest and returns201 Createdwith aLocationof/v2/{name}/blobs/{digest}.GETtheLocationat any point returns the bytes received so far.
Example
# Authenticate and pull (docker handles the /v2/token exchange):
docker login api.gitvetrix.com # username: any; password: <jwt-or-pat-or-job-token>
docker pull api.gitvetrix.com/alice/app:v1.0.0
# Push:
docker tag local/image api.gitvetrix.com/alice/app:v1.0.0
docker push api.gitvetrix.com/alice/app:v1.0.0
Errors
OCI errors use the Distribution Spec error envelope rather than the REST error
shape: a JSON body with an errors array of { "code", "message", "detail" }
objects, alongside the HTTP status.
{ "errors": [ { "code": "MANIFEST_UNKNOWN", "message": "manifest unknown", "detail": null } ] }
| Status | Code | When |
|---|---|---|
401 Unauthorized |
UNAUTHORIZED |
No or invalid credential; carries the Bearer challenge. |
403 Forbidden |
DENIED |
Authenticated but not permitted to push or delete here. |
404 Not Found |
NAME_UNKNOWN |
Unknown repository — or a private one the caller may not read. |
404 Not Found |
MANIFEST_UNKNOWN / BLOB_UNKNOWN |
The manifest or blob does not exist. |
400 Bad Request |
MANIFEST_INVALID / DIGEST_INVALID |
Malformed manifest, or a content/digest mismatch on finalize. |
405 Method Not Allowed |
UNSUPPORTED |
The method is not supported on this resource. |
A denied private-repository read returns NAME_UNKNOWN, not DENIED, so a
caller cannot probe for private images. See
errors.md.
Rate limits
Metered under the registry scope. See rate-limits.md.