Save and restore a sandbox with snapshots

Last updated Oct 22, 2026 A Durable Object saves the filesystem of a sandbox as a Container snapshot, stores the snapshot ID, and starts the next instance from the snapshot. In this example, the sandbox keeps a notes file in /workspace.

Public beta

Container snapshots are in public beta. Features and behavior may change.

Prerequisites

Save and restore a sandbox

  1. Add an inactivity timeout to your Durable Object. The constructor sets the timeout again when a restarted Durable Object finds the container running:

src/index.tsts

```ts const INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;

export class MyContainer extends DurableObject { constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); const container = ctx.container; if (container?.running) { void ctx.blockConcurrencyWhile(() => container.setInactivityTimeout(INACTIVITY_TIMEOUT_MS), ); } } } ```

Then start the container from the stored snapshot when there is one:

src/index.tsts

```ts export class MyContainer extends DurableObject { // ...

private async startSandbox(): Promise<Container> {
    const container = this.ctx.container;

    if (!container) {
        throw new Error("No container is configured");
    }

    if (!container.running) {
        const snapshotId = await this.ctx.storage.get<string>("snapshotId");

        container.start({
            ...(snapshotId
                ? { containerSnapshot: { id: snapshotId } }
                : { image: "cloudflare/debian-trixie" }),
            entrypoint: ["sleep", "infinity"],
            enableInternet: false,
        });
        await container.setInactivityTimeout(INACTIVITY_TIMEOUT_MS);
    }

    return container;
}

} ```

If the stored snapshot cannot be restored, the request fails with an error such as Snapshot "<SNAPSHOT_ID>" was not found. Later requests fail the same way until the stored ID changes. The method does not fall back to the image, because the sandbox would then start with an empty workspace. 2. Add a method that runs one operation at a time, and a method that saves the filesystem and stops the instance:

src/index.tsts

```ts export class MyContainer extends DurableObject { // ...

private queue: Promise<unknown> = Promise.resolve();

// Runs each operation after the previous one finishes.
private exclusive<T>(operation: () => Promise<T>): Promise<T> {
    const result = this.queue.then(operation);
    this.queue = result.catch(() => {});
    return result;
}

async save() {
    return this.exclusive(async () => {
        const container = await this.startSandbox();
        const snapshot = await container.snapshotContainer({ name: "workspace" });

        await this.ctx.storage.put("snapshotId", snapshot.id);
        await container.destroy();

        return { snapshotId: snapshot.id, size: snapshot.size };
    });
}

} ```

A command that writes a file while the snapshot runs can succeed and then disappear when destroy() stops the instance. exclusive() makes every operation that touches the disk wait for the save, and the save wait for them. Run each such method through it.

A snapshot saves the writable root filesystem. It does not include mounted directories, memory, or running processes. Finish writing files before you save. For more information about what a restored instance starts with, refer to Sandbox lifetime. 3. Add a method that adds a line to a file and returns the file, so you can check what a restore brings back:

src/index.tsts

```ts export class MyContainer extends DurableObject { // ...

async note(line?: string) {
    return this.exclusive(async () => {
        const container = await this.startSandbox();
        const script = line
            ? 'mkdir -p /workspace && printf "%s\n" "$1" >> /workspace/notes.txt && cat /workspace/notes.txt'
            : "cat /workspace/notes.txt";
        const process = await container.exec(["sh", "-c", script, "sh", line ?? ""]);
        const output = await process.output();

        return new TextDecoder().decode(output.stdout);
    });
}

} ```

  1. Add routes to your Worker that write a note, save the sandbox, and read the notes:

src/index.jsjs

```js export default { async fetch(request, env) { const url = new URL(request.url); const match = /^\/sandboxes\/(a-z0-9?)\/(notes|snapshot)$/.exec( url.pathname, );

    if (!match) {
        return new Response("Not found", { status: 404 });
    }

    const [, name, action] = match;
    const sandbox = env.MY_CONTAINER.getByName(name);

    if (action === "snapshot" && request.method === "POST") {
        return Response.json(await sandbox.save());
    }

    if (action === "notes" && request.method === "POST") {
        return new Response(await sandbox.note(await request.text()));
    }

    if (action === "notes") {
        return new Response(await sandbox.note());
    }

    return new Response("Method not allowed", { status: 405 });
},

}; ```

src/index.tsts

