INTEGRITY Dokumentace

Zálohování a obnova

Vytvářejte snímky adresářů sandboxu v daném okamžiku a obnovujte je pomocí překryvů copy-on-write. Zálohy se ukládají do bucketu R2 a používají kompresi squashfs.

Zálohování a obnovení použijte, když chcete stav pracovního prostoru, jako je /workspace, abyste se k nim mohli později vrátit. To se často lépe hodí pro trvalé adresáře projektu, zatímco připojení bucketu je vhodnější pro oddělené trvalé cesty úložiště. Pokud připojíte bucket přes /workspace, mějte na paměti, že v produkci může překrýt soubory vložené vaším obrazem.

Předpoklady

  1. Vytvořte bucket R2 pro ukládání záloh:

    npx wrangler r2 bucket create my-backup-bucket
  2. Přidejte BACKUP_BUCKET R2 binding a přihlašovací údaje pro presigned URL do konfigurace Wrangler:

    {
    	"name": "my-sandbox-worker",
    	"main": "src/index.ts",
    	// Set this to today's date
    	"compatibility_date": "2026-08-28",
    	"compatibility_flags": ["nodejs_compat"],
    	"containers": [
    		{
    			"class_name": "Sandbox",
    			"image": "./Dockerfile",
    		},
    	],
    	"durable_objects": {
    		"bindings": [
    			{
    				"class_name": "Sandbox",
    				"name": "Sandbox",
    			},
    		],
    	},
    	"migrations": [
    		{
    			"new_sqlite_classes": ["Sandbox"],
    			"tag": "v1",
    		},
    	],
    	"vars": {
    		"BACKUP_BUCKET_NAME": "my-backup-bucket",
    		"CLOUDFLARE_ACCOUNT_ID": "<YOUR_ACCOUNT_ID>",
    	},
    	"r2_buckets": [
    		{
    			"binding": "BACKUP_BUCKET",
    			"bucket_name": "my-backup-bucket",
    		},
    	],
    }
    name = "my-sandbox-worker"
    main = "src/index.ts"
    # Set this to today's date
    compatibility_date = "2026-08-28"
    compatibility_flags = [ "nodejs_compat" ]
    
    [[containers]]
    class_name = "Sandbox"
    image = "./Dockerfile"
    
    [[durable_objects.bindings]]
    class_name = "Sandbox"
    name = "Sandbox"
    
    [[migrations]]
    new_sqlite_classes = [ "Sandbox" ]
    tag = "v1"
    
    [vars]
    BACKUP_BUCKET_NAME = "my-backup-bucket"
    CLOUDFLARE_ACCOUNT_ID = "<YOUR_ACCOUNT_ID>"
    
    [[r2_buckets]]
    binding = "BACKUP_BUCKET"
    bucket_name = "my-backup-bucket"

    Pokud váš bucket R2 používá endpoint specifický pro danou jurisdikci, můžete také přidat BACKUP_BUCKET_ENDPOINT na vars k přepsání výchozího koncového bodu pro presigned URL adresu (například https://<ACCOUNT_ID>.eu.r2.cloudflarestorage.com pro bucket v regionu EU).

  3. Nastavte přihlašovací údaje R2 API jako tajné klíče:

    npx wrangler secret put R2_ACCESS_KEY_ID
    npx wrangler secret put R2_SECRET_ACCESS_KEY

    R2 API tokeny můžete vytvořit v Cloudflare dashboard ↗ v části R2 > Přehled > Správa tokenů R2 API. Token vyžaduje Object Read & Write oprávnění pro bucket se zálohami.

Vytvoření zálohy

Použijte createBackup() k vytvoření snímku adresáře a jeho nahrání do R2:

import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Create a backup of /workspace
const backup = await sandbox.createBackup({ dir: "/workspace" });
console.log(`Backup created: ${backup.id}`);
import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Create a backup of /workspace
const backup = await sandbox.createBackup({ dir: "/workspace" });
console.log(`Backup created: ${backup.id}`);

SDK vytvoří komprimovaný archiv squashfs z adresáře a nahraje ho přímo do vašeho R2 bucketu pomocí presigned URL adresy.

Obnovení zálohy

Použijte restoreBackup() k obnovení adresáře ze zálohy:

import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Create a backup
const backup = await sandbox.createBackup({ dir: "/workspace" });

// Restore the backup
const result = await sandbox.restoreBackup(backup);
console.log(`Restored: ${result.success}`);
import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Create a backup
const backup = await sandbox.createBackup({ dir: "/workspace" });

// Restore the backup
const result = await sandbox.restoreBackup(backup);
console.log(`Restored: ${result.success}`);

Vyloučit soubory z .gitignore

Při zálohování adresáře uvnitř git repozitáře nastavte useGitignore: true pro vyloučení souborů odpovídajících .gitignore pravidla. To se hodí pro vynechání velkých generovaných adresářů, jako je node_modules/, dist/, nebo build/ které lze znovu vytvořit.

import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Back up only tracked and untracked non-ignored files
const backup = await sandbox.createBackup({
	dir: "/workspace",
	useGitignore: true,
});
import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Back up only tracked and untracked non-ignored files
const backup = await sandbox.createBackup({
	dir: "/workspace",
	useGitignore: true,
});

SDK používá git ls-files k určení toho, které soubory se ignorují. Zohledňují se soubory na úrovni kořenového adresáře i vnořené .gitignore soubory se respektují.

Standardně useGitignore je false a všechny soubory v adresáři jsou součástí zálohy.

Checkpoint a rollback

Před rizikovými operacemi uložte stav a v případě selhání ho obnovte:

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Save checkpoint before risky operation
const checkpoint = await sandbox.createBackup({ dir: "/workspace" });

try {
	await sandbox.exec("npm install some-experimental-package");
	await sandbox.exec("npm run build");
} catch (error) {
	// Restore to checkpoint if something goes wrong
	await sandbox.restoreBackup(checkpoint);
	console.log("Rolled back to checkpoint");
}
const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Save checkpoint before risky operation
const checkpoint = await sandbox.createBackup({ dir: "/workspace" });

try {
	await sandbox.exec("npm install some-experimental-package");
	await sandbox.exec("npm run build");
} catch (error) {
	// Restore to checkpoint if something goes wrong
	await sandbox.restoreBackup(checkpoint);
	console.log("Rolled back to checkpoint");
}

Uložení popisovačů zálohy

DirectoryBackup handle je serializovatelný. Uložte ho trvale do úložiště KV, D1 nebo Durable Object pro pozdější použití:

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Create a backup and store the handle in KV
const backup = await sandbox.createBackup({
	dir: "/workspace",
	name: "deploy-v2",
	ttl: 604800, // 7 days
});

await env.KV.put(`backup:${userId}`, JSON.stringify(backup));

// Later, retrieve and restore
const stored = await env.KV.get(`backup:${userId}`);
if (stored) {
	const backupHandle = JSON.parse(stored);
	await sandbox.restoreBackup(backupHandle);
}
const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Create a backup and store the handle in KV
const backup = await sandbox.createBackup({
	dir: "/workspace",
	name: "deploy-v2",
	ttl: 604800, // 7 days
});

await env.KV.put(`backup:${userId}`, JSON.stringify(backup));

// Later, retrieve and restore
const stored = await env.KV.get(`backup:${userId}`);
if (stored) {
	const backupHandle = JSON.parse(stored);
	await sandbox.restoreBackup(backupHandle);
}

Používejte pojmenované zálohy

Přidejte name volba pro identifikaci záloh. Názvy mohou mít až 256 znaků:

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

const backup = await sandbox.createBackup({
	dir: "/workspace",
	name: "before-migration",
});

console.log(`Backup ID: ${backup.id}`);
const sandbox = getSandbox(env.Sandbox, "my-sandbox");

const backup = await sandbox.createBackup({
	dir: "/workspace",
	name: "before-migration",
});

console.log(`Backup ID: ${backup.id}`);

Nakonfigurujte TTL

Nastavte vlastní dobu platnosti (TTL) pro zálohy. Výchozí TTL je 3 dny (259200 sekund). Parametr ttl hodnota musí být kladné číslo vyjadřující počet sekund:

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Short-lived backup for a quick operation
const shortBackup = await sandbox.createBackup({
	dir: "/workspace",
	ttl: 600, // 10 minutes
});

// Long-lived backup for extended workflows
const longBackup = await sandbox.createBackup({
	dir: "/workspace",
	name: "daily-snapshot",
	ttl: 604800, // 7 days
});
const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Short-lived backup for a quick operation
const shortBackup = await sandbox.createBackup({
	dir: "/workspace",
	ttl: 600, // 10 minutes
});

// Long-lived backup for extended workflows
const longBackup = await sandbox.createBackup({
	dir: "/workspace",
	name: "daily-snapshot",
	ttl: 604800, // 7 days
});

Jak se vynucuje TTL

TTL se vynucuje na úrovni čas obnovy, nikoli v okamžiku vytvoření. Když zavoláte restoreBackup(), SDK načte metadata zálohy z R2 a porovná časové razítko vytvoření plus TTL s aktuálním časem (s 60sekundovou rezervou, která zabraňuje souběhu). Pokud platnost TTL uplynula, obnovení se odmítne s chybou BACKUP_EXPIRED chyba.

TTL ne automaticky mazal objekty zálohy z R2. Zálohy s vypršenou platností zůstávají v bucketu a nadále zabírají úložný prostor, dokud je výslovně neodstraníte nebo nenastavíte pravidlo automatického čištění.

Nakonfigurujte pravidla životního cyklu R2 pro automatické čištění

Chcete-li automaticky odstraňovat vypršelé záložní objekty z R2, nastavte Pravidlo životního cyklu objektu R2 na vašem záložním bucketu. Toto je doporučený způsob, jak zabránit tomu, aby se expirované zálohy neomezeně hromadily.

Pokud je například vaše nejdelší TTL 7 dní, nastavte lifecycle rule, které bude mazat objekty starší než 7 dní z backups/ prefix. Díky tomu úložiště R2 neroste neomezeně, a zároveň máte rezervu pro obnovení jakékoli zálohy, která ještě nevypršela.

Lokální vývoj

Zálohování a obnovení můžete během lokálního vývoje použít pomocí wrangler dev předáním localBucket: true možnost na createBackup(). Používá se BACKUP_BUCKET R2 binding přímo z prostředí vašeho Workeru, takže nejsou potřeba žádné přihlašovací údaje pro presigned URL.

Nakonfigurujte R2 binding

Přidejte BACKUP_BUCKET R2 binding do konfigurace Wrangler:

{
	"r2_buckets": [
		{
			"binding": "BACKUP_BUCKET",
			"bucket_name": "my-backup-bucket"
		}
	]
}
[[r2_buckets]]
binding = "BACKUP_BUCKET"
bucket_name = "my-backup-bucket"

Zálohujte a obnovujte pomocí localBucket

Předejte localBucket: true na createBackup() pro zálohování a obnovu přímo pomocí R2 bindingu:

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Create a local backup
const backup = await sandbox.createBackup({
	dir: "/workspace",
	localBucket: true,
});

// Restore the backup
const result = await sandbox.restoreBackup(backup);
console.log(`Restored: ${result.success}`);
const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Create a local backup
const backup = await sandbox.createBackup({
	dir: "/workspace",
	localBucket: true,
});

// Restore the backup
const result = await sandbox.restoreBackup(backup);
console.log(`Restored: ${result.success}`);

Aspekty místního vývoje

Vyčistěte záložní objekty v R2

Archivy záloh se ukládají do vašeho R2 bucketu pod backups/ prefix se strukturou backups/{backupId}/data.sqsh a backups/{backupId}/meta.json. Můžete použít BACKUP_BUCKET R2 binding pro přímou správu těchto objektů.

Nahrazení nejnovější zálohy (nejprve smazat, poté zapsat)

Pokud potřebujete jen nejnovější zálohu, smažte předchozí ještě před vytvořením nové:

import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Delete the previous backup's R2 objects before creating a new one
if (previousBackup) {
	await env.BACKUP_BUCKET.delete(`backups/${previousBackup.id}/data.sqsh`);
	await env.BACKUP_BUCKET.delete(`backups/${previousBackup.id}/meta.json`);
}

// Create a fresh backup
const backup = await sandbox.createBackup({
	dir: "/workspace",
	name: "latest",
});

// Store the handle so you can delete it next time
await env.KV.put("latest-backup", JSON.stringify(backup));
import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Delete the previous backup's R2 objects before creating a new one
if (previousBackup) {
	await env.BACKUP_BUCKET.delete(`backups/${previousBackup.id}/data.sqsh`);
	await env.BACKUP_BUCKET.delete(`backups/${previousBackup.id}/meta.json`);
}

// Create a fresh backup
const backup = await sandbox.createBackup({
	dir: "/workspace",
	name: "latest",
});

// Store the handle so you can delete it next time
await env.KV.put("latest-backup", JSON.stringify(backup));

Vypište a odstraňte staré zálohy podle prefixu

Chcete-li vyčistit více starých záloh, vypište objekty pod backups/ prefix a smazat je podle klíče:

// List all backup objects in the bucket
const listed = await env.BACKUP_BUCKET.list({ prefix: "backups/" });

for (const object of listed.objects) {
	// Parse the backup ID from the key (backups/{id}/data.sqsh or backups/{id}/meta.json)
	const parts = object.key.split("/");
	const backupId = parts[1];

	// Delete objects older than 7 days
	const ageMs = Date.now() - object.uploaded.getTime();
	const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
	if (ageMs > sevenDaysMs) {
		await env.BACKUP_BUCKET.delete(object.key);
		console.log(`Deleted expired object: ${object.key}`);
	}
}
// List all backup objects in the bucket
const listed = await env.BACKUP_BUCKET.list({ prefix: "backups/" });

for (const object of listed.objects) {
	// Parse the backup ID from the key (backups/{id}/data.sqsh or backups/{id}/meta.json)
	const parts = object.key.split("/");
	const backupId = parts[1];

	// Delete objects older than 7 days
	const ageMs = Date.now() - object.uploaded.getTime();
	const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
	if (ageMs > sevenDaysMs) {
		await env.BACKUP_BUCKET.delete(object.key);
		console.log(`Deleted expired object: ${object.key}`);
	}
}

Odstraní konkrétní zálohu podle ID

Pokud máte ID zálohy, smažte přímo její archiv i metadata:

const backupId = backup.id;

await env.BACKUP_BUCKET.delete(`backups/${backupId}/data.sqsh`);
await env.BACKUP_BUCKET.delete(`backups/${backupId}/meta.json`);
const backupId = backup.id;

await env.BACKUP_BUCKET.delete(`backups/${backupId}/data.sqsh`);
await env.BACKUP_BUCKET.delete(`backups/${backupId}/meta.json`);

Chování copy-on-write

V produkčním prostředí obnova používá FUSE overlayfs k připojení zálohy jako spodní vrstvy jen pro čtení. Nové zápisy směřují do zapisovatelné horní vrstvy a neovlivňují původní zálohu:

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Create a backup
const backup = await sandbox.createBackup({ dir: "/workspace" });

// Restore the backup
await sandbox.restoreBackup(backup);

// New writes go to the upper layer — the backup is unchanged
await sandbox.writeFile(
	"/workspace/new-file.txt",
	"This does not modify the backup",
);

// Restore the same backup again to discard changes
await sandbox.restoreBackup(backup);
const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Create a backup
const backup = await sandbox.createBackup({ dir: "/workspace" });

// Restore the backup
await sandbox.restoreBackup(backup);

// New writes go to the upper layer — the backup is unchanged
await sandbox.writeFile(
	"/workspace/new-file.txt",
	"This does not modify the backup",
);

// Restore the same backup again to discard changes
await sandbox.restoreBackup(backup);

Zpracování chyb

Operace zálohování a obnovy mohou vyvolat specifické chyby. Volání obalte do try...catch ↗ blokuje:

import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Handle backup errors
try {
	const backup = await sandbox.createBackup({ dir: "/workspace" });
} catch (error) {
	if (error.code === "INVALID_BACKUP_CONFIG") {
		// Missing BACKUP_BUCKET binding or invalid directory path
		console.error("Configuration error:", error.message);
	} else if (error.code === "BACKUP_CREATE_FAILED") {
		// Archive creation or upload to R2 failed
		console.error("Backup failed:", error.message);
	}
}

// Handle restore errors
try {
	await sandbox.restoreBackup(backup);
} catch (error) {
	if (error.code === "BACKUP_NOT_FOUND") {
		console.error("Backup not found in R2:", error.message);
	} else if (error.code === "BACKUP_EXPIRED") {
		console.error("Backup TTL has elapsed:", error.message);
	} else if (error.code === "BACKUP_RESTORE_FAILED") {
		console.error("Restore failed:", error.message);
	}
}
import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "my-sandbox");

