# Images

workbenches.dev runs an OCI image registry at `images.workbenches.dev` for Workbench runtime images. Repository names are `<organization>/<image>`. Push with `wb image push` or any OCI client; see [Publishing](/docs/authoring/publishing#publish-an-image).

## Who can pull

An image is private until a public Workbench references it. A repository is pullable anonymously only while at least one public, approved version in that organization names an image in it. Otherwise a pull needs:

- an API key of the owning organization with `catalog:read` or `packages:write`, sent as the `Basic` password, or
- a pull token from `POST /v1/pulls`, sent as the `Basic` password with any username.

Otherwise the registry answers 401 with a `Basic` challenge. Cloud runs pull internal images of their own organization with a short-lived pull token; another organization's internal image fails the run.

## Who can push

The registry issues its own token. A key needs `packages:write` and an owning user to receive push scope, and push also needs a signed-in account. Tokens carry the key's organization.

## List images

`GET /v1/images`

Open. With `organization_id`, a key with `catalog:read` or a signed-in member.

| Query             | Type | Meaning                     |
| ----------------- | ---- | --------------------------- |
| `organization_id` | UUID | This organization's images. |
| `publisher_slug`  | slug | A publisher's images.       |

Response `200`:

```json
{
    "images": [
        {
            "id": "uuid",
            "name": "project-core",
            "status": "active",
            "reference": "images.workbenches.dev/acme/project-core",
            "storage_bytes": 734003200,
            "publisher": {
                "id": "uuid",
                "slug": "acme",
                "name": "Acme",
                "accent": null,
                "logo_url": null
            },
            "tags": [
                {
                    "name": "0.4.0",
                    "digest": "sha256:...",
                    "media_type": "application/vnd.oci.image.manifest.v1+json",
                    "manifest_size": 1234,
                    "updated_at": "..."
                }
            ],
            "created_at": "...",
            "updated_at": "..."
        }
    ]
}
```

`status` is `active`, `unlisted`, or `deprecated`.

## Mint a pull credential

`POST /v1/pulls` with `{ "repository": "acme/project-core" }`

Key `catalog:read` of the organization that owns the repository. The image must exist.

Response `200`:

```json
{ "username": "...", "password": "...", "expires_at": "..." }
```

The password is a signed token, valid for 10 minutes, that grants pull on that one repository and nothing else.

```sh
printf "%s" "$PASSWORD" | docker login images.workbenches.dev -u "$USERNAME" --password-stdin
docker pull images.workbenches.dev/acme/project-core:0.4.0
```

## OCI Distribution

The registry implements the OCI Distribution routes under `images.workbenches.dev/v2`. Errors use the OCI error format.

| Method                  | Path                                         | Auth                                                | Purpose                                                     |
| ----------------------- | -------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------- |
| GET                     | `/v2/`                                       | Open                                                | Ping. Answers 401 with a `WWW-Authenticate` challenge.      |
| GET                     | `/v2/auth`                                   | Open; send `Basic` or `Bearer` credentials for push | Token endpoint. A 5 minute signed token.                    |
| POST                    | `/v2/:publisher/:image/blobs/uploads`        | Push                                                | Start a blob upload, or mount with `?mount=`.               |
| GET, PATCH, PUT, DELETE | `/v2/:publisher/:image/blobs/uploads/:id`    | Push                                                | Status, append, complete (`?digest=`), or cancel an upload. |
| GET, HEAD               | `/v2/:publisher/:image/blobs/:digest`        | Pull                                                | Fetch a blob.                                               |
| GET, HEAD               | `/v2/:publisher/:image/manifests/:reference` | Pull                                                | Fetch a manifest by tag or digest.                          |
| PUT                     | `/v2/:publisher/:image/manifests/:reference` | Push                                                | Push a manifest.                                            |
| GET                     | `/v2/:publisher/:image/tags/list`            | Pull                                                | List tags.                                                  |

### Upload limits

A blob sent in one request may be any size the request limit allows. A chunked upload follows these rules: every chunk after the first must match the first chunk's size, which must be at least 5 MiB, and only the last chunk may be smaller. Upload responses send `OCI-Chunk-Min-Length: 5242880`. A chunk that breaks the rule answers `400 BLOB_UPLOAD_INVALID` before anything is stored. A blob is at most 5 GiB.

Direct client pushes are subject to the registry edge's per-request body limit. Use `wb image push` for images with large layers, because it controls the chunk size.
