# Registries

Registries hold the credentials Daytona uses to pull images from private container registries. A registry credential is a username and a password or token for one registry host. A project holds at most one credential per host, matched by the host in the image reference.

Stored credentials apply to registry [images](https://www.daytona.io/docs/en/v2/images.md). Daytona uses the credential to import a registry image and to pull it again for a Linux sandbox. Pulls from a host without a credential, and the base image of a Dockerfile build, are anonymous and work only for public images. macOS sandboxes ignore stored credentials.

Registry credentials work with [GitHub Container Registry](#github-container-registry), [Docker Hub](#docker-hub), [Google Artifact Registry](#google-artifact-registry), [Amazon ECR](#amazon-ecr), and any registry that accepts HTTP Basic authentication. Daytona encrypts the password or token at rest and never returns it. The provider kinds check the host when you store the credential, so a credential stored for the wrong host fails at that point instead of at pull time.

| **Concept**             | **Description**                                                                                                                              |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| **Registry credential** | A username and a password or token, stored for one registry host in a project.                                                               |
| **Host**                | The registry host, as **`host[:port]`**. Docker Hub hosts are stored as **`index.docker.io`**, and image references without a host use that credential. |
| **Kind**                | The authentication type. **`basic`** works with any registry that accepts HTTP Basic authentication. **`ghcr`**, **`gcr`**, and **`ecr`** also check the host. |
| **Anonymous pull**      | A pull from a host with no stored credential. It works only for public images.                                                               |
| **Rotation**            | Storing a credential for a host that already has one replaces it. The next pull uses the new credential.                                    |

1. You store a credential for a registry host: the host, a kind (**`basic`**, **`ghcr`**, **`gcr`**, or **`ecr`**), a username, and a password or token. The **`ghcr`**, **`gcr`**, and **`ecr`** kinds validate the host, so a misconfigured credential fails at write time rather than silently at pull time.
2. Daytona takes the host from the registry reference of an image import. A sandbox create takes a registered image name or ID; when the image was imported from a registry and is not present in the target region, Daytona pulls it again from that image's registry reference.
3. If a credential is stored for that host, the pull authenticates with it; otherwise the pull is anonymous. No opt-in is needed on individual requests.
4. To rotate a credential, store the same host again with the new password. The update replaces the credential in place and takes effect on the next pull.

A registry credential has the following fields:

| **Field**         | **Description**                                                                                                                                    |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`host`**        | Bare **`host[:port]`**, no scheme or path. One credential per host.                                                                                       |
| **`kind`**        | **`basic`** (default), **`ghcr`**, **`gcr`**, or **`ecr`**. **`ghcr`** requires **`ghcr.io`**; **`gcr`** requires a **`gcr.io`**, **`*.gcr.io`**, or **`*-docker.pkg.dev`** host; **`ecr`** requires an **`*.dkr.ecr.<region>.amazonaws.com`** host. |
| **`username`**    | Registry username, such as a GitHub username for **`ghcr`**, **`_json_key`** for **`gcr`**, or an AWS access key ID for **`ecr`**.                                    |
| **`password`**    | Password or token, at most 64 KiB, such as a service account JSON key for **`gcr`** or an AWS secret access key for **`ecr`**. Write-only: encrypted at rest and never returned. |
| **`description`** | Optional free-form note.                                                                                                                               |

## Use cases

Registry credentials are used when the images that sandboxes boot from are not public.

- **Private application images**: images that a CI pipeline builds and pushes to a private registry are imported and boot in sandboxes.
- **Images in a cloud provider's registry**: images stored in Amazon ECR or Google Artifact Registry are pulled with that provider's access keys, such as an AWS access key or a service account JSON key.
- **Private Docker Hub repositories**: a credential stored for Docker Hub also applies to image references without a host, such as **`my-org/my-image`**.
- **Self-hosted registries**: any registry that accepts HTTP Basic authentication works with a **`basic`** credential for its host.
- **Sandboxes in other regions**: a sandbox created in a region that does not have the image yet pulls it again from the registry with the stored credential.
- **Credential rotation**: a leaked or expired token is replaced in place, and the next pull uses the new one with no change to images or sandboxes.
- **Early credential checks**: the **`ghcr`**, **`gcr`**, and **`ecr`** kinds validate the host when the credential is stored. A credential for the wrong host fails at that point instead of at pull time.
- **Team access without shared tokens**: a credential is stored once per host for the whole project, and its token is encrypted and never returned. Team members import private images without holding the token.

## Container registries

Store a credential for a registry host. Storing a credential for a host that already has one replaces the stored credential. Daytona encrypts the password at rest, and reads return only the host, kind, username, description, and last update time.

1. Go to [Daytona Registries ↗](https://app.daytona.com/dashboard/registries)
2. Click <Button>Add Registry</Button>
3. Enter the following details:
        - <Button>Host</Button>: the registry host, as **`host[:port]`**
        - <Button>Kind</Button>: the registry kind: **Basic (username + password)**, **GitHub Container Registry**, **Google Container Registry**, or **Amazon ECR**
        - <Button>Username</Button>: the registry username
        - <Button>Password / Token</Button>: the registry password or token
        - <Button>Description</Button>: an optional description
4. Click <Button>Add</Button>


```bash
printf %s 'YOUR_REGISTRY_PASSWORD' | daytona registry set YOUR_REGISTRY_HOST \
  --username YOUR_REGISTRY_USERNAME \
  --password-stdin
```

| **Flag** | **Type** | **Description** |
| --- | --- | --- |
| **`--username`** | string | Sets the registry username. This flag is required. |
| **`--password-stdin`** | bool | Reads the password or token from standard input. Pass exactly one of **`--password-stdin`** and **`--password`**. |
| **`--password`** | string | Sets the password or token on the command line. The value is visible in the process list and shell history. Prefer **`--password-stdin`**. |
| **`--kind`** | string | Sets the registry kind: **`basic`**, **`ghcr`**, **`gcr`**, **`ecr`**, or **`ecr_role`**. **`ecr_role`** takes the IAM role ARN as **`--username`** and no password. The default is **`basic`**, or the stored kind if the host already has a credential. |
| **`--description`** | string | Sets the description. An empty string clears the stored description. The default is the stored description if the host already has a credential. |


```bash
curl 'https://api.daytona.com/registries/YOUR_REGISTRY_HOST' \
  --request PUT \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "username": "YOUR_REGISTRY_USERNAME",
  "password": "YOUR_REGISTRY_PASSWORD"
}'
```

| **Field** | **Type** | **Description** |
| --- | --- | --- |
| **`username`** | string | Sets the registry username. This field is required. |
| **`password`** | string | Sets the password or token, at most 64 KiB. This field is required for every kind except **`ecr_role`**, which ignores it. |
| **`kind`** | string | Sets the registry kind: **`basic`**, **`ghcr`**, **`gcr`**, **`ecr`**, or **`ecr_role`**. For **`ecr_role`**, **`username`** is the IAM role ARN that Daytona assumes, with the organization ID as the external ID. If omitted, the kind resets to **`basic`**. |
| **`description`** | string | Sets a free-form note. If omitted, the stored description is cleared. |

- Each request replaces the whole credential.


### GitHub Container Registry


```bash
printf %s 'YOUR_GITHUB_PAT' | daytona registry set ghcr.io \
  --kind ghcr \
  --username YOUR_GITHUB_USERNAME \
  --password-stdin
```


```bash
curl 'https://api.daytona.com/registries/ghcr.io' \
  --request PUT \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "kind": "ghcr",
  "username": "YOUR_GITHUB_USERNAME",
  "password": "YOUR_GITHUB_PAT"
}'
```


### Docker Hub

Store a credential for Docker Hub. Daytona stores Docker Hub credentials under the host index.docker.io. The hosts docker.io, registry-1.docker.io, and registry.hub.docker.com refer to the same credential, and image references without a registry host, such as python:3.12, use it.


```bash
printf %s 'YOUR_DOCKERHUB_TOKEN' | daytona registry set docker.io \
  --username YOUR_DOCKERHUB_USERNAME \
  --password-stdin
```


```bash
curl 'https://api.daytona.com/registries/docker.io' \
  --request PUT \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "username": "YOUR_DOCKERHUB_USERNAME",
  "password": "YOUR_DOCKERHUB_TOKEN"
}'
```


### Google Artifact Registry


```bash
# The password is the service account JSON key
daytona registry set us-docker.pkg.dev \
  --kind gcr \
  --username _json_key \
  --password-stdin < service-account-key.json
```


```bash
curl 'https://api.daytona.com/registries/us-docker.pkg.dev' \
  --request PUT \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "kind": "gcr",
  "username": "_json_key",
  "password": "YOUR_SERVICE_ACCOUNT_JSON_KEY"
}'
```


### Amazon ECR

Store AWS access keys for an Amazon ECR registry. Daytona checks the keys with AWS STS on store and refuses keys that AWS rejects or that belong to an account other than the host's. A failed check leaves any existing credential for the host unchanged.


```bash
printf %s 'YOUR_AWS_SECRET_ACCESS_KEY' | daytona registry set AWS_ACCOUNT_ID.dkr.ecr.AWS_REGION.amazonaws.com \
  --kind ecr \
  --username YOUR_AWS_ACCESS_KEY_ID \
  --password-stdin
```


```bash
curl 'https://api.daytona.com/registries/AWS_ACCOUNT_ID.dkr.ecr.AWS_REGION.amazonaws.com' \
  --request PUT \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "kind": "ecr",
  "username": "YOUR_AWS_ACCESS_KEY_ID",
  "password": "YOUR_AWS_SECRET_ACCESS_KEY"
}'
```


## Use private registry images

Import a private image into the project, then create sandboxes from it by name. The import authenticates with the credential stored for the host in the image reference. A Linux sandbox create takes a registered [image](https://www.daytona.io/docs/en/v2/images.md) name or ID, which must be ready first.


```python
from daytona import Daytona

daytona = Daytona()

# The pull authenticates with the credential stored for ghcr.io
image = daytona.v2.images.pull("ghcr.io/acme/private-runtime:latest", name="private-runtime")

# The pull returns once it is queued; each wait returns after 30 seconds at most
while image.status not in ("ready", "failed"):
    image = daytona.v2.images.get(image.id, wait="ready")
if image.status == "failed":
    raise RuntimeError(image.error)

sandbox = daytona.v2.create("private-runtime")
```


```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// The pull authenticates with the credential stored for ghcr.io
let image = await daytona.v2.images.pull('ghcr.io/acme/private-runtime:latest', { name: 'private-runtime' })

// The pull returns once it is queued; each wait returns after 30 seconds at most
while (image.status !== 'ready' && image.status !== 'failed') {
  image = await daytona.v2.images.get(image.id, { wait: 'ready' })
}
if (image.status === 'failed') throw new Error(image.error ?? 'image build failed')

const sandbox = await daytona.v2.create('private-runtime')
```


```bash
# The import authenticates with the credential stored for ghcr.io
daytona image create private-runtime --ref ghcr.io/acme/private-runtime:latest
daytona sandbox create --image private-runtime
```

- **`daytona image create`** waits up to **`--timeout`** and exits with an error if the image fails.
- The default **`--timeout`** is **`10m`**.


```bash
# The import authenticates with the credential stored for ghcr.io
curl 'https://api.daytona.com/images' \
  --request POST \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{ "name": "private-runtime", "source": { "type": "registry", "ref": "ghcr.io/acme/private-runtime:latest" } }'

# Returns on ready or failed, or after timeout_ms; repeat while status is pending or building
curl 'https://api.daytona.com/images/{imageId}?wait=ready&timeout_ms=60000' \
  --header 'Authorization: Bearer YOUR_API_KEY'

curl 'https://api.daytona.com/sandboxes' \
  --request POST \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{ "image": "private-runtime" }'
```

- **`POST /images`** returns once the pull is queued.
- **`{imageId}`** is the **`image_id`** field of the **`POST /images`** response.
- **`timeout_ms`** defaults to 30 seconds and is at most 60 seconds.


## Operations

### List registries


```bash
daytona registry list
```


```bash
curl 'https://api.daytona.com/registries' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```


### Get a registry


```bash
daytona registry get ghcr.io
```


```bash
curl 'https://api.daytona.com/registries/ghcr.io' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```


### Delete a registry

Delete the credential for a registry host. Later pulls from that host are anonymous.


```bash
daytona registry delete ghcr.io
```

- The command asks for confirmation.
- A non-interactive shell requires **`--yes`**.


```bash
curl 'https://api.daytona.com/registries/ghcr.io' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

- Deleting a host that has no stored credential succeeds.