Skip to content

Archil.Disk reference

Source: packages/archil/src/Branch.ts

An independent, writable fork of a disk, started from a committed checkpoint in about 35 ms whatever the disk’s size. Writes on the branch never reach its source or its siblings. Branches share the disk’s credentials, so a disk user can mount any branch of its disk.

Archil has no API to delete a branch yet, so destroying the resource only removes it from Alchemy’s state. Branches made per session at run time come from BranchDisk.

export const Staging = Effect.gen(function* () {
const seed = yield* Seed;
const base = yield* Base;
return yield* Archil.Branch("Staging", {
disk: seed.diskId,
fromCheckpoint: base.name,
});
});

Source: packages/archil/src/BranchDisk.ts

Fork one disk per session or task at run time: each branch is an independent, writable copy started from a committed checkpoint in about 35 ms, whatever the disk’s size. Seed the disk once at deploy time with an Checkpoint, then branch from it per request.

Mount a branch on your own machines with MountDisk and branch. Branches share the disk’s credentials. Sandboxes can’t mount a branch.

export default class Api extends Cloudflare.Worker<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const seeds = yield* Archil.BranchDisk(Seed);
return {
fetch: Effect.gen(function* () {
const branch = yield* seeds.branch({ name: "session-42", fromCheckpoint: "base" });
return yield* HttpServerResponse.json(branch);
}),
};
}).pipe(Effect.provide(Archil.BranchDiskHttp)),
) {}

Source: packages/archil/src/Checkpoint.ts

A named, point-in-time snapshot of a disk, the seeded base that branches fork from. Alchemy runs the run commands against the disk, then takes the checkpoint in the same microVM, so the checkpoint holds exactly what they produced. A checkpoint takes about 15 ms whatever the disk’s size, and never changes after it’s taken.

Archil can’t delete checkpoints yet, so destroying the resource only removes it from Alchemy’s state.

export const Seed = Archil.Disk("Seed", {});
export const Base = Effect.gen(function* () {
const seed = yield* Seed;
return yield* Archil.Checkpoint("Base", {
disk: seed.diskId,
name: "base",
run: [
"git clone --depth 1 https://github.com/alchemy-run/alchemy repo",
"cd repo && corepack enable && pnpm install --frozen-lockfile",
],
});
});

Source: packages/archil/src/CreateDisk.ts

Create disks at run time, one per user, session, or task, by name. This is how most apps use Archil: a disk mounts in about 50 ms, so it can belong to a single request. The client returns full disk handles (exec, grep, objects, branches, disk tokens), the same shape the deploy-time bindings return.

Objects created at run time are tagged with the stack, stage and binding (alchemy::stack, alchemy::stage, alchemy::id) and are not deleted when the stack is destroyed. List them by those tags to clean them up. Archil has no tags, labels or descriptions, so the tags are a name prefix, alc-<id>-<hash of stack/stage/id>: every name this client creates is <prefix>-<name>, and prefix on the client is the value.

export default class Api extends Cloudflare.Worker<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const disks = yield* Archil.CreateDisk();
return {
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest;
const user = request.headers["x-user"] ?? "anonymous";
const home = yield* disks.create(`user-${user}`);
const result = yield* home.exec("ls -la /mnt/archil");
return HttpServerResponse.text(result.stdout);
}),
};
}).pipe(Effect.provide(Archil.CreateDiskHttp)),
) {}
const disks = yield* Archil.CreateDisk();
for (const disk of yield* disks.list()) {
yield* disks.delete(disk.name.slice(disks.prefix.length + 1));
}

Source: packages/archil/src/Disk.ts

An Archil disk: a durable filesystem that mounts in about 50 ms on any Linux machine, sandbox, or serverless exec, with hot data on SSD and the rest in object storage. Keep its files in Archil-managed storage, or in your own bucket as ordinary objects.

Declare a disk at deploy time for the few long-lived ones an app shares, like a dataset or a seeded base you branch from. Disks that belong to one user, session, or task are created at run time with CreateDisk, which returns the same disk shape.

Archil has no tags, so Alchemy owns a disk through its generated name. A disk with a fixed name that already exists is adopted only with --adopt.

Archil-managed storage

export const Dataset = Archil.Disk("Dataset", {});

On your own S3 bucket, with no keys

export const Store = AWS.S3.Bucket("Store", {});
export const Dataset = ArchilAWS.S3Disk("Dataset", { bucket: Store, prefix: "datasets/" });

On an R2 bucket

export const Dataset = Archil.Disk("Dataset", {
storage: {
type: "r2",
bucketName: "datasets",
bucketEndpoint: "https://<account>.r2.cloudflarestorage.com",
accessKeyId: Alchemy.Secret("R2_ACCESS_KEY_ID"),
secretAccessKey: Alchemy.Secret("R2_SECRET_ACCESS_KEY"),
},
});
export const Dataset = Archil.Disk("Dataset", {
allowedIps: ["203.0.113.0/24"],
allowSandboxMount: false,
});

