# Images and the registry

Every workspace has its own private namespace in the isolance container registry, `registry.caas.sh/<tenant-id>/`. Push an image there with Docker, then run it in a sandbox or an MCP server. Only your workspace's API keys, and your workspace's own sandboxes, can pull it.

This guide covers logging in, naming, building and pushing, running an image by its digest, listing tags, pricing and deleting images. The CLI examples assume you've installed and configured `caas` (see [The CLI](/docs/cli)).

## Log in to the registry

The registry takes the same API keys as the API. Docker logs in with your workspace's tenant ID as the username and an API key as the password.

### Create an API key

API keys are created only in the console:

1. Sign in to the [console](https://console.isolance.com/) and open **Settings**.
2. Under **Workspace**, copy the **Tenant ID**.
3. Under **API keys**, enter a **Key name**, such as `docker on my laptop`, and choose **Create key**.
4. Copy the key. It starts with `ck_`, and the console shows it only once.

Owners and operators can create keys; viewers can't. A new key can push and pull images.

### Log in with Docker

```sh
export CAAS_TENANT=<tenant-id>
export CAAS_API_KEY=<api-key>

echo "$CAAS_API_KEY" | docker login registry.caas.sh --username "$CAAS_TENANT" --password-stdin
```

### Log in with the CLI

If you've saved your tenant ID and key with `caas configure`, log Docker in with them:

```sh
caas registry login
```

It runs `docker login registry.caas.sh` with your tenant ID as the username and your saved key as the password.

### What a key can do

| Access | Needs | Lets the key |
| --- | --- | --- |
| `registry:read` | Any key from the console | Pull images and list tags |
| `registry:write` | Any key from the console, while its creator is an owner or operator | Push and delete images, and pull them |

A key only ever reaches its own workspace's namespace. If the member who created a key becomes a viewer, the key can still pull but no longer push. A revoked key stops working within a minute. See [The API](/docs/api) for scopes and roles.

## Name your images

An image in your registry is named `registry.caas.sh/<tenant-id>/<name>:<tag>`, for example `registry.caas.sh/<tenant-id>/agent:1.2.0`.

- `<tenant-id>` is your workspace's Tenant ID. You can only push to and pull from your own.
- `<name>` is one to four parts separated by `/`, such as `agent` or `tools/python`. Each part is lowercase letters and digits, which `.`, `_`, `__` or hyphens may join, as in `my-agent` or `node_24.tools`. `<tenant-id>/<name>` is at most 255 characters.
- `<tag>` is up to 128 letters, digits, `_`, `.` and `-`, and doesn't start with `.` or `-`.

## Build and push an image

Sandboxes run `linux/amd64` images. Build for that platform, even on an Arm Mac, then push:

```sh
docker build --platform linux/amd64 -t registry.caas.sh/$CAAS_TENANT/agent:1.2.0 .
docker push registry.caas.sh/$CAAS_TENANT/agent:1.2.0
```

`docker push` ends with the image's digest:

```text
1.2.0: digest: sha256:4f1c…e9a2 size: 1784
```

A multi-platform image works too, as long as it includes `linux/amd64`.

To look up a tag's digest later, ask the registry for its manifest's headers:

```sh
curl -sI -u "$CAAS_TENANT:$CAAS_API_KEY" \
  https://registry.caas.sh/v2/$CAAS_TENANT/agent/manifests/1.2.0 | grep -i docker-content-digest
```

To pull an image to your own machine, log in, then use `docker pull` as usual:

```sh
docker pull registry.caas.sh/$CAAS_TENANT/agent:1.2.0
```

### What the image needs

- **`/bin/sh`, `base64`, `tar` and `gzip`.** isolance runs your commands, moves files and takes snapshots inside the image's container, with these tools.
- **Layers under about 100 MB.** `docker push` uploads each layer in one request, and a layer over about 100 MB can exceed the registry's upload limit. Split large installs across several `RUN` instructions to keep each layer smaller.

## Run your image

Sandboxes and MCP servers run an image pinned to its digest, `registry.caas.sh/<tenant-id>/<name>@sha256:…`, so what runs is exactly the image you pushed, even if you push the tag again later. The CLI looks tags up for you; the console and the API take the digest. To copy one, open **Registry** in the console, then the repository: each image shows its reference, shortened, with a copy icon for the whole reference.

Before anything is created, isolance checks that the digest exists in your namespace.

### Sizes

A sandbox or MCP server on your own image needs size M (1/2 vCPU, 4 GiB of memory, 8 GB of disk) or larger, or a custom size with at least 4 GiB of memory and 8 GB of disk. XS and S are refused with `sandboxes on your own image need size M or larger`. See [Sizes](/docs/sandboxes#sizes).

### In a sandbox

A sandbox runs its `command` at every start, in place of the image's entrypoint and command; without a `command`, the image's own entrypoint and command run, and an image without any runs nothing until you send it commands. These, and every command you run in the sandbox, run in the image's container, where the sandbox's working directory, `/workspace`, is mounted.

**In the console:**

1. Open **Sandboxes** and choose **New sandbox**.
2. Under **1. Choose an image**, pick **Your image**.
3. In **Image**, enter the digest reference, `registry.caas.sh/<tenant-id>/agent@sha256:…`.
4. Optionally, in **Command**, enter what the sandbox runs at every start, such as `node agent.js`. Leave it empty to run the image's own entrypoint and command.
5. Under **2. Configure**, pick size **M** or larger, and choose **Create sandbox**.

**With the CLI**, `--image` takes a tag or a digest. `caas run` creates a sandbox without a `command`, so the image's own entrypoint and command, if it has any, run at its start; then it runs `node agent.js` in the sandbox once and prints its output. To make `node agent.js` the sandbox's `command`, pass `--command`:

```sh
caas run --image agent:1.2.0 -- node agent.js
caas run --image agent@sha256:<digest> --size l -- node agent.js
caas sandboxes create --image agent:1.2.0 --command "node agent.js"
```

You can write the image as `NAME[:TAG]` or `NAME@sha256:DIGEST`, optionally under `registry.caas.sh/` or `registry.caas.sh/<tenant-id>/`. Without a tag, the CLI uses `latest`. It looks the tag up in the registry, which needs `registry:read`, and creates the sandbox with the digest. Images from other registries or other workspaces are refused with `push the image to registry.caas.sh/<tenant-id>/NAME first`.

**With the API**, put the digest reference in `image`:

```sh
curl https://api.caas.sh/v1/sandboxes \
  -H "Authorization: Bearer $CAAS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "image": "registry.caas.sh/<tenant-id>/agent@sha256:<digest>",
    "command": ["node", "agent.js"],
    "size": "m"
  }'
```

The API doesn't take tags. A reference in another workspace's namespace gets `400` with `image must belong to this tenant`, and a digest the registry doesn't hold gets `400` with `image digest not found in registry.caas.sh`. Without `command`, the image's own entrypoint and command run. [Sandboxes](/docs/sandboxes#fields) covers the other fields.

### In an MCP server

`caas mcp create --image server:1.2.3 --http 3000` creates an MCP server from your image, serving MCP over HTTP on port 3000. Without `--command`, the image's own entrypoint and command start the server. [MCP servers](/docs/mcp) covers servers on your own image.

## Private pulls

Your images never need to be public, and your sandboxes never need your API key to pull them:

- When a sandbox or an MCP server starts on your image, isolance pulls it from `registry.caas.sh` for you, with a short-lived credential that can only pull that image. You don't log in from inside the sandbox.
- A sandbox can always reach `registry.caas.sh`. You don't add it to the sandbox's allowed outbound hosts.
- Traffic between your sandboxes and `registry.caas.sh` doesn't bill as egress. Pulls count only as registry pulls (see [Pricing](#pricing)).
- Nobody outside your workspace can pull your images: the registry serves a key only its own workspace's namespace, and refuses requests without a key.

## List tags

The registry answers the standard tags list for one image name:

```sh
curl -s -u "$CAAS_TENANT:$CAAS_API_KEY" \
  https://registry.caas.sh/v2/$CAAS_TENANT/agent/tags/list
```

```json
{ "name": "<tenant-id>/agent", "tags": ["1.1.0", "1.2.0", "latest"] }
```

It returns up to 50 tags at a time; add `?n=100` for more. When there are more, the response's `Link` header holds the URL of the next page. Each request bills as one list.

Docker's catalog (`/v2/_catalog`) is refused. To see every image name in your workspace, open **Registry** in the console, or ask the API with a key that has `registry:read`; these reads don't bill as lists:

```sh
curl -s https://api.caas.sh/v1/images -H "Authorization: Bearer $CAAS_API_KEY"
```

It answers each repository with its tag and image counts, the bytes it stores and when it was last pushed. `GET /v1/images/<name>` lists one repository's images, newest first, each with its digest, tags, platforms, size and when it was pushed.

## Pricing

| Meter | Price | What counts |
| --- | --- | --- |
| Registry storage | On the [pricing page](/pricing.html) | Everything stored in your namespace: manifests, configs and layers, measured every hour |
| Registry pulls | On the [pricing page](/pricing.html) | Each image manifest fetched, by Docker or by your sandboxes. A multi-platform image counts two: its index and the platform's manifest. `HEAD` requests and layer downloads are free. |
| Registry lists | On the [pricing page](/pricing.html) | Each tags listing |

A GB is 1,000,000,000 bytes, and a month is 30 days. A sandbox on your own image pulls it each time it starts, and each of those pulls bills to your workspace. Registry usage draws from the workspace's balance like everything else; see [Billing and usage](/docs/billing).

## Delete images

The simplest way is **Registry** in the console: open the repository, then choose **Delete tag** or **Delete image** from the image's ••• menu. The console asks first and says which of your sandboxes and MCP servers run it. Deleting an image there also frees the storage its layers used, unless another image still uses them.

The API does the same with a key that has `registry:write`:

```sh
# Remove one tag; the image stays under its digest and any other tags.
curl -X DELETE https://api.caas.sh/v1/images/agent:1.1.0 -H "Authorization: Bearer $CAAS_API_KEY"
# Remove the image, every tag naming it, and the layers no other image uses.
curl -X DELETE https://api.caas.sh/v1/images/agent@sha256:<digest> -H "Authorization: Bearer $CAAS_API_KEY"
```

A sandbox pulls its image every time it starts. Don't delete an image that a sandbox still uses.

The registry's own Docker `DELETE` requests work too, as below, but they leave the layers behind.

### Delete a tag

```sh
curl -X DELETE -u "$CAAS_TENANT:$CAAS_API_KEY" \
  https://registry.caas.sh/v2/$CAAS_TENANT/agent/manifests/1.1.0
```

This removes only the tag. The image stays, under its digest and any other tags.

### Delete an image

```sh
curl -X DELETE -u "$CAAS_TENANT:$CAAS_API_KEY" \
  https://registry.caas.sh/v2/$CAAS_TENANT/agent/manifests/sha256:<digest>
```

This removes the image's manifest and every tag that points to it.

### Free the storage it used

Deleting a manifest doesn't delete its layers, which keep counting toward registry storage. To free them:

1. Before you delete the image, read its manifest and note the `config.digest` and each `layers[].digest`. If it's an index (a multi-platform image), it lists other manifests under `manifests`: read each of those too, and delete them along with the index.

   ```sh
   curl -s -u "$CAAS_TENANT:$CAAS_API_KEY" \
     https://registry.caas.sh/v2/$CAAS_TENANT/agent/manifests/sha256:<digest>
   ```

2. Delete the image, as above.
3. Delete each layer and config that no other image in your namespace uses:

   ```sh
   curl -X DELETE -u "$CAAS_TENANT:$CAAS_API_KEY" \
     https://registry.caas.sh/v2/$CAAS_TENANT/agent/blobs/sha256:<layer-digest>
   ```

Docker can share a layer between image names when it pushes, so an image under another name may use it too. Deleting a layer that another image still uses breaks that image.

## Troubleshooting

| What you see | What to do |
| --- | --- |
| `docker login` fails with `unauthorized` | Use your Tenant ID as the username and an API key from the same workspace as the password. Check that the key isn't revoked. |
| `docker push` is refused | Push to `registry.caas.sh/<tenant-id>/…` with your own Tenant ID, with a key whose creator is still an owner or operator. |
| A large layer fails to upload | Keep each layer under about 100 MB. |
| `429` with `TOOMANYREQUESTS` | One address or one key sent more than about 1,200 requests in a minute. Wait a minute, then retry. |
| `image tag not found` from the CLI | Push the tag first, or check its spelling. |
| `image digest not found in registry.caas.sh` | The digest is wrong, or the image was deleted. Copy it again from `docker push`. |
| `image must belong to this tenant` | The reference names another workspace's namespace. Push the image to yours. |
| `sandboxes on your own image need size M or larger` | Pick M or larger, or a custom size with at least 4 GiB and 8 GB. |