// Handle backup errors
try {
	const backup = await sandbox.createBackup({ dir: "/workspace" });
} catch (error) {
	if (error.code === "INVALID_BACKUP_CONFIG") {
		// Missing BACKUP_BUCKET binding or invalid directory path
		console.error("Configuration error:", error.message);
	} else if (error.code === "BACKUP_CREATE_FAILED") {
		// Archive creation or upload to R2 failed
		console.error("Backup failed:", error.message);
	}
}

// Handle restore errors
try {
	await sandbox.restoreBackup(backup);
} catch (error) {
	if (error.code === "BACKUP_NOT_FOUND") {
		console.error("Backup not found in R2:", error.message);
	} else if (error.code === "BACKUP_EXPIRED") {
		console.error("Backup TTL has elapsed:", error.message);
	} else if (error.code === "BACKUP_RESTORE_FAILED") {
		console.error("Restore failed:", error.message);
	}
}

Oprávnění cesty

createBackup() metoda používá mksquashfs pro vytvoření komprimovaného archivu cílového adresáře. Tento proces musí umět přečíst každý soubor a podadresář v rámci zálohované cesty. Pokud má některý soubor nebo adresář omezená oprávnění, která archivátoru brání ve čtení, zálohování selže s BackupCreateError a zprávu „Permission denied“.

