Skip to content

Part 3: A Copy per User

A Modal sandbox lasts as long as its command, so a user’s files can’t live in it. They live on the user’s volume, and every sandbox the user starts from Warm mounts it: the warm image, plus their own files on top.

src/Api.ts
const match = /^\/users\/([a-z0-9-]+)\/files\/(.+)$/.exec(url.pathname);
if (match !== null) {
const home = yield* volumes.getOrCreate(`home-${match[1]}`);
if (request.method === "PUT") yield* home.writeFile(`/${match[2]}`, yield* request.text);
return yield* HttpServerResponse.json({ content: yield* home.readFileString(`/${match[2]}`) });
}

The Worker writes the file straight to the volume. No sandbox runs.

The run function from Part 2 already mounts the user’s volume at /home/user, so their next command reads what they wrote:

Terminal window
pnpm alchemy deploy
curl -X PUT "$URL/users/ada/files/notes.md" -d 'hello from ada'
curl -X POST "$URL/users/ada/run" -d 'ls && cat /home/user/notes.md'

The output lists the repository’s files from Warm, then hello from ada from Ada’s volume. Another user’s sandbox mounts their own volume and never sees hers.

src/Api.ts
const user = /^\/users\/([a-z0-9-]+)$/.exec(url.pathname)?.[1];
if (request.method === "DELETE" && user !== undefined) {
yield* volumes.delete(`home-${user}`);
return HttpServerResponse.empty({ status: 204 });
}

Volumes your Worker creates aren’t deleted with the Stack until its environment is, so delete them when their owner goes away.

Part 4 changes the image.