# Custom Images

Every image-based template names a normalized, SHA-256 digest-pinned OCI image.
Sandbox0 accepts public images and team-scoped private images and imports their
RootFS into the regional block store before runtime use. Mutable tags are
rejected rather than resolved during template creation.

The Nomad task driver does not pull the template image. Manager's durable OCI
importer authenticates to the registry, unpacks the digest-pinned RootFS,
injects the pinned `procd` artifact, builds the block artifact, and publishes it
to the regional S3-compatible RootFS store.

<Callout variant="info">
If a template runs [Sandbox Functions](/docs/sandbox/functions), its image must
include `python3` in `PATH` and use Python 3.9 or later. Sandbox0 injects the
function runner, but the runner uses the template's Python interpreter.
</Callout>

## Public Images

Reference a public image by its canonical registry, repository, and digest:

```yaml
spec:
    mainContainer:
        image: docker.io/library/python@sha256:<64-hex-digest>
        resources:
            memory: 2Gi
```

The name must already be normalized. For example, use
`docker.io/library/python@sha256:...`, not the Docker shorthand `python@...`.
A registry tag change therefore cannot alter either a queued import or an
already committed RootFS generation.

## Private Image Workflow

When you have a Dockerfile or another reproducible build, use the `s0` CLI.

### 1. Build

```bash
s0 template image build . -t my-app:v1.2.3
```

This command wraps the local Docker build. You can also use `docker build`
directly.

### 2. Push

```bash
s0 template image push my-app:v1.2.3 -t my-app:v1.2.3
```

The CLI:

1. calls `POST /api/v1/registry/credentials`;
2. receives a provider-specific, team-scoped destination and credential;
3. retags the local image; and
4. pushes with the local Docker daemon.

Use the returned **Template image reference** rather than reconstructing a
provider path yourself.

Resolve that pushed reference to the registry-reported manifest digest and
replace its tag with `@sha256:<digest>`. Keep the exact pull hostname returned
by Sandbox0; push and manager-side pull endpoints may differ. Standard registry
tools such as `docker buildx imagetools inspect` or `skopeo inspect` can report
the manifest digest.

### 3. Create The Template

```yaml
# template.yaml
spec:
    mainContainer:
        image: registry.example.com/t-<team-key>/my-app@sha256:<64-hex-digest>
        resources:
            memory: 8Gi
```

```bash
s0 template create --id my-app --spec-file template.yaml
s0 sandbox create --template my-app
```

If the desired environment already exists in a sandbox, use
`/api/v1/templates/from-sandbox` instead. That route retains the sandbox's
immutable block-COW generation and does not require a new image push.

## Registry Providers

Sandbox0 supports these manager-side registry credential providers:

| Provider | Backend |
| --- | --- |
| `builtin` | A directly configured OCI Registry v2 endpoint |
| `aws` | Amazon ECR, optionally with AssumeRole and a team-scoped session policy |
| `gcp` | Google Artifact Registry or GCR |
| `azure` | Azure Container Registry |
| `aliyun` | Alibaba Cloud Container Registry with repository-scoped STS |
| `harbor` | Harbor |

External push and node/private pull endpoints may differ. Configure
`push_registry` for developers and CI, `pull_registry` for manager's durable
RootFS importer, and `internal_registry` for server-side registry operations
when needed. Template image references use the pull address returned by the
credential API.

Self-hosted registry credentials are direct manager configuration inputs. Each
secret may be supplied by its documented value field or file field, but never
both. The durable importer uses `registry.pull_credentials_file`, a root-owned
Docker `config.json`/`dockerconfigjson` file, when the source registry requires
static pull authentication.

```yaml
registry:
    provider: builtin
    push_registry: registry.example.com
    pull_registry: registry.internal.example.com
    pull_credentials_file: /etc/sandbox0/registry/pull-config.json
    builtin:
        username_file: /etc/sandbox0/registry/push-username
        password_file: /etc/sandbox0/registry/push-password
```

The files must be readable by manager and must not be exposed to a sandbox.
There is no per-allocation registry secret and no runtime-mounted registry
credential.

## Import Safety

The durable importer is active-active across manager replicas. PostgreSQL
leases exactly one builder for an import attempt. Publication uses conditional
object creation and compare-and-swap state transitions so retries cannot
silently replace a committed artifact.

The importer validates:

- canonical OCI digest and target platform;
- bounded layer count and unpacked size;
- the pinned `procd` digest and protocol;
- the block filesystem artifact; and
- conditional publication to the configured RootFS object store.

An unavailable registry or failed import leaves the sandbox creation operation
unready or failed; the task driver never falls back to pulling and running an
unattested tag.

## Next Steps

<CardGroup>
  <Card title="Template Configuration" href="/docs/sandbox/template/configuration" cta="Continue">
    Configure image, resources, environment, and network defaults.
  </Card>
  <Card title="Create From A Sandbox" href="/docs/sandbox/template#create-from-a-sandbox" cta="Continue">
    Reuse an initialized block-COW RootFS without publishing an image layer.
  </Card>
</CardGroup>