Časté příčiny

Opravte oprávnění v době sestavení

Doporučeným postupem je nastavit oprávnění v Dockerfile, aby každý kontejner startoval se správným přístupem. Tím se předejde spouštění chmod za běhu před každou zálohou:

# Ensure the backup target directory is readable
RUN mkdir -p /home/sandbox && chmod -R a+rX /home/sandbox

a+rX příznak uděluje přístup pro čtení ke všem souborům a přístup pro spuštění (průchod) ke všem adresářům, aniž by měnil oprávnění k zápisu.

Opravte oprávnění za běhu

Pokud omezující oprávnění pocházejí ze souborů vytvořených za běhu (například nástroj, který generuje konfigurační soubory s 0600 režim), opravte je ještě před voláním createBackup():

await sandbox.exec("chmod -R a+rX /home/sandbox/.claude");
const backup = await sandbox.createBackup({ dir: "/home/sandbox" });

Příklad chyby

Pokud při zálohování dojde k problému s oprávněními, zobrazí se chyba podobná této:

BackupCreateError: mksquashfs failed: Could not create destination file: Permission denied

To znamená mksquashfs se nepodařilo přečíst jeden nebo více souborů uvnitř adresáře, který jste předali do createBackup(). Zkontrolujte oprávnění všech souborů a podadresářů v dané cestě.

Osvědčené postupy