Skip to content

Docker Registry HTTP API V2 - Technical Documentation

This document describes the Docker Registry HTTP API V2 implementation in Nitro Repo. Use this to understand how Docker clients interact with the registry or to implement custom clients.

API Endpoints

All Docker Registry endpoints are prefixed with /v2/.

Version Check

Endpoint: GET /v2/

Returns the Docker Registry API version. This endpoint typically does not require authentication.

Response:

HTTP/1.1 200 OK
Docker-Distribution-API-Version: registry/2.0
Content-Type: application/json

{}

Upload Initialization

Endpoint: POST /v2/{storage}/{repository}/{name}/blobs/uploads/

Initiates a blob (layer or config) upload session.

Request:

POST /v2/docker/docker-test/my-app/blobs/uploads/
Authorization: Basic base64(username:password)

Response:

HTTP/1.1 202 Accepted
Location: /v2/docker/docker-test/my-app/blobs/uploads/{uuid}
Range: 0-0
Docker-Upload-UUID: {uuid}

Blob Upload (Chunked)

Endpoint: PATCH /v2/{storage}/{repository}/{name}/blobs/uploads/{uuid}

Uploads a chunk of blob data.

Request:

PATCH /v2/docker/docker-test/my-app/blobs/uploads/{uuid}
Content-Type: application/octet-stream
Content-Range: 0-1023
Content-Length: 1024

[binary data]

Response:

HTTP/1.1 202 Accepted
Location: /v2/docker/docker-test/my-app/blobs/uploads/{uuid}
Range: 0-1023

Blob Upload Completion

Endpoint: PUT /v2/{storage}/{repository}/{name}/blobs/uploads/{uuid}?digest={digest}

Completes a blob upload and verifies the digest.

Request:

PUT /v2/docker/docker-test/my-app/blobs/uploads/{uuid}?digest=sha256:abc123...
Content-Length: 0

Response:

HTTP/1.1 201 Created
Location: /v2/docker/docker-test/my-app/blobs/sha256:abc123...
Content-Length: 0
Docker-Content-Digest: sha256:abc123...

Blob Download

Endpoint: GET /v2/{storage}/{repository}/{name}/blobs/{digest}

Downloads a blob by its digest.

Request:

GET /v2/docker/docker-test/my-app/blobs/sha256:abc123...

Response:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 12345
Docker-Content-Digest: sha256:abc123...

[binary data]

Blob Existence Check

Endpoint: HEAD /v2/{storage}/{repository}/{name}/blobs/{digest}

Checks if a blob exists without downloading it.

Response:

HTTP/1.1 200 OK
Content-Length: 12345
Docker-Content-Digest: sha256:abc123...

Manifest Upload

Endpoint: PUT /v2/{storage}/{repository}/{name}/manifests/{reference}

Uploads an image manifest. The reference can be a tag or digest.

Request:

PUT /v2/docker/docker-test/my-app/manifests/latest
Content-Type: application/vnd.docker.distribution.manifest.v2+json

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 1234,
    "digest": "sha256:config123..."
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 5678,
      "digest": "sha256:layer123..."
    }
  ]
}

Response:

HTTP/1.1 201 Created
Location: /v2/docker/docker-test/my-app/manifests/latest
Docker-Content-Digest: sha256:manifest123...

Manifest Download

Endpoint: GET /v2/{storage}/{repository}/{name}/manifests/{reference}

Downloads an image manifest by tag or digest.

Request:

GET /v2/docker/docker-test/my-app/manifests/latest
Accept: application/vnd.docker.distribution.manifest.v2+json

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.docker.distribution.manifest.v2+json
Docker-Content-Digest: sha256:manifest123...

{manifest JSON}

Tags List

Endpoint: GET /v2/{storage}/{repository}/{name}/tags/list

Lists all tags for an image.

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "name": "my-app",
  "tags": ["latest", "v1.0.0", "v1.0.1"]
}

Authentication

Docker clients use HTTP Basic Authentication. Nitro Repo supports two authentication methods:

Username + Password

Standard user authentication:

bash
docker login your-registry.com
Username: alice
Password: her_password

This sends:

Authorization: Basic base64(alice:her_password)

Use an auth token as the password. The username can be anything:

bash
docker login your-registry.com
Username: token
Password: nr_abc123...your_auth_token

This sends:

Authorization: Basic base64(token:nr_abc123...your_auth_token)

When Nitro Repo receives Basic auth:

  1. First attempts to verify as username/password
  2. If that fails, treats the password field as an auth token
  3. This allows token-based authentication with any username

Authentication Challenge

When authentication is required, Nitro Repo issues a Docker-compliant bearer challenge:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="https://your-registry.com/v2/token",service="your-registry.com",scope="repository/{storage}/{repository}:pull,push"
Docker-Distribution-API-Version: registry/2.0

The Docker client then requests a bearer token from /v2/token, supplying the scope that was provided in the challenge. Nitro Repo validates the user's credentials (username/password, session, or automation token), issues a short-lived bearer token scoped to the requested repository actions, and the client retries the original request with:

Authorization: Bearer <token>

This flow matches the Docker Registry specification, allowing standard Docker CLIs and OCI clients to authenticate without additional configuration.

Authorization Behavior

Nitro Repo's Docker repositories follow this authorization model:

When Auth is Disabled

  • Pull (GET/HEAD): Allowed without authentication
  • Push (PUT/POST/DELETE): Requires authentication

When Auth is Enabled

  • All Operations: Require authentication

This matches standard package repository behavior where public registries allow anonymous pulls but require auth for pushes.

Content Addressing

Docker uses content-addressable storage with SHA256 digests:

sha256:1234567890abcdef...

All blobs (layers, configs) and manifests are stored by their digest, ensuring:

  • Immutability: Content cannot be changed once stored
  • Deduplication: Identical content is stored only once
  • Integrity: Content is verified against its digest

Media Types

Nitro Repo supports these media types:

Docker Manifest V2

  • application/vnd.docker.distribution.manifest.v2+json
  • application/vnd.docker.distribution.manifest.list.v2+json

OCI Image Format

  • application/vnd.oci.image.manifest.v1+json
  • application/vnd.oci.image.index.v1+json

Layer Media Types

  • application/vnd.docker.image.rootfs.diff.tar.gzip
  • application/vnd.oci.image.layer.v1.tar+gzip

Error Responses

Errors follow the Docker Registry API V2 error format:

json
{
  "errors": [
    {
      "code": "MANIFEST_INVALID",
      "message": "manifest invalid",
      "detail": "Invalid manifest JSON"
    }
  ]
}

Common error codes:

  • BLOB_UNKNOWN - Blob not found
  • BLOB_UPLOAD_INVALID - Invalid upload session
  • MANIFEST_INVALID - Invalid manifest format
  • MANIFEST_UNKNOWN - Manifest not found
  • NAME_INVALID - Invalid repository name
  • TAG_INVALID - Invalid tag name
  • UNAUTHORIZED - Authentication required
  • DENIED - Permission denied

Path Rewriting

Nitro Repo uses a special path rewriting mechanism to maintain compatibility with Docker clients while preserving its internal URL structure.

Docker clients expect: /v2/{name}/... Nitro Repo internally uses: /repositories/{storage}/{repository}/...

The router automatically rewrites:

/v2/{storage}/{repository}/{*path}
→ /repositories/{storage}/{repository}/v2/{*path}

This allows Docker clients to use natural URLs while Nitro Repo maintains its organized structure.