Object bindings give each host its own S3 user on this disk (read-only for ReadDisk), so the host never holds an API key.

Read and write the disk from a program sandbox

export default class Api extends Archil.Sandbox<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const files = yield* Archil.ReadWriteDisk(Dataset);
return {
fetch: Effect.map(files.getText("README.md"), (text) => HttpServerResponse.text(text)),
};
}).pipe(Effect.provide(Archil.ReadWriteDiskHttp)),
) {}

Mount it into a sandbox

export const Box = Archil.Sandbox("Box", {
mounts: [{ disk: Dataset, path: "/data", mode: "read-only" }],
});

Mount it in a Cloudflare Container

const dataset = yield* Archil.MountDisk(Dataset, { path: "/data", mode: "read-only" });
const path = yield* dataset.mount();

Run commands and read files

const dataset = yield* Archil.ExecDisk(Dataset);
const files = yield* Archil.ReadDisk(Dataset);
const result = yield* dataset.exec("wc -l /mnt/archil/*.csv");
const readme = yield* files.getText("README.md");

Upload a large file and share a link

const files = yield* Archil.ReadWriteDisk(Dataset);
yield* files.upload("exports/2026-11.parquet", body);
const url = yield* files.share("exports/2026-11.parquet", 3600);
export const Seed = Archil.Disk("Seed", {});
export const SeedBase = Effect.gen(function* () {
const seed = yield* Seed;
return yield* Archil.Checkpoint("SeedBase", {
disk: seed.diskId,
name: "base",
run: ["git clone https://github.com/acme/app ."],
});
});
// runtime
const seeds = yield* Archil.BranchDisk(Seed);
const branch = yield* seeds.branch({ name: `session-${id}`, fromCheckpoint: "base" });

Source: packages/archil/src/DiskUser.ts

Access to one disk. A token user gets a disk token for archil mount; an s3 user gets an access key pair for the disk’s S3-compatible API; an IAM user lets an AWS principal mount with its own identity, so no secret exists at all. Archil checks each one itself, so a host holding a disk user can’t reach any other disk. readOnly, expiresAt and oneUse narrow it further.

Archil only shows a token or secret key once, when it’s created, so Alchemy stores it in state as a Redacted value. Destroying the user revokes it. Archil.MountDisk(disk) and the object bindings (Archil.ReadDisk, Archil.WriteDisk, Archil.ReadWriteDisk) create one per host for you.

A token for one machine

const user = yield* Archil.DiskUser("Indexer", { disk: dataset.diskId });
// ARCHIL_MOUNT_TOKEN=<user.token> archil mount <disk> /mnt/data --region aws-us-east-1

An ECS task role that mounts with its own identity

const user = yield* Archil.DiskUser("Workers", {
disk: dataset.diskId,
principal: taskRole.roleArn,
});

A read-only token that expires

const user = yield* Archil.DiskUser("Viewer", {
disk: dataset.diskId,
readOnly: true,
expiresAt: "2027-01-01T00:00:00Z",
});
const reader = yield* Archil.DiskUser("Reader", {
disk: dataset.diskId,
type: "s3",
readOnly: true,
});
// reader.accessKeyId, reader.secretAccessKey sign requests to
// https://s3.green.us-east-1.aws.prod.archil.com/<disk id>/<key>

Source: packages/archil/src/ExecDisk.ts

Run serverless commands against one disk, and search it, from a Worker, Lambda, or any other host. Each exec boots a fresh microVM with the disk mounted at /mnt/archil, runs the command to completion (up to five minutes), and shuts down, so you pay only for the call. Files the command writes stay on the disk.

The host calls Archil with the API key you deploy with: Archil only creates API keys from the console and can’t scope them to one disk.

export default class Api extends Cloudflare.Worker<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const dataset = yield* Archil.ExecDisk(Dataset);
return {
fetch: Effect.gen(function* () {
const result = yield* dataset.exec("wc -l /mnt/archil/*.csv");
return HttpServerResponse.text(result.stdout);
}),
};
}).pipe(Effect.provide(Archil.ExecDiskHttp)),
) {}
const hits = yield* dataset.grep({ pattern: "TODO", directory: "src", recursive: true });

A mounted client holds write delegations on the paths it writes. When it crashes without unmounting, reclaim them so other clients can write.

for (const d of yield* dataset.delegations()) {
if (d.isOrphaned) yield* dataset.revokeDelegation(d);
}

Source: packages/archil/src/ExecDisks.ts

Run one serverless command against several disks at once, e.g. read a shared dataset and write into a user’s own disk in the same call. Disks are named at run time, so it works with disks created per user.

const exec = yield* Archil.ExecDisks();
const result = yield* exec.exec({
disks: {
data: { disk: datasetId, readOnly: true },
home: userDisk.diskId,
},
command: "cp -r /mnt/archil/data/samples /mnt/archil/home/",
});

