DirectoryBackup API

Last updated Oct 22, 2026 DirectoryBackup saves a directory from a running container to an R2 bucket and restores it later into a container, including a container that runs a newer image. The container sends the backup through DirectoryBackupGateway in your Worker, so the container needs no Internet access and no R2 credentials. The gateway lets the container reach only the object that the operation needs. DirectoryBackup does not start or stop the container.

import { DirectoryBackup } from "@cloudflare/sandbox";
import { DurableObject } from "cloudflare:workers";

export { DirectoryBackupGateway } from "@cloudflare/sandbox";

export class MyContainer extends DurableObject {
    #backups;

constructor(ctx, env) {
        super(ctx, env);
        if (!ctx.container) {
            throw new Error("No container is configured");
        }
        // ctx.container stays the same object while the Durable Object runs.
        this.#backups = new DirectoryBackup(
            ctx.container,
            ctx.exports.DirectoryBackupGateway,
            { binding: "BACKUPS", prefix: "workspaces/" },
        );
    }

// The container must already be running.
    async saveWorkspace() {
        const backup = await this.#backups.backup({
            dir: "/workspace/app",
            exclude: ["node_modules/"],
        });
        // Keep the record. The package does not list backups.
        await this.ctx.storage.put("workspace", backup);
    }

async restoreWorkspace() {
        const backup = await this.ctx.storage.get("workspace");
        if (backup) {
            await this.#backups.restore(backup);
        }
    }
}
import {
    DirectoryBackup,
    type DirectoryBackupRecord,
} from "@cloudflare/sandbox";
import { DurableObject } from "cloudflare:workers";

export { DirectoryBackupGateway } from "@cloudflare/sandbox";

export class MyContainer extends DurableObject<Env> {
    readonly #backups: DirectoryBackup;

constructor(ctx: DurableObjectState, env: Env) {
        super(ctx, env);
        if (!ctx.container) {
            throw new Error("No container is configured");
        }
        // ctx.container stays the same object while the Durable Object runs.
        this.#backups = new DirectoryBackup(
            ctx.container,
            ctx.exports.DirectoryBackupGateway,
            { binding: "BACKUPS", prefix: "workspaces/" },
        );
    }

// The container must already be running.
    async saveWorkspace() {
        const backup = await this.#backups.backup({
            dir: "/workspace/app",
            exclude: ["node_modules/"],
        });
        // Keep the record. The package does not list backups.
        await this.ctx.storage.put("workspace", backup);
    }

async restoreWorkspace() {
        const backup =
            await this.ctx.storage.get<DirectoryBackupRecord>("workspace");
        if (backup) {
            await this.#backups.restore(backup);
        }
    }
}

For a complete Worker, refer to Back up a directory to R2.

Requirements

In addition to the package requirements, DirectoryBackup has these requirements:

  • The Worker has an R2 bucket binding.
  • The main module of the Worker re-exports DirectoryBackupGateway from @cloudflare/sandbox:
  export { DirectoryBackupGateway } from "@cloudflare/sandbox";
  ```

Do not route HTTP requests to `DirectoryBackupGateway`.

## `DirectoryBackup`

```ts
new DirectoryBackup(
    container: Pick<Container, "exec" | "interceptOutboundHttp">,
    gateway: DirectoryBackupGatewayBinding,
    storage: DirectoryBackupStorage,
)
  • container — the container from this.ctx.container.
  • gateway — this.ctx.exports.DirectoryBackupGateway.
  • storage — where backups are stored. Refer to DirectoryBackupStorage.

An invalid storage throws TypeError.

Methods

backup

backup(options: DirectoryBackupOptions): Promise<DirectoryBackupRecord>

Saves options.dir as one object in the bucket and returns a DirectoryBackupRecord. For the options, refer to DirectoryBackupOptions. What a backup contains lists what the object keeps.

backup() reads the directory while processes can still change it, and it does not detect changes. A file that changes during the backup can be saved partly old and partly new. A backup is consistent only when no process writes to dir while it runs.

A backup that fails or is canceled returns no record, and backup() aborts its multipart upload. If the Durable Object restarts during a backup, the upload stays in the bucket until an R2 lifecycle rule aborts it.

restore

restore(backup: DirectoryBackupRecord, options?: DirectoryRestoreOptions): Promise<void>

