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

# Cloudflare Containers

> Mount an Archil disk in a Cloudflare Container so its files outlive the container.

A [Cloudflare Container](https://developers.cloudflare.com/containers/) runs your image in a VM that belongs to a Durable Object, which starts it, runs commands in it, and stops it after a period of inactivity. When the container stops, its disk is thrown away. Mount an Archil disk in the container and anything written there stays on the disk, where the next container can mount it and your app can read it with the Archil SDK.

Cloudflare Containers support FUSE, so the disk mounts as a regular directory. The Durable Object mounts the disk when it starts the container, and the container unmounts it when Cloudflare stops it. The same approach works with the [Sandbox SDK](https://developers.cloudflare.com/sandbox/), which runs on the same Container API.

## Prerequisites

* A Cloudflare account on the Workers Paid plan, which Containers require.
* [Docker](https://docs.docker.com/get-started/get-docker/) running locally. Wrangler uses it to build the image.
* An Archil disk in the [Archil region](https://docs.archil.com/reference/regions.md) of your choice.

Set your Archil API key and region:

```bash theme={null}
export ARCHIL_API_KEY=key-...
export ARCHIL_REGION=aws-us-east-1
```

Create the disk with the [`disk` CLI](https://docs.archil.com/reference/disk-cli). The output includes the disk ID and a one-time [mount token](https://docs.archil.com/concepts/authorization.md#token-based-disk-authorization), which grants access to this disk only:

```bash theme={null}
npx disk create container-data
```

Export both for the steps below:

```bash theme={null}
export ARCHIL_DISK_ID=dsk-...
export ARCHIL_MOUNT_TOKEN=adt_...
```

## Create the project

Create a directory for the project and install Wrangler:

```bash theme={null}
mkdir archil-container && cd archil-container
npm init -y
npm install --save-dev wrangler
```

The project needs four files: the image, a script the container runs, the Worker, and Wrangler's configuration.

The image installs the `archil` client. Cloudflare runs `linux/amd64` images, and the installer picks the matching build:

```dockerfile Dockerfile theme={null}
FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \
 && curl -fsSL https://archil.com/install | sh \
 && rm -rf /var/lib/apt/lists/* \
 && mkdir -p /mnt/archil
COPY start.sh /usr/local/bin/start.sh
CMD ["sh", "/usr/local/bin/start.sh"]
```

The container's main process waits for Cloudflare's `SIGTERM` and unmounts the disk before it exits, which sends every buffered write to the disk and releases it:

```sh start.sh theme={null}
trap 'archil unmount /mnt/archil; exit 0' TERM
sleep infinity &
wait
```

The Worker routes each request to a Durable Object by name. The Durable Object starts its container, mounts the disk with the token from a Worker secret, and then runs a command in the mounted directory. The command appends a line to a file on the disk and prints the file:

```ts src/index.ts theme={null}
import { DurableObject } from "cloudflare:workers";

interface Env {
  WORKSPACE: DurableObjectNamespace<Workspace>;
  ARCHIL_DISK_ID: string;
  ARCHIL_REGION: string;
  ARCHIL_MOUNT_TOKEN: string;
}

const MOUNTPOINT = "/mnt/archil";
const decoder = new TextDecoder();

export class Workspace extends DurableObject<Env> {
  private mounted?: Promise<void>;

  async run(argv: string[], env: Record<string, string> = {}) {
    const container = this.ctx.container!;
    if (!container.running) {
      container.start({ image: container.images.workspace, instance: "standard-1", enableInternet: true });
      this.mounted = undefined;
      await container.setInactivityTimeout(10 * 60 * 1000);
    }
    this.mounted ??= this.mount(container).catch((error) => {
      this.mounted = undefined;
      throw error;
    });
    await this.mounted;

    const process = await container.exec(argv, { cwd: MOUNTPOINT, env });
    const output = await process.output();
    return { exitCode: output.exitCode, stdout: decoder.decode(output.stdout) };
  }

  async stop() {
    const container = this.ctx.container!;
    if (!container.running) return;
    const exited = container.monitor().catch(() => {});
    container.signal(15);
    await exited;
  }

  private async mount(container: Container) {
    const process = await container.exec(
      ["sh", "-c", `mountpoint -q ${MOUNTPOINT} || archil mount "$ARCHIL_DISK_ID" ${MOUNTPOINT} --region "$ARCHIL_REGION"`],
      {
        env: {
          ARCHIL_DISK_ID: this.env.ARCHIL_DISK_ID,
          ARCHIL_REGION: this.env.ARCHIL_REGION,
          ARCHIL_MOUNT_TOKEN: this.env.ARCHIL_MOUNT_TOKEN,
        },
      },
    );
    const output = await process.output();
    if (output.exitCode !== 0) {
      throw new Error(`archil mount failed: ${decoder.decode(output.stdout)}${decoder.decode(output.stderr)}`);
    }
  }
}

export default {
  async fetch(request, env) {
    // Authenticate the request here, then pick the workspace for this user or task.
    const name = new URL(request.url).pathname.slice(1) || "default";
    const workspace = env.WORKSPACE.getByName(name);
    if (request.method === "DELETE") {
      await workspace.stop();
      return new Response(`Stopped ${name}\n`);
    }
    const { stdout } = await workspace.run(
      ["sh", "-c", 'echo "Hello from $NAME" >> visits.log && cat visits.log'],
      { NAME: name },
    );
    return new Response(stdout);
  },
} satisfies ExportedHandler<Env>;
```

`wrangler.jsonc` binds the Durable Object to the image and sets the disk ID and region:

```jsonc wrangler.jsonc theme={null}
{
  "name": "archil-container",
  "main": "src/index.ts",
  "compatibility_date": "2026-09-02",
  "durable_objects": {
    "bindings": [{ "name": "WORKSPACE", "class_name": "Workspace" }]
  },
  "containers": [
    {
      "class_name": "Workspace",
      "scheduling_policy": "durable_object",
      "images": { "workspace": { "dockerfile": "./Dockerfile" } }
    }
  ],
  "exports": {
    "Workspace": { "type": "durable-object", "storage": "sqlite" }
  },
  "vars": {
    "ARCHIL_DISK_ID": "dsk-...",
    "ARCHIL_REGION": "aws-us-east-1"
  }
}
```

Replace the two values under `vars` with your disk ID and region.

## Deploy and try it

Log in, deploy, and store the mount token as a Worker secret. The first deploy builds the image and pushes it to Cloudflare's registry:

```bash theme={null}
npx wrangler login
npx wrangler deploy
printf '%s' "$ARCHIL_MOUNT_TOKEN" | npx wrangler secret put ARCHIL_MOUNT_TOKEN
```

The deploy prints the Worker's URL. Request a workspace by name:

```bash theme={null}
export WORKER_URL=https://archil-container.<your-subdomain>.workers.dev
curl "$WORKER_URL/alice"
```

```text theme={null}
Hello from alice
```

The first request to a workspace starts its container and mounts the disk, so it takes a few seconds. Stop the container. The Durable Object sends it `SIGTERM`, `start.sh` unmounts the disk, and the request returns once the container has exited:

```bash theme={null}
curl -X DELETE "$WORKER_URL/alice"
```

Request a different workspace. Its container mounts the same disk and finds the line the first one wrote:

```bash theme={null}
curl "$WORKER_URL/bob"
```

```text theme={null}
Hello from alice
Hello from bob
```

## How the disk behaves in a container

**One container holds the disk at a time.** A plain mount is exclusive. To mount the same disk in several containers at once, add `--conditional` to the `archil mount` command. Every container can then read and write anywhere on the disk; see [Sharing disks](https://docs.archil.com/concepts/sharing-disks.md). To give each user their own storage instead, create a disk per user and pass its ID and token to that user's Durable Object.

**Unmount when the container stops.** Cloudflare sends a container `SIGTERM` when its inactivity timeout expires, and `stop()` sends it with `signal()`. Unmounting in the `SIGTERM` handler sends the client's buffered writes to the disk and releases it for the next container. Stop containers with `signal()` rather than `destroy()`, which doesn't give the container time to unmount. If a container ever goes away with the disk still mounted, see [Release a stale lock](https://docs.archil.com/guides/platforms/troubleshooting.md#release-a-stale-lock).

## Clean up

Delete the Worker, its container application and image, and the disk once you no longer need them. `wrangler delete` leaves the container application and its image behind, so delete those by ID and tag:

```bash theme={null}
npx wrangler delete
npx wrangler containers list
npx wrangler containers delete <application-id>
npx wrangler containers images list
npx wrangler containers images delete <repository>:<tag>
npx disk delete "$ARCHIL_DISK_ID"
```

## Next steps

* [TypeScript SDK](https://docs.archil.com/sdks/typescript.md): read, write, and search the disk without mounting it.
* [Sharing disks](https://docs.archil.com/concepts/sharing-disks.md): mount one disk in many containers at once.
* [`archil` CLI reference](https://docs.archil.com/reference/archil-cli): every `archil mount` option.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.