Source: packages/archil/src/MountDisk.ts

Mount an Archil disk where your program runs. MountDisk(disk, options) returns a mount reference: the fields of a mounts entry (disk, path, mode, …) plus mount().

  • In an Archil.Sandbox program, the sandbox mounts the disk natively: the binding adds the entry to the sandbox’s mounts, and mount() returns the path at once.
  • On your own machines (a Cloudflare Container, an ECS task, a Fly Machine, or a VM), the binding installs the Archil client into the host’s image and checks at plan time that the host provides FUSE. mount() runs the client for the life of the scope that called it. The host gets its own disk user (read-only for mode: "read-only"), so Archil refuses it any other disk and it never holds an API key.

MountDisk() mounts whatever disk the program names, with a disk token it receives at run time (e.g. from a control Worker’s DiskHandle.createToken).

The client reaches Archil over the network, so start a Cloudflare Container with container.start({ enableInternet: true }).

export default class Api extends Archil.Sandbox<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const dataset = yield* Archil.MountDisk(Dataset, { path: "/data", mode: "read-only" });
const fs = yield* FileSystem.FileSystem;
return {
fetch: Effect.gen(function* () {
return yield* HttpServerResponse.json(yield* fs.readDirectory(dataset.path));
}).pipe(Effect.orDie),
};
}).pipe(Effect.provide(Archil.MountDiskLive)),
) {}

Read a dataset in a Cloudflare Container

export default class Indexer extends Cloudflare.Container<Indexer>()(
"Indexer",
{ main: import.meta.url },
Effect.gen(function* () {
const dataset = yield* Archil.MountDisk(Dataset, { path: "/data", mode: "read-only" });
const fs = yield* FileSystem.FileSystem;
return {
fetch: Effect.scoped(
Effect.gen(function* () {
const path = yield* dataset.mount();
return yield* HttpServerResponse.json(yield* fs.readDirectory(path));
}),
),
};
}).pipe(Effect.provide(Archil.MountDiskLive)),
) {}

Write through a mount in an ECS task on EC2

const scratch = yield* Archil.MountDisk(Scratch, { path: "/scratch" });
// runtime
const path = yield* scratch.mount();
yield* fs.writeFileString(`${path}/result.json`, JSON.stringify(result));
const workspace = yield* Archil.MountDisk(Workspace, { path: "/workspace", mode: "conditional" });
const sandboxes = yield* Archil.CreateSandbox();
// runtime
const sandbox = yield* sandboxes.create({ name: `task-${id}`, mounts: [workspace] });

MountDisk: Mounting Disks Named at Run Time

Section titled “MountDisk: Mounting Disks Named at Run Time”
const archil = yield* Archil.MountDisk();
const path = yield* archil.mount({ disk: job.diskId, token: job.token, path: "/work" });

Source: packages/archil/src/ReadDisk.ts

Read a disk’s files as objects over Archil’s S3-compatible API, with no machine and no mount: get, stream, head, list, and presigned share URLs. Reads see what mounted clients have written.

The host gets its own read-only S3 user on this disk: its keys can only GET and HEAD this disk’s files, and the host never holds an API key. Destroying the stack revokes the user.

Serve files from a disk in a Worker

export default class Api extends Cloudflare.Worker<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const files = yield* Archil.ReadDisk(Dataset);
return {
fetch: Effect.gen(function* () {
const readme = yield* files.getText("README.md");
return HttpServerResponse.text(readme);
}),
};
}).pipe(Effect.provide(Archil.ReadDiskHttp)),
) {}

Stream a large file to the response

return HttpServerResponse.stream(files.getStream("exports/data.parquet"));
const url = yield* files.share("reports/2026-11.pdf", 3600);

Source: packages/archil/src/ReadWriteDisk.ts

Read and write a disk’s files as objects: everything ReadDisk and WriteDisk do, in one client. The host gets its own read-write S3 user on this disk, and never holds an API key.

const files = yield* Archil.ReadWriteDisk(Dataset);
yield* files.put("uploads/photo.jpg", bytes, { contentType: "image/jpeg" });
const url = yield* files.share("uploads/photo.jpg", 600);

Source: packages/archil/src/WriteDisk.ts

Write a disk’s files as objects over Archil’s S3-compatible API: put, append, copy, delete, and multipart uploads of any size. The host gets its own read-write S3 user on this disk, so it can’t reach any other disk and never holds an API key. A write that conflicts with a mounted client’s write delegation fails with ObjectConflict; ExecDisk’s delegations and revokeDelegation reclaim paths a crashed client still holds.

const files = yield* Archil.WriteDisk(Dataset);
yield* files.append("events.ndjson", JSON.stringify(event) + "\n");
const files = yield* Archil.WriteDisk(Dataset);
const request = yield* HttpServerRequest;
yield* files.upload("uploads/video.mp4", request.stream, { contentType: "video/mp4" });