From 726b57a2ac04222da149beb46335ad884d5cc29d Mon Sep 17 00:00:00 2001 From: Craig Osterhout <103533812+craig-osterhout@users.noreply.github.com> Date: Fri, 23 May 2025 10:34:20 -0700 Subject: [PATCH] registry: add reference (#22497) ## Description Re-add registry reference from point in time of deletion at https://github.com/docker/docs/pull/18390, not upstream. https://deploy-preview-22497--docsdocker.netlify.app/reference/api/registry/latest/ https://deploy-preview-22497--docsdocker.netlify.app/reference/api/registry/auth/ ## Related issues or tickets ENGDOCS-2577 ## Reviews - [ ] Technical review - [ ] Editorial review - [ ] Product review --------- Signed-off-by: Craig Co-authored-by: sheltongraves <148902861+sheltongraves@users.noreply.github.com> Co-authored-by: Sarah Sanders --- content/reference/_index.md | 4 + content/reference/api/registry/_index.md | 5 + content/reference/api/registry/auth.md | 221 +++ .../api/registry/images/v2-registry-auth.png | Bin 0 -> 15859 bytes content/reference/api/registry/latest.md | 7 + content/reference/api/registry/latest.yaml | 1345 +++++++++++++++++ 6 files changed, 1582 insertions(+) create mode 100644 content/reference/api/registry/_index.md create mode 100644 content/reference/api/registry/auth.md create mode 100644 content/reference/api/registry/images/v2-registry-auth.png create mode 100644 content/reference/api/registry/latest.md create mode 100644 content/reference/api/registry/latest.yaml diff --git a/content/reference/_index.md b/content/reference/_index.md index b0b093cd70..d51c780f93 100644 --- a/content/reference/_index.md +++ b/content/reference/_index.md @@ -42,6 +42,10 @@ params: description: API for Docker Verified Publishers to fetch analytics data. icon: area_chart link: /reference/api/hub/dvp/ + - title: Registry API + description: API for Docker Registry. + icon: database + link: /reference/api/registry/latest/ --- This section includes the reference documentation for the Docker platform's diff --git a/content/reference/api/registry/_index.md b/content/reference/api/registry/_index.md new file mode 100644 index 0000000000..0d376d4a28 --- /dev/null +++ b/content/reference/api/registry/_index.md @@ -0,0 +1,5 @@ +--- +title: Registry API +build: + render: never +--- \ No newline at end of file diff --git a/content/reference/api/registry/auth.md b/content/reference/api/registry/auth.md new file mode 100644 index 0000000000..d395066014 --- /dev/null +++ b/content/reference/api/registry/auth.md @@ -0,0 +1,221 @@ +--- +title: Registry authentication +description: "Specifies the Docker Registry v2 authentication" +keywords: registry, images, tags, repository, distribution, Bearer authentication, advanced +--- + +This document outlines the registry authentication scheme: + +![v2 registry auth](./images/v2-registry-auth.png) + +1. Attempt to begin a push/pull operation with the registry. +2. If the registry requires authorization it will return a `401 Unauthorized` + HTTP response with information on how to authenticate. +3. The registry client makes a request to the authorization service for a + Bearer token. +4. The authorization service returns an opaque Bearer token representing the + client's authorized access. +5. The client retries the original request with the Bearer token embedded in + the request's Authorization header. +6. The Registry authorizes the client by validating the Bearer token and the + claim set embedded within it and begins the push/pull session as usual. + +## Requirements + +- Registry clients which can understand and respond to token auth challenges + returned by the resource server. +- An authorization server capable of managing access controls to their + resources hosted by any given service (such as repositories in a Docker + Registry). +- A Docker Registry capable of trusting the authorization server to sign tokens + which clients can use for authorization and the ability to verify these + tokens for single use or for use during a sufficiently short period of time. + +## Authorization server endpoint descriptions + +The described server is meant to serve as a standalone access control manager +for resources hosted by other services which want to authenticate and manage +authorizations using a separate access control manager. + +A service like this is used by the official Docker Registry to authenticate +clients and verify their authorization to Docker image repositories. + +As of Docker 1.6, the registry client within the Docker Engine has been updated +to handle such an authorization workflow. + +## How to authenticate + +Registry V1 clients first contact the index to initiate a push or pull. Under +the Registry V2 workflow, clients should contact the registry first. If the +registry server requires authentication it will return a `401 Unauthorized` +response with a `WWW-Authenticate` header detailing how to authenticate to this +registry. + +For example, say I (username `jlhawn`) am attempting to push an image to the +repository `samalba/my-app`. For the registry to authorize this, I will need +`push` access to the `samalba/my-app` repository. The registry will first +return this response: + +```text +HTTP/1.1 401 Unauthorized +Content-Type: application/json; charset=utf-8 +Docker-Distribution-Api-Version: registry/2.0 +Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:samalba/my-app:pull,push" +Date: Thu, 10 Sep 2015 19:32:31 GMT +Content-Length: 235 +Strict-Transport-Security: max-age=31536000 + +{"errors":[{"code":"UNAUTHORIZED","message":"access to the requested resource is not authorized","detail":[{"Type":"repository","Name":"samalba/my-app","Action":"pull"},{"Type":"repository","Name":"samalba/my-app","Action":"push"}]}]} +``` + +Note the HTTP Response Header indicating the auth challenge: + +```text +Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:samalba/my-app:pull,push" +``` + +This format is documented in [Section 3 of RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750#section-3) + +This challenge indicates that the registry requires a token issued by the +specified token server and that the request the client is attempting will +need to include sufficient access entries in its claim set. To respond to this +challenge, the client will need to make a `GET` request to the URL +`https://auth.docker.io/token` using the `service` and `scope` values from the +`WWW-Authenticate` header. + +## Requesting a token + +Defines getting a bearer and refresh token using the token endpoint. + +### Query parameters + +#### `service` + +The name of the service which hosts the resource. + +#### `offline_token` + +Whether to return a refresh token along with the bearer token. A refresh token +is capable of getting additional bearer tokens for the same subject with +different scopes. The refresh token does not have an expiration and should be +considered completely opaque to the client. + +#### `client_id` + +String identifying the client. This `client_id` does not need to be registered +with the authorization server but should be set to a meaningful value in order +to allow auditing keys created by unregistered clients. Accepted syntax is +defined in [RFC6749 Appendix +A.1](https://tools.ietf.org/html/rfc6749#appendix-A.1). + +#### `scope` + +The resource in question, formatted as one of the space-delimited entries from +the `scope` parameters from the `WWW-Authenticate` header shown previously. This +query parameter should be specified multiple times if there is more than one +`scope` entry from the `WWW-Authenticate` header. The previous example would be +specified as: `scope=repository:samalba/my-app:push`. The scope field may be +empty to request a refresh token without providing any resource permissions to +the returned bearer token. + +### Token response fields + +#### `token` + +An opaque `Bearer` token that clients should supply to subsequent +requests in the `Authorization` header. + +#### `access_token` + +For compatibility with OAuth 2.0, the `token` under the name `access_token` is +also accepted. At least one of these fields must be specified, but both may +also appear (for compatibility with older clients). When both are specified, +they should be equivalent; if they differ the client's choice is undefined. + +#### `expires_in` + +(Optional) The duration in seconds since the token was issued that it will +remain valid. When omitted, this defaults to 60 seconds. For compatibility +with older clients, a token should never be returned with less than 60 seconds +to live. + +#### `issued_at` + +(Optional) The [RFC3339](https://www.ietf.org/rfc/rfc3339.txt)-serialized UTC +standard time at which a given token was issued. If `issued_at` is omitted, the +expiration is from when the token exchange completed. + +#### `refresh_token` + +(Optional) Token which can be used to get additional access tokens for +the same subject with different scopes. This token should be kept secure +by the client and only sent to the authorization server which issues +bearer tokens. This field will only be set when `offline_token=true` is +provided in the request. + +### Example + +For this example, the client makes an HTTP GET request to the following URL: + +```text +https://auth.docker.io/token?service=registry.docker.io&scope=repository:samalba/my-app:pull,push +``` + +The token server should first attempt to authenticate the client using any +authentication credentials provided with the request. From Docker 1.11 the +Docker Engine supports both Basic Authentication and OAuth2 for +getting tokens. Docker 1.10 and before, the registry client in the Docker Engine +only supports Basic Authentication. If an attempt to authenticate to the token +server fails, the token server should return a `401 Unauthorized` response +indicating that the provided credentials are invalid. + +Whether the token server requires authentication is up to the policy of that +access control provider. Some requests may require authentication to determine +access (such as pushing or pulling a private repository) while others may not +(such as pulling from a public repository). + +After authenticating the client (which may simply be an anonymous client if +no attempt was made to authenticate), the token server must next query its +access control list to determine whether the client has the requested scope. In +this example request, if I have authenticated as user `jlhawn`, the token +server will determine what access I have to the repository `samalba/my-app` +hosted by the entity `registry.docker.io`. + +Once the token server has determined what access the client has to the +resources requested in the `scope` parameter, it will take the intersection of +the set of requested actions on each resource and the set of actions that the +client has in fact been granted. If the client only has a subset of the +requested access **it must not be considered an error** as it is not the +responsibility of the token server to indicate authorization errors as part of +this workflow. + +Continuing with the example request, the token server will find that the +client's set of granted access to the repository is `[pull, push]` which when +intersected with the requested access `[pull, push]` yields an equal set. If +the granted access set was found only to be `[pull]` then the intersected set +would only be `[pull]`. If the client has no access to the repository then the +intersected set would be empty, `[]`. + +It is this intersected set of access which is placed in the returned token. + +The server then constructs an implementation-specific token with this +intersected set of access, and returns it to the Docker client to use to +authenticate to the audience service (within the indicated window of time): + +```text +HTTP/1.1 200 OK +Content-Type: application/json + +{"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w", "expires_in": 3600,"issued_at": "2009-11-10T23:00:00Z"} +``` + +## Using the Bearer token + +Once the client has a token, it will try the registry request again with the +token placed in the HTTP `Authorization` header like so: + +```text +Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkJWM0Q6MkFWWjpVQjVaOktJQVA6SU5QTDo1RU42Ok40SjQ6Nk1XTzpEUktFOkJWUUs6M0ZKTDpQT1RMIn0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJCQ0NZOk9VNlo6UUVKNTpXTjJDOjJBVkM6WTdZRDpBM0xZOjQ1VVc6NE9HRDpLQUxMOkNOSjU6NUlVTCIsImF1ZCI6InJlZ2lzdHJ5LmRvY2tlci5jb20iLCJleHAiOjE0MTUzODczMTUsIm5iZiI6MTQxNTM4NzAxNSwiaWF0IjoxNDE1Mzg3MDE1LCJqdGkiOiJ0WUpDTzFjNmNueXk3a0FuMGM3cktQZ2JWMUgxYkZ3cyIsInNjb3BlIjoiamxoYXduOnJlcG9zaXRvcnk6c2FtYWxiYS9teS1hcHA6cHVzaCxwdWxsIGpsaGF3bjpuYW1lc3BhY2U6c2FtYWxiYTpwdWxsIn0.Y3zZSwaZPqy4y9oRBVRImZyv3m_S9XDHF1tWwN7mL52C_IiA73SJkWVNsvNqpJIn5h7A2F8biv_S2ppQ1lgkbw +``` + +This is also described in [Section 2.1 of RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750#section-2.1) \ No newline at end of file diff --git a/content/reference/api/registry/images/v2-registry-auth.png b/content/reference/api/registry/images/v2-registry-auth.png new file mode 100644 index 0000000000000000000000000000000000000000..0ea8a4205bc33d1763dfce3a38a26c2644f32849 GIT binary patch literal 15859 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 B8PEU# literal 0 HcmV?d00001 diff --git a/content/reference/api/registry/latest.md b/content/reference/api/registry/latest.md new file mode 100644 index 0000000000..7df6e59716 --- /dev/null +++ b/content/reference/api/registry/latest.md @@ -0,0 +1,7 @@ +--- +layout: api +title: Supported registry API for Docker Hub +linktitle: Latest +description: "Supported registry API endpoints." +keywords: registry, on-prem, images, tags, repository, distribution, api, advanced +--- diff --git a/content/reference/api/registry/latest.yaml b/content/reference/api/registry/latest.yaml new file mode 100644 index 0000000000..c38c9ffd71 --- /dev/null +++ b/content/reference/api/registry/latest.yaml @@ -0,0 +1,1345 @@ +openapi: 3.0.3 +info: + title: Supported registry API for Docker Hub + description: | + Docker Hub is an OCI-compliant registry, which means it adheres to the open + standards defined by the Open Container Initiative (OCI) for distributing + container images. This ensures compatibility with a wide range of tools and + platforms in the container ecosystem. + + This reference documents the Docker Hub-supported subset of the Registry HTTP API V2. + It focuses on pulling, pushing, and deleting images. It does not cover the full OCI Distribution Specification. + + For the complete OCI specification, see [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec). +servers: + - description: Docker Hub registry API + x-audience: public + url: https://registry-1.docker.io + +tags: + - name: overview + x-displayName: Overview + description: | + All endpoints in this API are prefixed by the version and repository name, for example: + + ``` + /v2// + ``` + + This format provides structured access control and URI-based scoping of image operations. + + For example, to interact with the `library/ubuntu` repository, use: + + ``` + /v2/library/ubuntu/ + ``` + + Repository names must meet these requirements: + 1. Consist of path components matching `[a-z0-9]+(?:[._-][a-z0-9]+)*` + 2. If more than one component, they must be separated by `/` + 3. Full repository name must be fewer than 256 characters + + + - name: authentication + x-displayName: Authentication + description: | + Specifies registry authentication. + externalDocs: + description: Detailed authentication workflow and token usage + url: https://docs.docker.com/reference/api/registry/auth/ + + - name: Manifests + x-displayName: Manifests + description: | + Image manifests are JSON documents that describe an image: its configuration blob, the digests of each layer blob, and metadata such as media‑types and annotations. + + - name: Blobs + x-displayName: Blobs + description: | + Blobs are the binary objects referenced from manifests: + the config JSON and one or more compressed layer tarballs. + + - name: pull + x-displayName: Pulling Images + description: | + Pulling an image involves retrieving the manifest and downloading each of the image's layer blobs. This section outlines the general steps followed by a working example. + + 1. [Get a bearer token for the repository](https://docs.docker.com/reference/api/registry/auth/). + 2. [Get the image manifest](#operation/GetImageManifest). + 3. If the response in the previous step is a multi-architecture manifest list, you must do the following: + - Parse the `manifests[]` array to locate the digest for your target platform (e.g., `linux/amd64`). + - [Get the image manifest](#operation/GetImageManifest) using the located digest. + 4. [Check if the blob exists](#operation/CheckBlobExists) before downloading. The client should send a `HEAD` request for each layer digest. + 5. [Download each layer blob](#operation/GetBlob) using the digest obtained from the manifest. The client should send a `GET` request for each layer digest. + + The following bash script example pulls `library/ubuntu:latest` from Docker Hub. + + ```bash + #!/bin/bash + + # Step 1: Get a bearer token + TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/ubuntu:pull" | jq -r .token) + + # Step 2: Get the image manifest. In this example, an image manifest list is returned. + curl -s -H "Authorization: Bearer $TOKEN" \ + -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" \ + https://registry-1.docker.io/v2/library/ubuntu/manifests/latest \ + -o manifest-list.json + + # Step 3a: Parse the `manifests[]` array to locate the digest for your target platform (e.g., `linux/amd64`). + IMAGE_MANIFEST_DIGEST=$(jq -r '.manifests[] | select(.platform.architecture == "amd64" and .platform.os == "linux") | .digest' manifest-list.json) + + # Step 3b: Get the platform-specific image manifest + curl -s -H "Authorization: Bearer $TOKEN" \ + -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ + https://registry-1.docker.io/v2/library/ubuntu/manifests/$IMAGE_MANIFEST_DIGEST \ + -o manifest.json + + # Step 4: Send a HEAD request to check if the layer blob exists + DIGEST=$(jq -r '.layers[0].digest' manifest.json) + curl -I -H "Authorization: Bearer $TOKEN" \ + https://registry-1.docker.io/v2/library/ubuntu/blobs/$DIGEST + + # Step 5: Download the layer blob + curl -L -H "Authorization: Bearer $TOKEN" \ + https://registry-1.docker.io/v2/library/ubuntu/blobs/$DIGEST + ``` + + This example pulls the manifest and the first layer for the `ubuntu:latest` image on the `linux/amd64` platform. Repeat steps 4 and 5 for each digest in the `.layers[]` array in the manifest. + + + - name: push + x-displayName: Pushing Images + description: | + Pushing an image involves uploading any image blobs (such as the config or layers), and then uploading the manifest that references those blobs. + + This section outlines the basic steps to push an image using the registry API. + + 1. [Get a bearer token for the repository](https://docs.docker.com/reference/api/registry/auth/) + + 2. [Check if the blob exists](#operation/CheckBlobExists) using a `HEAD` request for each blob digest. + + 3. If the blob does not exist, [upload the blob](#operation/CompleteBlobUpload) using a monolithic `PUT` request: + - First, [initiate the upload](#operation/InitiateBlobUpload) with `POST`. + - Then [upload and complete](#operation/CompleteBlobUpload) with `PUT`. + + **Note**: Alternatively, you can upload the blob in multiple chunks by using `PATCH` requests to send each chunk, followed by a final `PUT` request to complete the upload. This is known as a [chunked upload](#operation/UploadBlobChunk) and is useful for large blobs or when resuming interrupted uploads. + + + 4. [Upload the image manifest](#operation/PutImageManifest) using a `PUT` request to associate the config and layers. + + The following bash script example pushes a dummy config blob and manifest to `yourusername/helloworld:latest` on Docker Hub. You can replace `yourusername` with your Docker Hub username and `dckr_pat` with your Docker Hub personal access token. + + ```bash + #!/bin/bash + + USERNAME=yourusername + PASSWORD=dckr_pat + REPO=yourusername/helloworld + TAG=latest + CONFIG=config.json + MIME_TYPE=application/vnd.docker.container.image.v1+json + + # Step 1: Get a bearer token + TOKEN=$(curl -s -u "$USERNAME:$PASSWORD" \ + "https://auth.docker.io/token?service=registry.docker.io&scope=repository:$REPO:push,pull" \ + | jq -r .token) + + # Create a dummy config blob and compute its digest + echo '{"architecture":"amd64","os":"linux","config":{},"rootfs":{"type":"layers","diff_ids":[]}}' > $CONFIG + DIGEST="sha256:$(sha256sum $CONFIG | awk '{print $1}')" + + # Step 2: Check if the blob exists + STATUS=$(curl -s -o /dev/null -w "%{http_code}" -I \ + -H "Authorization: Bearer $TOKEN" \ + https://registry-1.docker.io/v2/$REPO/blobs/$DIGEST) + + if [ "$STATUS" != "200" ]; then + # Step 3: Upload blob using monolithic upload + LOCATION=$(curl -sI -X POST \ + -H "Authorization: Bearer $TOKEN" \ + https://registry-1.docker.io/v2/$REPO/blobs/uploads/ \ + | grep -i Location | tr -d '\r' | awk '{print $2}') + + curl -s -X PUT "$LOCATION&digest=$DIGEST" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @$CONFIG + fi + + # Step 4: Upload the manifest that references the config blob + MANIFEST=$(cat <` header. + + x-codeSamples: + - lang: Bash + label: cURL + source: | + # GET a manifest (by tag or digest) + curl -H "Authorization: Bearer $TOKEN" \ + -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ + https://registry-1.docker.io/v2/library/ubuntu/manifests/latest + parameters: + - name: name + in: path + required: true + description: Name of the target repository + example: library/ubuntu + schema: + type: string + - name: reference + in: path + required: true + description: Tag or digest of the target manifest + examples: + by-tag: + summary: Tag + value: latest + by-digest: + summary: Digest + value: sha256:abc123def456... + schema: + type: string + - name: Authorization + in: header + required: true + description: RFC7235-compliant authorization header (e.g., `Bearer `). + schema: + type: string + - name: Accept + in: header + required: false + description: | + Media type(s) the client supports for the manifest. + + The registry supports the following media types: + - application/vnd.docker.distribution.manifest.v2+json + - application/vnd.docker.distribution.manifest.list.v2+json + - application/vnd.oci.image.manifest.v1+json + - application/vnd.oci.image.index.v1+json + schema: + type: string + + responses: + "200": + description: Manifest fetched successfully. + headers: + Docker-Content-Digest: + description: Digest of the returned manifest content. + schema: + type: string + Content-Type: + description: Media type of the returned manifest. + schema: + type: string + content: + application/vnd.docker.distribution.manifest.v2+json: + schema: + type: object + required: + - schemaVersion + - mediaType + - config + - layers + properties: + schemaVersion: + type: integer + example: 2 + mediaType: + type: string + example: application/vnd.docker.distribution.manifest.v2+json + config: + type: object + properties: + mediaType: + type: string + example: application/vnd.docker.container.image.v1+json + size: + type: integer + example: 7023 + digest: + type: string + example: sha256:a3f3e...c1234 + layers: + type: array + items: + type: object + properties: + mediaType: + type: string + example: application/vnd.docker.image.rootfs.diff.tar.gzip + size: + type: integer + example: 32654 + digest: + type: string + example: sha256:bcf2...78901 + examples: + docker-manifest: + summary: Docker image manifest (schema v2) + value: + { + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 7023, + "digest": "sha256:123456abcdef..." + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:abcdef123456..." + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 16724, + "digest": "sha256:7890abcdef12..." + } + ] + } + + "400": + description: Invalid name or reference. + "401": + description: Authentication required. + "403": + description: Access denied. + "404": + description: Repository or manifest not found. + "429": + description: Too many requests. + + + put: + tags: + - Manifests + summary: Put image manifest + operationId: PutImageManifest + description: | + Upload an image manifest for a given tag or digest. This operation registers a manifest in a repository, allowing it to be pulled using the specified reference. + + This endpoint is typically used after all layer and config blobs have been uploaded to the registry. + + The manifest must conform to the expected schema and media type. For Docker image manifest schema version 2, use: + `application/vnd.docker.distribution.manifest.v2+json` + + Requires authentication via a bearer token with `push` scope for the target repository. + x-codeSamples: + - lang: Bash + label: cURL + source: | + # PUT a manifest (tag = latest) + curl -X PUT \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.docker.distribution.manifest.v2+json" \ + --data-binary @manifest.json \ + https://registry-1.docker.io/v2/library/ubuntu/manifests/latest + parameters: + - name: name + in: path + required: true + description: Name of the target Repository + example: library/ubuntu + schema: + type: string + - name: reference + in: path + required: true + description: Tag or digest to associate with the uploaded Manifest + examples: + by-tag: + summary: Tag + value: latest + by-digest: + summary: Digest + value: sha256:abc123def456... + schema: + type: string + - name: Authorization + in: header + required: true + description: RFC7235-compliant authorization header (e.g., `Bearer `). + schema: + type: string + - name: Content-Type + in: header + required: true + description: Media type of the manifest being uploaded. + schema: + type: string + example: application/vnd.docker.distribution.manifest.v2+json + + requestBody: + required: true + content: + application/vnd.docker.distribution.manifest.v2+json: + schema: + type: object + required: + - schemaVersion + - mediaType + - config + - layers + properties: + schemaVersion: + type: integer + example: 2 + mediaType: + type: string + example: application/vnd.docker.distribution.manifest.v2+json + config: + type: object + required: + - mediaType + - size + - digest + properties: + mediaType: + type: string + example: application/vnd.docker.container.image.v1+json + size: + type: integer + example: 7023 + digest: + type: string + example: sha256:123456abcdef... + layers: + type: array + items: + type: object + required: + - mediaType + - size + - digest + properties: + mediaType: + type: string + example: application/vnd.docker.image.rootfs.diff.tar.gzip + size: + type: integer + example: 32654 + digest: + type: string + example: sha256:abcdef123456... + + examples: + sample-manifest: + summary: Sample Docker image manifest (schema v2) + value: + { + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 7023, + "digest": "sha256:123456abcdef..." + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:abcdef123456..." + } + ] + } + + responses: + "201": + description: Manifest created successfully. + headers: + Docker-Content-Digest: + description: Digest of the stored manifest. + schema: + type: string + example: sha256:abcdef123456... + Location: + description: Canonical location of the uploaded manifest. + schema: + type: string + example: /v2/library/ubuntu/manifests/latest + Content-Length: + description: Always zero. + schema: + type: integer + example: 0 + "400": + description: Invalid name, reference, or manifest. + "401": + description: Authentication required. + "403": + description: Access denied. + "404": + description: Repository not found. + "405": + description: Operation not allowed. + "429": + description: Too many requests. + head: + tags: + - Manifests + summary: Check if manifest exists + operationId: HeadImageManifest + description: | + Use this endpoint to verify whether a manifest exists by tag or digest. + + This is a lightweight operation that returns only headers (no body). It is useful for: + - Checking for the existence of a specific image version + - Determining the digest or size of a manifest before downloading or deleting + + This endpoint requires authentication with pull scope. + + parameters: + - name: name + in: path + required: true + description: Name of the Repository + example: library/ubuntu + schema: + type: string + - name: reference + in: path + required: true + description: Tag or digest to check + examples: + by-tag: + summary: Tag + value: latest + by-digest: + summary: Digest + value: sha256:abc123def456... + schema: + type: string + - name: Authorization + in: header + required: true + schema: + type: string + description: Bearer token for authentication + - name: Accept + in: header + required: false + schema: + type: string + example: application/vnd.docker.distribution.manifest.v2+json + description: | + Media type of the manifest to check. The response will match one of the accepted types. + x-codeSamples: + - lang: Bash + label: cURL + source: | + # HEAD /v2/{name}/manifests/{reference} + curl -I \ + -H "Authorization: Bearer $TOKEN" \ + -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ + https://registry-1.docker.io/v2/library/ubuntu/manifests/latest + responses: + "200": + description: Manifest exists. + headers: + Content-Length: + description: Size of the manifest in bytes + schema: + type: integer + example: 7082 + Docker-Content-Digest: + description: Digest of the manifest + schema: + type: string + example: sha256:abc123... + Content-Type: + description: Media type of the manifest + schema: + type: string + example: application/vnd.docker.distribution.manifest.v2+json + "404": + description: Manifest not found. + "401": + description: Authentication required. + "403": + description: Access denied. + "429": + description: Too many requests. + delete: + tags: + - Manifests + summary: Delete image manifest + operationId: DeleteImageManifest + description: | + Delete an image manifest from a repository by digest. + + Only untagged or unreferenced manifests can be deleted. If the manifest is still referenced by a tag or another image, the registry will return `403 Forbidden`. + + This operation requires `delete` access to the repository. + parameters: + - name: name + in: path + required: true + description: Name of the repository + example: yourusername/helloworld + schema: + type: string + - name: reference + in: path + required: true + description: Digest of the manifest to delete (e.g., `sha256:...`) + example: sha256:abc123def456... + schema: + type: string + - name: Authorization + in: header + required: true + description: Bearer token with `delete` access + schema: + type: string + x-codeSamples: + - lang: Bash + label: cURL + source: | + # DELETE a manifest by digest + curl -X DELETE \ + -H "Authorization: Bearer $TOKEN" \ + https://registry-1.docker.io/v2/yourusername/helloworld/manifests/sha256:abc123def456... + responses: + "202": + description: Manifest deleted successfully. No content returned. + "401": + description: Authentication required. + "403": + description: Access denied. The manifest may still be referenced. + "404": + description: Manifest or repository not found. + "405": + description: Only digest-based deletion is allowed. + "429": + description: Too many requests. + /v2/{name}/blobs/uploads/: + post: + tags: + - Blobs + summary: Initiate blob upload or attempt cross-repository blob mount + operationId: InitiateBlobUpload + description: | + Initiate an upload session for a blob (layer or config) in a repository. + + This is the first step in uploading a blob. It returns a `Location` URL where the blob can be uploaded using `PATCH` (chunked) or `PUT` (monolithic). + + Instead of uploading a blob, a client may attempt to mount a blob from another repository (if it has read access) by including the `mount` and `from` query parameters. + + If successful, the registry responds with `201 Created` and the blob is reused without re-upload. + + If the mount fails, the upload proceeds as usual and returns a `202 Accepted`. + + You must authenticate with `push` access to the target repository. + x-codeSamples: + - lang: Bash + label: cURL (Initiate Standard Upload) + source: | + # Initiate a standard blob upload session + curl -i -X POST \ + -H "Authorization: Bearer $TOKEN" \ + https://registry-1.docker.io/v2/library/ubuntu/blobs/uploads/ + + - lang: Bash + label: cURL (Cross-Repository Blob Mount) + source: | + # Attempt a cross-repository blob mount + curl -i -X POST \ + -H "Authorization: Bearer $TOKEN" \ + "https://registry-1.docker.io/v2/library/ubuntu/blobs/uploads/?mount=sha256:abc123def456...&from=library/busybox" + + parameters: + - name: name + in: path + required: true + description: Name of the target repository + example: library/ubuntu + schema: + type: string + - name: mount + in: query + required: false + description: Digest of the blob to mount from another repository + schema: + type: string + example: sha256:abc123def456... + - name: from + in: query + required: false + description: Source repository to mount the blob from + schema: + type: string + example: library/busybox + - name: Authorization + in: header + required: true + schema: + type: string + description: Bearer token for authentication with `push` scope + + responses: + "201": + description: Blob successfully mounted from another repository. + headers: + Location: + description: URL where the mounted blob is accessible + schema: + type: string + example: /v2/library/ubuntu/blobs/sha256:abc123... + Docker-Content-Digest: + description: Canonical digest of the mounted blob + schema: + type: string + example: sha256:abc123... + Content-Length: + description: Always zero + schema: + type: integer + example: 0 + "202": + description: Upload initiated successfully (fallback if mount fails). + headers: + Location: + description: Upload location URL for `PATCH` or `PUT` requests + schema: + type: string + example: /v2/library/ubuntu/blobs/uploads/abc123 + Docker-Upload-UUID: + description: Server-generated UUID for the upload session + schema: + type: string + example: abc123 + Range: + description: Current upload byte range (typically `0-0` at init) + schema: + type: string + example: 0-0 + Content-Length: + description: Always zero + schema: + type: integer + example: 0 + "401": + description: Authentication required. + "403": + description: Access denied. + "404": + description: Repository not found. + "429": + description: Too many requests. + /v2/{name}/blobs/{digest}: + head: + tags: + - Blobs + summary: Check existence of blob + operationId: CheckBlobExists + description: | + Check whether a blob (layer or config) exists in the registry. + + This is useful before uploading a blob to avoid duplicates. + + If the blob is present, the registry returns a `200 OK` response with headers like `Content-Length` and `Docker-Content-Digest`. + + If the blob does not exist, the response will be `404 Not Found`. + x-codeSamples: + - lang: Bash + label: cURL + source: | + # HEAD to check if a blob exists + curl -I \ + -H "Authorization: Bearer $TOKEN" \ + https://registry-1.docker.io/v2/library/ubuntu/blobs/sha256:abc123... + parameters: + - name: name + in: path + required: true + description: Name of the Repository + example: library/ubuntu + schema: + type: string + - name: digest + in: path + required: true + description: Digest of the blob + schema: + type: string + example: sha256:abc123def4567890... + - name: Authorization + in: header + required: true + description: Bearer token with pull or push scope + schema: + type: string + example: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6... + + responses: + "200": + description: Blob exists + headers: + Content-Length: + description: Size of the blob in bytes + schema: + type: integer + example: 32654 + Docker-Content-Digest: + description: Digest of the blob + schema: + type: string + example: sha256:abc123def4567890... + Content-Type: + description: MIME type of the blob content + schema: + type: string + example: application/octet-stream + content: + application/json: + examples: + blob-check-request: + summary: Sample request + value: + method: HEAD + url: /v2/library/ubuntu/blobs/sha256:abc123def4567890... + headers: + Authorization: Bearer + Accept: '*/*' + blob-check-response: + summary: Sample 200 response headers + value: + status: 200 OK + headers: + Docker-Content-Digest: sha256:abc123def4567890... + Content-Length: 32654 + Content-Type: application/octet-stream + + "404": + description: Blob not found + "401": + description: Authentication required + "403": + description: Access denied + "429": + description: Too many requests + get: + tags: + - Blobs + summary: Retrieve blob + operationId: GetBlob + description: | + Download the blob identified by digest from the registry. + + Blobs include image layers and configuration objects. Clients must use the digest from the manifest to retrieve a blob. + + This endpoint may return a `307 Temporary Redirect` to a CDN or storage location. Clients must follow the redirect to obtain the actual blob content. + + The blob content is typically a gzipped tarball (for layers) or JSON (for configs). The MIME type is usually `application/octet-stream`. + x-codeSamples: + - lang: Bash + label: cURL + source: | + # GET (download) a blob + curl -L \ + -H "Authorization: Bearer $TOKEN" \ + https://registry-1.docker.io/v2/library/ubuntu/blobs/sha256:abc123... \ + -o layer.tar.gz + parameters: + - name: name + in: path + required: true + description: Repository Name + example: library/ubuntu + schema: + type: string + - name: digest + in: path + required: true + description: Digest of the Blob + schema: + type: string + example: sha256:abc123def456... + - name: Authorization + in: header + required: true + schema: + type: string + description: Bearer token with pull scope + example: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6... + + responses: + "200": + description: Blob content returned directly + headers: + Content-Length: + description: Size of the blob in bytes + schema: + type: integer + example: 32768 + Content-Type: + description: MIME type of the blob + schema: + type: string + example: application/octet-stream + Docker-Content-Digest: + description: Digest of the returned blob + schema: + type: string + example: sha256:abc123def456... + content: + application/octet-stream: + schema: + type: string + format: binary + examples: + small-layer: + summary: Example binary blob (gzipped tar layer) + value: "" + + "307": + description: Temporary redirect to blob location + headers: + Location: + description: Redirect URL for blob download (e.g., S3 or CDN) + schema: + type: string + example: https://cdn.docker.io/blobs/library/ubuntu/abc123... + "401": + description: Authentication required + "403": + description: Access denied + "404": + description: Blob not found + "429": + description: Too many requests + /v2/{name}/blobs/uploads/{uuid}: + get: + tags: + - Blobs + summary: Get blob upload status + operationId: GetBlobUploadStatus + description: | + Retrieve the current status of an in-progress blob upload. + + This is useful for: + - Resuming an interrupted upload + - Determining how many bytes have been accepted so far + - Retrying from the correct offset in chunked uploads + + The response includes the `Range` header indicating the byte range received so far, and a `Docker-Upload-UUID` for identifying the session. + x-codeSamples: + - lang: Bash + label: cURL + source: | + # GET upload status + curl -I \ + -H "Authorization: Bearer $TOKEN" \ + https://registry-1.docker.io/v2/library/ubuntu/blobs/uploads/abc123 + parameters: + - name: name + in: path + required: true + description: Repository Name + example : library/ubuntu + schema: + type: string + - name: uuid + in: path + required: true + description: Upload session UUID + schema: + type: string + example: abc123 + - name: Authorization + in: header + required: true + schema: + type: string + example: Bearer eyJhbGciOi... + + responses: + "204": + description: Upload in progress. No body is returned. + headers: + Range: + description: Current byte range uploaded (inclusive) + schema: + type: string + example: 0-16383 + Docker-Upload-UUID: + description: UUID of the upload session + schema: + type: string + example: abc123 + Location: + description: URL to continue or complete the upload + schema: + type: string + example: /v2/library/ubuntu/blobs/uploads/abc123 + "401": + description: Authentication required + "403": + description: Access denied + "404": + description: Upload session not found + "429": + description: Too many requests + + put: + tags: + - Blobs + summary: Complete blob upload + operationId: CompleteBlobUpload + description: | + Complete the upload of a blob by finalizing an upload session. + + This request must include the `digest` query parameter and optionally the last chunk of data. When the registry receives this request, it verifies the digest and stores the blob. + + This endpoint supports: + - Monolithic uploads (upload entire blob in this request) + - Finalizing chunked uploads (last chunk plus `digest`) + + x-codeSamples: + - lang: Bash + label: cURL + source: | + # PUT – complete upload (monolithic or final chunk) + curl -X PUT \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @layer.tar.gz \ + "https://registry-1.docker.io/v2/library/ubuntu/blobs/uploads/abc123?digest=sha256:abcd1234..." + + + parameters: + - name: name + in: path + required: true + description: Repository name + schema: + type: string + example: library/ubuntu + - name: uuid + in: path + required: true + description: Upload session UUID returned from the POST request + schema: + type: string + example: abc123 + - name: digest + in: query + required: true + description: Digest of the uploaded blob + schema: + type: string + example: sha256:abcd1234... + - name: Authorization + in: header + required: true + schema: + type: string + example: Bearer eyJhbGciOi... + + requestBody: + required: false + content: + application/octet-stream: + schema: + type: string + format: binary + examples: + layer-upload: + summary: Layer tarball blob + value: "" + + responses: + "201": + description: Upload completed successfully + headers: + Docker-Content-Digest: + description: Canonical digest of the stored blob + schema: + type: string + example: sha256:abcd1234... + Location: + description: URL where the blob is now accessible + schema: + type: string + example: /v2/library/ubuntu/blobs/sha256:abcd1234... + Content-Length: + description: Always zero for completed uploads + schema: + type: integer + example: 0 + "400": + description: Invalid digest or missing parameters + "401": + description: Authentication required + "403": + description: Access denied + "404": + description: Upload session not found + "416": + description: Requested range not satisfiable (if used in chunked mode) + "429": + description: Too many requests + + patch: + tags: + - Blobs + summary: Upload blob chunk + operationId: UploadBlobChunk + description: | + Upload a chunk of a blob to an active upload session. + + Use this method for **chunked uploads**, especially for large blobs or when resuming interrupted uploads. + + The client sends binary data using `PATCH`, optionally including a `Content-Range` header. + + After each chunk is accepted, the registry returns a `202 Accepted` response with: + - `Range`: current byte range stored + - `Docker-Upload-UUID`: identifier for the upload session + - `Location`: URL to continue the upload or finalize with `PUT` + x-codeSamples: + - lang: Bash + label: cURL + source: | + # PATCH – upload a chunk (first 64 KiB) + curl -X PATCH \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @chunk-0.bin \ + "https://registry-1.docker.io/v2/library/ubuntu/blobs/uploads/abc123" + parameters: + - name: name + in: path + required: true + description: Repository name + schema: + type: string + example: library/ubuntu + - name: uuid + in: path + required: true + description: Upload session UUID + schema: + type: string + example: abc123 + - name: Authorization + in: header + required: true + schema: + type: string + example: Bearer eyJhbGciOi... + - name: Content-Range + in: header + required: false + schema: + type: string + example: bytes 0-65535 + description: Optional. Byte range of the chunk being sent + + requestBody: + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + examples: + chunk-0: + summary: Upload chunk 0 of a blob + value: "" + + responses: + "202": + description: Chunk accepted and stored + headers: + Location: + description: URL to continue or finalize the upload + schema: + type: string + example: /v2/library/ubuntu/blobs/uploads/abc123 + Range: + description: Byte range uploaded so far (inclusive) + schema: + type: string + example: 0-65535 + Docker-Upload-UUID: + description: Upload session UUID + schema: + type: string + example: abc123 + "400": + description: Malformed content or range + "401": + description: Authentication required + "403": + description: Access denied + "404": + description: Upload session not found + "416": + description: Range error (e.g., chunk out of order) + "429": + description: Too many requests + delete: + tags: + - Blobs + summary: Cancel blob upload + operationId: CancelBlobUpload + description: | + Cancel an in-progress blob upload session. + + This operation discards any data that has been uploaded and invalidates the upload session. + + Use this when: + - An upload fails or is aborted mid-process + - The client wants to clean up unused upload sessions + + After cancellation, the UUID is no longer valid and a new `POST` must be issued to restart the upload. + + x-codeSamples: + - lang: Bash + label: cURL + source: | + # DELETE – cancel an upload session + curl -X DELETE \ + -H "Authorization: Bearer $TOKEN" \ + https://registry-1.docker.io/v2/library/ubuntu/blobs/uploads/abc123` + + parameters: + - name: name + in: path + required: true + description: Name of the repository + schema: + type: string + example: library/ubuntu + - name: uuid + in: path + required: true + description: Upload session UUID + schema: + type: string + example: abc123 + - name: Authorization + in: header + required: true + schema: + type: string + example: Bearer eyJhbGciOi... + + responses: + "204": + description: Upload session cancelled successfully. No body is returned. + headers: + Content-Length: + description: Always zero + schema: + type: integer + example: 0 + "401": + description: Authentication required + "403": + description: Access denied + "404": + description: Upload session not found + "429": + description: Too many requests + + +x-tagGroups: + - name: General + tags: + - overview + - authentication + - pull + - push + - delete + - name: API + tags: + - Manifests + - Blobs