> ## Documentation Index
> Fetch the complete documentation index at: https://microsanbox-staging-toks-cloud-snapshot-contracts.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Images

> Use OCI container images or boot from disk image files

## OCI images

microsandbox uses standard OCI container images as root filesystems. Docker Hub, GHCR, ECR, GCR, any OCI-compatible registry works. Existing images run as-is.

When you specify an image like `python`, microsandbox pulls the manifest, downloads the layers in parallel, and stacks them as a copy-on-write filesystem. Changes inside the sandbox don't modify the base image. Two sandboxes using the same image share the same cached layers on disk.

### Pull policies

By default, microsandbox pulls an image only if it isn't already cached. You can change this behavior.

| Policy         | Behavior                              |
| -------------- | ------------------------------------- |
| `"if-missing"` | Pull only if not cached (default)     |
| `"always"`     | Always check the registry for updates |
| `"never"`      | Use local cache only, fail if missing |

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { Sandbox } from "microsandbox";

  await using sb = await Sandbox.builder("worker")
      .image("python")
      .pullPolicy("always")
      .create();
  ```

  ```rust Rust theme={null}
  let sb = Sandbox::builder("worker")
      .image("python")
      .pull_policy(PullPolicy::Always)
      .create()
      .await?;
  ```

  ```python Python theme={null}
  from microsandbox import PullPolicy, Sandbox

  sb = await Sandbox.create("worker", image="python", pull_policy=PullPolicy.ALWAYS)
  ```

  ```go Go theme={null}
  sb, err := m.CreateSandbox(ctx, "worker",
      m.WithImage("python"),
      m.WithPullPolicy(m.PullPolicyAlways),
  )
  ```
</CodeGroup>

<Tip>
  Once microsandbox resolves an image reference, it pins the exact layers. At first pull, `python` resolves to a specific set of immutable layers. Subsequent `start()` calls use the pinned layers without re-resolving the mutable tag, so your sandbox is reproducible even if the upstream tag moves.
</Tip>

### Private registries

Authenticate to private registries by passing credentials.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await using sb = await Sandbox.builder("worker")
      .image("registry.corp.io/team/app:latest")
      .registry((r) => r.auth({
          kind: "basic",
          username: "deploy",
          password: process.env.REGISTRY_TOKEN!,
      }))
      .pullPolicy("always")
      .create();
  ```

  ```rust Rust theme={null}
  let sb = Sandbox::builder("worker")
      .image("registry.corp.io/team/app:latest")
      .registry(|r| r.auth(RegistryAuth::Basic {
          username: "deploy".into(),
          password: std::env::var("REGISTRY_PASSWORD")?,
      }))
      .pull_policy(PullPolicy::Always)
      .create()
      .await?;
  ```

  ```python Python theme={null}
  import os
  from microsandbox import PullPolicy, RegistryAuth, Sandbox

  sb = await Sandbox.create(
      "worker",
      image="registry.corp.io/team/app:latest",
      registry_auth=RegistryAuth.basic("deploy", os.environ["REGISTRY_PASSWORD"]),
      pull_policy=PullPolicy.ALWAYS,
  )
  ```

  ```go Go theme={null}
  sb, err := m.CreateSandbox(ctx, "worker",
      m.WithImage("registry.corp.io/team/app:latest"),
      m.WithRegistryAuth(m.RegistryAuth{
          Username: "deploy",
          Password: os.Getenv("REGISTRY_PASSWORD"),
      }),
      m.WithPullPolicy(m.PullPolicyAlways),
  )
  ```
</CodeGroup>

### Registry TLS

<Tooltip tip="Plain-HTTP registries and custom CA certificates are not supported on microsandbox cloud; images are pulled from HTTPS registries only."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

By default, microsandbox connects to registries over HTTPS using system CA roots. You can customize this per-registry in `~/.microsandbox/config.json`.

#### Plain HTTP registries

Local registries often run without TLS. Mark them as insecure to connect over plain HTTP:

```json theme={null}
{
  "registries": {
    "hosts": {
      "localhost:5050": {
        "insecure": true
      }
    }
  }
}
```

#### Custom CA certificates

For registries using self-signed or internal CA certificates, point `ca_certs` to a PEM file. This applies globally to all registry connections:

```json theme={null}
{
  "registries": {
    "ca_certs": "/path/to/ca-bundle.pem"
  }
}
```

microsandbox adds these certificates to the default system roots, so public registries continue to work normally. You can also supply the same roots per-sandbox through the SDK: `caCerts` in TypeScript, `ca_certs` in Rust, `registry_ca_certs` in Python, and `WithRegistryCACerts` / `WithRegistryCACertsPath` in Go.

#### Combined configuration

```json theme={null}
{
  "registries": {
    "ca_certs": "/path/to/corporate-ca.pem",
    "hosts": {
      "localhost:5050": {
        "insecure": true,
        "auth": { "username": "deploy", "password_env": "REGISTRY_TOKEN" }
      },
      "ghcr.io": {
        "auth": { "username": "user", "store": "keyring" }
      }
    }
  }
}
```