```ts export default { async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); const match = /^\/sandboxes\/(a-z0-9?)\/(notes|snapshot)$/.exec( url.pathname, );

    if (!match) {
        return new Response("Not found", { status: 404 });
    }

    const [, name, action] = match;
    const sandbox = env.MY_CONTAINER.getByName(name);

    if (action === "snapshot" && request.method === "POST") {
        return Response.json(await sandbox.save());
    }

    if (action === "notes" && request.method === "POST") {
        return new Response(await sandbox.note(await request.text()));
    }

    if (action === "notes") {
        return new Response(await sandbox.note());
    }

    return new Response("Method not allowed", { status: 405 });
},

} satisfies ExportedHandler; ```

Snapshots can contain credentials or other sensitive files written in the sandbox. Authenticate callers first, so other people cannot read or restore the files of another sandbox. For more information, refer to Sandbox security. 5. Deploy your Worker:npmyarnpnpm

npx wrangler deploy

yarn wrangler deploy

pnpm wrangler deploy

  1. Write a note in the sandbox named ada, save the sandbox, then read the notes. Replace the example hostname with the workers.dev URL that Wrangler prints:

sh curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/sandboxes/ada/notes --data "Continue from this checkpoint." curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/sandboxes/ada/snapshot --request POST curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/sandboxes/ada/notes

The save responds with the snapshot ID and its size in bytes, such as {"snapshotId":"<SNAPSHOT_ID>","size":123456}. The last request starts a new instance from the snapshot, and the note is back:

txt Continue from this checkpoint.

Copy a sandbox

A snapshot ID restores in any Durable Object of the same class. To copy the files of one sandbox to a new sandbox, store the snapshot ID in the Durable Object of the new sandbox:

  1. Add methods to your Durable Object that return the stored snapshot ID and store another one:

src/index.tsts

```ts export class MyContainer extends DurableObject { // ...

async snapshotId() {
    return (await this.ctx.storage.get<string>("snapshotId")) ?? null;
}

async useSnapshot(snapshotId: string) {
    return this.exclusive(async () => {
        // Replacing a running instance would discard files it has not saved.
        if (this.ctx.container?.running) {
            return false;
        }

        await this.ctx.storage.put("snapshotId", snapshotId);
        return true;
    });
}

} ```

  1. Add a route to your Worker that copies one sandbox to another:

src/index.tsts

```ts if (action === "copy" && request.method === "POST") { const from = url.searchParams.get("from") ?? ""; const snapshotId = await env.MY_CONTAINER.getByName(from).snapshotId();

if (!snapshotId || !(await sandbox.useSnapshot(snapshotId))) {
    return new Response("Cannot copy", { status: 409 });
}

return new Response(null, { status: 204 });

} ```

Add copy to the actions in the route pattern. A copy receives every file in the snapshot, so copy only between sandboxes that belong to the same user. 3. Copy ada to grace, then read the notes from grace:

sh curl "https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/sandboxes/grace/copy?from=ada" --request POST curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/sandboxes/grace/notes

txt Continue from this checkpoint.

From this point, each sandbox changes its own files.

Start a sandbox over

To discard the files of a sandbox, destroy its instance and delete the stored snapshot ID. The next request starts from the image:

src/index.tsts

export class MyContainer extends DurableObject<Env> {
    // ...

    async reset() {
        return this.exclusive(async () => {
            if (this.ctx.container?.running) {
                await this.ctx.container.destroy();
            }

            await this.ctx.storage.delete("snapshotId");
        });
    }
}

Deleting the stored ID does not delete the snapshot, so a sandbox that copied it can still restore it until the snapshot expires.

Was this helpful?

YesNo

On this page

Save and restore a sandbox with snapshot - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/files/save-and-restore-a-workspace/#page","headline":"Save and restore a sandbox with snapshots","description":"Save the filesystem of a sandbox as a Container snapshot, stop its Linux instance, and restore the files in a new instance.","url":"https://developers.cloudflare.com/sandbox/files/save-and-restore-a-workspace/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/files/save-and-restore-a-workspace/og.png?v=18cbde6e5d6299aa","dateModified":"2026-10-22","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Open a web server that runs in a Linux sandbox through your Worker, keep its WebSocket connections open, and share it with a link. title: Preview a web application image: https://developers.cloudflare.com/sandbox/previews/og.png?v=a671b00fd45ffa2b


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.