> ## Documentation Index
> Fetch the complete documentation index at: https://docs.run.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom images

> Boot sandboxes from any Docker image, and pre-bake images in CI

Sandboxes boot from images. You can use the built-in ones
(`runcloud/agent-base`, `runcloud/desktop`), or point a create at any
Docker/OCI image and Run Cloud converts it into a bootable microVM image
automatically.

## Use any Docker image

Pass an image reference directly to a create — CLI, SDK, or API:

```bash theme={null}
runcloud sandbox create --image python:3.12-slim
```

```typescript theme={null}
const sandbox = await cloud.sandboxes.create({ image: "python:3.12-slim" });
```

The **first time** a given image is used on the platform, Run Cloud builds it:
the layers are pulled from the upstream registry, flattened, and converted to
a microVM root filesystem. `sandbox create` returns immediately (it does not
block on the build) with the sandbox in the `building_image` state — poll
`sandbox get <id>` until it moves past that state, typically after one to a
few minutes depending on image size. Every later create from the same image
boots in the normal sub-second-to-seconds range, for every user, on every
host. If you don't want request-path traffic to ever see `building_image`,
[pre-bake the image in CI](#pre-bake-images-in-ci) instead.

Private registries (GHCR, ECR, GAR, Docker Hub private repos) are supported
with registry credentials; images from public registries are converted once
platform-wide, so popular bases are almost always already built.

## Runtime user, working directory, and environment

Sandboxes execute commands as `root`; an image's OCI `USER` directive is
ignored. The filesystem still contains files owned by the declared image user,
so caches may remain under paths such as `/home/<user>` even though `$HOME` is
`/root`.

OCI `WORKDIR`, `ENV`, `ENTRYPOINT`, and `CMD` are also not applied
automatically. Exec defaults to `/` with a small, safe environment. Pass the
working directory and environment explicitly when your command needs them:

```typescript theme={null}
await cloud.sandboxes.exec(sandbox.id, ["node", "render.mjs"], {
  cwd: "/app",
  env: { PUPPETEER_CACHE_DIR: "/home/pptruser/.cache/puppeteer" },
});
```

## Tags are pinned until you refresh them

The first time your organization uses a tag like `python:3.12-slim`, Run
Cloud resolves it to the upstream image digest and **pins it**. Your builds
stay deterministic: upstream pushing a new `3.12-slim` never changes what
your sandboxes boot, and never triggers a surprise rebuild.

To pick up a newer upstream build, refresh the pin explicitly. The command
prints the ref along with the digest it was pinned to and the digest it moved
to:

```bash theme={null}
runcloud image refresh python:3.12-slim
# ref: python:3.12-slim
# old_digest: sha256:4f0ba8c…
# new_digest: sha256:9a21fe1…
```

<Note>
  A refresh moves the pin platform-wide for that tag, not just for your
  organization — every organization using `python:3.12-slim` sees the new
  digest on their next create.
</Note>

For fully explicit control, reference images by digest — digests bypass
pinning entirely:

```bash theme={null}
runcloud sandbox create --image python@sha256:4f0ba8c…
```

## Pre-bake images in CI

If you never want production traffic to hit a first-use build, register the
image ahead of time. `runcloud image create` registers the ref (or no-ops if
it's already registered), waits for the build to settle, and exits non-zero
with the registry error if the build fails:

```bash theme={null}
runcloud image create --ref python:3.12-slim
```

Under the hood this polls the registration every 3 seconds. `--timeout`
controls how long it waits (in seconds, default 1200 = 20 minutes). Pass
`--no-wait` to schedule the build and return immediately with `state:
building`:

```bash theme={null}
runcloud image create --ref python:3.12-slim --timeout 600
runcloud image create --ref python:3.12-slim --no-wait
```

A typical GitHub Actions job that pre-bakes on every merge:

```yaml theme={null}
jobs:
  prebake-sandbox-image:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g runcloud
      - run: runcloud image create --ref "ghcr.io/acme/agent-runtime@${{ github.sha }}"
        env:
          RUN_CLOUD_API_KEY: ${{ secrets.RUN_CLOUD_API_KEY }}
```

<Note>
  Pre-baking from a local Dockerfile/build context (rather than an already
  published OCI ref) is not supported yet.
</Note>

List what your organization has registered:

```bash theme={null}
runcloud image list
```

`runcloud image list` prints every image ref your organization has
registered, with its current `state` (`building`, `ready`, or `failed`) and
resolved `digest`. Pass `--json` for machine-readable output.

## How distribution works

Built images are stored content-addressed in Run Cloud's object storage and
cached on fleet hosts. When an image is first built it is pre-warmed onto a
fraction of hosts in each region; the scheduler prefers hosts that already
hold your image, and any host that doesn't fetches it once, on demand, with
integrity verification. You never manage placement — but it explains the
latency you see: warm creates are fast, and only the very first boot of a
new image on a cold host pays a short one-time fetch.

## Images vs snapshots

Images are immutable, reusable boot templates with no user data in them.
[Snapshots](/sandboxes/snapshots) capture a *specific sandbox's* state —
files, installed tools, everything — and are private to your organization.
Use an image to define what a fleet of sandboxes starts from; use a snapshot
to fork or resume the state of one particular sandbox.