You can also set these per-sandbox via the SDK, overriding global config:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await using sb = await Sandbox.builder("worker")
      .image("localhost:5050/my-app:latest")
      .registry((r) => r.insecure())
      .create();
  ```

  ```rust Rust theme={null}
  let sb = Sandbox::builder("worker")
      .image("localhost:5050/my-app:latest")
      .registry(|r| r.insecure())
      .create()
      .await?;
  ```

  ```python Python theme={null}
  from microsandbox import Sandbox

  sb = await Sandbox.create(
      "worker",
      image="localhost:5050/my-app:latest",
      registry_insecure=True,
  )
  ```

  ```go Go theme={null}
  sb, err := m.CreateSandbox(ctx, "worker",
      m.WithImage("localhost:5050/my-app:latest"),
      m.WithRegistryInsecure(),
  )
  ```
</CodeGroup>

### Image storage

<Tooltip tip="Image-cache commands are local-only. On microsandbox cloud, specify an OCI image on create and it is pulled for you."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

Images are cached in the global microsandbox home directory:

| Path                            | Description                                  |
| ------------------------------- | -------------------------------------------- |
| `~/.microsandbox/cache/layers/` | Materialized EROFS image layers              |
| `~/.microsandbox/cache/fsmeta/` | Merged metadata images for cached manifests  |
| `~/.microsandbox/cache/vmdk/`   | VMDK descriptors used to boot cached images  |
| `~/.microsandbox/db/`           | Database tracking image metadata and digests |

Layers are content-addressable and deduplicated. If `python:3.12` and `python:3.11` share a base layer, it's stored once.

<Tip>
  Use `msb pull` from the CLI to pre-pull images before creating sandboxes. This avoids blocking on a download during `Sandbox.create`.
</Tip>

Use `msb image prune` to remove cached images that are not used by any sandbox or indexed snapshot and reclaim dangling image artifacts. Prune keeps images referenced by existing sandboxes or snapshots. It cleans up image metadata, unreachable manifests, orphaned layers, layer EROFS artifacts, fsmeta EROFS artifacts, and VMDK descriptor artifacts.

`msb load` accepts Docker image archives and OCI Image Layout archives. By default, `msb save` exports from the materialized EROFS cache as a Docker archive. Use `--format oci` to export as an OCI Image Layout archive instead. The expanded forms are `msb image load` and `msb image save`. The exported image should run the same way after `msb load`, but it is a regenerated archive: manifest digest and layer digests can differ from the original registry image.

## Disk images

<Tooltip tip="Booting from a disk image references a file on the caller host and is local-only. On microsandbox cloud, publish the filesystem as an OCI image instead."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

In addition to OCI container images, microsandbox can boot a sandbox directly from a disk image file. The guest gets raw block device access, and its kernel mounts the filesystem from the device directly.

This is a fundamentally different path from OCI images. With OCI, microsandbox stacks image layers as a copy-on-write filesystem. With disk images, the guest owns the block device. No overlay, no copy-on-write between sandboxes.

Use disk images when you need a pre-built VM template, a custom kernel configuration, or an OS that isn't available as a container image.

### Supported formats

| Format    | Description                                                                                  |
| --------- | -------------------------------------------------------------------------------------------- |
| **QCOW2** | QEMU Copy-On-Write v2. Supports thin provisioning and backing files. The most common format. |
| **Raw**   | Uncompressed raw disk image. No overhead, but no thin provisioning.                          |
| **VMDK**  | VMware virtual disk format.                                                                  |

### Usage

When you pass a file path ending in `.qcow2`, `.raw`, or `.vmdk`, microsandbox auto-detects the format. For disk-image roots, rename ambiguous files with one of those extensions and set the filesystem type when auto-detection needs a hint.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { Sandbox } from "microsandbox";

  // Auto-detect format from the file extension
  await using sb = await Sandbox.builder("custom-vm")
      .image("./ubuntu-22.04.qcow2")
      .cpus(2)
      .memory(2048)
      .create();

  // Explicit filesystem type via the ImageBuilder
  await using sb2 = await Sandbox.builder("custom-vm")
      .imageWith((i) => i.disk("./alpine.raw").fstype("ext4"))
      .cpus(1)
      .memory(512)
      .create();
  ```

  ```rust Rust theme={null}
  use microsandbox::size::SizeExt;

  // Auto-detect
  let sb = Sandbox::builder("custom-vm")
      .image("./ubuntu-22.04.qcow2")
      .cpus(2)
      .memory(2.gib())
      .create()
      .await?;

  // Explicit filesystem type
  let sb2 = Sandbox::builder("custom-vm")
      .image_with(|i| i.disk("./alpine.raw").fstype("ext4"))
      .cpus(1)
      .memory(512)
      .create()
      .await?;
  ```

  ```python Python theme={null}
  from microsandbox import Image, Sandbox

  # Auto-detect format from file extension
  sb = await Sandbox.create("custom-vm", image="./ubuntu-22.04.qcow2", cpus=2, memory=2048)

  # Explicit filesystem type
  sb2 = await Sandbox.create("custom-vm", image=Image.disk("./alpine.raw", fstype="ext4"), cpus=1, memory=512)
  ```

  ```go Go theme={null}
  // Auto-detect format from the file extension
  sb, err := m.CreateSandbox(ctx, "custom-vm",
      m.WithImage("./ubuntu-22.04.qcow2"),
      m.WithCPUs(2),
      m.WithMemory(2048),
  )

  // Explicit filesystem type
  sb2, err := m.CreateSandbox(ctx, "custom-vm-2",
      m.WithImageDisk("./alpine.raw", "ext4"),
      m.WithCPUs(1),
      m.WithMemory(512),
  )
  ```
</CodeGroup>

<Note>
  The filesystem type (`ext4`, `xfs`, etc.) must match what's actually on the disk image. The guest kernel mounts it using the specified filesystem driver.
</Note>

## Reference

For exact image APIs, see [TypeScript](/sdk/typescript/images), [Rust](/sdk/rust/images), [Python](/sdk/python/images), or [Go](/sdk/go/images). For local image management, see [Image commands](/cli/image-commands).
