DOCSImages and the registry
DOCS

Images and the registry

Every workspace has its own private namespace in the caas.sh container registry, registry.caas.sh/<tenant-id>/. Push an image there with Docker, then run it in a sandbox, an MCP server or a workflow step. 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).

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 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

shell
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:

shell
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

AccessNeedsLets the key
registry:readAny key from the consolePull images and list tags
registry:writeAny key from the console, while its creator is an owner or operatorPush 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 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:

shell
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:

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:

shell
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:

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

What the image needs

  • /bin/sh, base64, tar and gzip. caas.sh runs your commands, moves files and takes snapshots inside the image's container, with these tools.

  • Node, for a stdio MCP server. A server that speaks MCP over stdio runs behind caas.sh's bridge, which runs on Node. A server that serves MCP over HTTP doesn't need it.

  • 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, MCP servers and workflow steps 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.

Before anything is created, caas.sh 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.

A workflow machine's size is rounded up to at least 4 GiB of memory and 8 GB of disk when it runs your own image.

In a sandbox

The command you give replaces the image's entrypoint and command, and a sandbox on your own image needs one. It runs in /workspace, which is mounted into the image's container.

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. In Command, enter what the sandbox runs, such as node agent.js.

  5. Under 2. Configure, pick size M or larger, and choose Create sandbox.

With the CLI, --image takes a tag or a digest:

shell
caas run --image agent:1.2.0 -- node agent.js
caas run --image agent@sha256:<digest> --size l -- 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 the source:

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

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. Sandboxes covers the other fields.

In an MCP server

caas mcp deploy --image server:1.2.3 deploys an MCP server from your image. Without --command, the image's own entrypoint and command start the server. MCP servers covers stdio and HTTP servers on your own image.

In a workflow step

Set image at the top of the workflow file for every step, under a named sandbox, or on a step that has its own sandbox. A step's image replaces the file's:

yaml
workflow: smoke-test
image: registry.caas.sh/agent:1.2.0
steps:
  - id: agent-version
    name: Check the agent image
    run: node --version

  - id: python-version
    name: Check the Python image
    image: registry.caas.sh/tools/python:3.12
    run: python3 --version

In a workflow file, image takes registry.caas.sh/NAME:TAG or registry.caas.sh/<tenant-id>/NAME:TAG, or either pinned with @sha256:…. Without a tag, the CLI uses latest.

  • CLI: caas workflows run smoke-test.yaml pins each tag to its digest before it starts the run.

  • Console: New run only accepts images pinned to a digest, registry.caas.sh/<tenant-id>/NAME@sha256:….

  • API: the API doesn't look tags up. Map each image the file names in any other form than registry.caas.sh/<tenant-id>/NAME@sha256:… to that digest reference in images (see The API).

A resumed run uses the same pinned images. See Workflows.

Private pulls

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

  • When a sandbox, an MCP server or a workflow step starts on your image, caas.sh 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).

  • 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:

shell
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.

The registry doesn't list your workspace's image names: it refuses /v2/_catalog. Keep track of the names you push.

Pricing

MeterPriceWhat counts
Registry storage$0.025 / GB-monthEverything stored in your namespace: manifests, configs and layers, measured every hour
Registry pulls$0.01 / 1,000 pullsEach 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$0.005 / 1,000 listsEach tags listing

A GB is 1,000,000,000 bytes, and a month is 30 days. A 2 GB image kept for a month costs $0.05. 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.

Delete images

Docker can't delete images from a registry, so send the registry's DELETE requests with curl and a key that has registry:write. Each answers 202.

A sandbox pulls its image every time it starts, and a resumed workflow run pulls the images it started with. Don't delete an image that one of them still uses.

Delete a tag

shell
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

shell
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.

    shell
    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:

    shell
    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 seeWhat to do
docker login fails with unauthorizedUse 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 refusedPush 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 uploadKeep each layer under about 100 MB.
429 with TOOMANYREQUESTSOne 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 CLIPush the tag first, or check its spelling.
image digest not found in registry.caas.shThe digest is wrong, or the image was deleted. Copy it again from docker push.
image must belong to this tenantThe reference names another workspace's namespace. Push the image to yours.
sandboxes on your own image need size M or largerPick M or larger, or a custom size with at least 4 GiB and 8 GB.