Replaces the target directory with the contents of the backup. The target is dir from the record, unless you pass another absolute path in options.dir.

  • The parent directory of the target must exist. The target does not need to exist.
  • The target must not be a mount point or contain one, such as an S3Mount mount. Otherwise restore() fails with EBUSY, including when a mount appears during the restore. Unmount first, for example with S3Mount.unmount(), and mount again after the restore.
  • restore() extracts the backup into a new directory beside the target and checks the size and SHA-256 of the object against the record. It then replaces the target with the new directory in one atomic rename. When restore() throws SandboxFileError or SandboxBackupError, or is canceled before it verifies the download, it leaves the target as it was and removes the new directory.
  • A process that has its working directory inside the target, or has a file in the target open, keeps using the old directory or file. Restart such processes after restore(), or have them reopen files by absolute path.
  • The filesystem needs free space for the old and the restored directory at the same time. restore() stops with ENOSPC before free space falls below 5% of the filesystem or 256 MiB, whichever is smaller.
  • The root filesystem of a deployed container compresses files. restore() writes files without compression. The restored files take more disk space until a process rewrites them.

delete

delete(backup: DirectoryBackupRecord, options?: DirectoryBackupDeleteOptions): Promise<void>

Deletes the backup object. The container does not need to be running. Deleting an object that does not exist succeeds. A restore() that reads the object at the same time fails with BACKUP_NOT_FOUND or BACKUP_INTEGRITY.

intercept

intercept(): Promise<void>

Routes backup traffic from the container to DirectoryBackupGateway, which refuses every request until a backup or restore starts. intercept() does not start the container.

backup() and restore() add this route themselves. If the Durable Object also calls interceptAllOutboundHttp(), call intercept() before it. Otherwise the catch-all receives the backup traffic from the container, and backup() and restore() fail with BACKUP_TRANSFER.

  • Call intercept() in the setup that runs after you start the container, and again when a restarted Durable Object finds the container running. Backups and restores in that container then keep their route.
  • Do not call intercept() while a backup or restore can be running. It replaces the access of the running operation, and the operation fails.
  • A container that registered the catch-all first keeps sending backup traffic to it. Restart the container to use intercept().
#setup: Promise<void> | null = null;

// Call before each use of the container.
#ensureRunning(container: Container): Promise<void> {
    // Set up each new container, and a running container
    // after this Durable Object restarts.
    if (this.#setup === null || !container.running) {
        this.#setup = this.#setUp(container).catch((error) => {
            this.#setup = null;
            throw error;
        });
    }
    return this.#setup;
}

async #setUp(container: Container): Promise<void> {
    if (!container.running) {
        container.start({
            image: container.images.sandbox,
            enableInternet: false,
        });
    }
    await this.#backups.intercept();
    await container.interceptAllOutboundHttp(
        this.ctx.exports.Outbound({ props: {} }),
    );
}

Types

DirectoryBackupRecord

The record that backup() returns. It is a plain object that you can store in Durable Object storage, a database, or JSON. The package keeps no list of backups.

  • id string — UUID of the backup. The object key is <prefix><id>.tar.zst.
  • dir string — absolute path of the directory that was backed up, and the default restore target.
  • size number — size of the stored object in bytes.
  • name string optional — the name passed to backup().
  • sha256 string — SHA-256 of the stored object, as 64 lowercase hexadecimal characters. Every restore() checks it.
  • format "tar+zstd/1" — archive format of the object.

restore() and delete() use the object at <prefix><id>.tar.zst in the bucket and prefix that the DirectoryBackup instance was created with, so pass a record to an instance whose storage holds that object. restore() checks the size and SHA-256 of the object against the record. A record that does not match this shape throws TypeError.

DirectoryBackupStorage

  • binding string — name of the R2 bucket binding in the Worker env, such as "BACKUPS".
  • prefix string optional — key prefix for the objects. A prefix that is not empty must end in /. The default is no prefix.

DirectoryBackupOptions

  • dir string — absolute path of the directory to back up.
  • name string optional — label stored in the record and in the custom metadata of the object as name.
  • exclude string[] optional — patterns in .gitignore syntax ↗︎, relative to dir. "node_modules/" excludes every directory named node_modules. "/build" excludes only build at the top of dir.
  • gitignore boolean optional — also apply the .gitignore files inside dir and .git/info/exclude. Global Git excludes do not apply. The image does not need git. The default is false.
  • signal AbortSignal optional — cancels the operation. Refer to Cancellation.