← Sandbox SDK / sandbox / guides
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
-
Vytvořte bucket R2 pro ukládání záloh:
npx wrangler r2 bucket create my-backup-bucket -
Přidejte
BACKUP_BUCKETR2 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_ENDPOINTnavarsk přepsání výchozího koncového bodu pro presigned URL adresu (napříkladhttps://<ACCOUNT_ID>.eu.r2.cloudflarestorage.compro bucket v regionu EU). -
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_KEYR2 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
- Žádné presigned URL adresy - SDK čte a zapisuje záložní archivy přímo přes R2 binding, takže nejsou potřeba žádné přihlašovací údaje kompatibilní s S3.
- Není vyžadován FUSE - Místní obnova rozbaluje archiv pomocí
unsquashfsmísto připojení pomocí FUSE overlayfs. Záloha se neaplikuje jako copy-on-write overlay, adresář se při obnově nahradí. - Stejný formát archivu - Místní zálohy používají stejný formát archivu squashfs jako produkční zálohy.
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
- Adresáře vlastněné jinými uživateli : Pokud cílový adresář obsahuje podadresáře vytvořené jiným uživatelem nebo procesem (například
/home/sandbox/.claude), archivátor nemusí mít přístup pro čtení. - Restriktivní práva k souborům : Soubory s režimy jako
0600nebo adresáře s0700které patří jinému uživateli, než je ten, jenž spouští proces zálohování. - Konfigurační adresáře generované za běhu : Nástroje a aplikace často vytvářejí konfigurační adresáře (například
.cache,.config, nebo dotfiles specifické pro daný nástroj) s omezujícími oprávněními.
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 deniedTo 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
- Zastavení zápisů před obnovením - Zastavte procesy zapisující do cílového adresáře před voláním
restoreBackup() - Používejte kontrolní body - Před rizikovými operacemi, jako je instalace balíčků nebo migrace, vytvářejte zálohy
- Vyloučit soubory z .gitignore - Nastavte
useGitignore: truepři zálohování git repozitářů, aby se přeskočily generované soubory jakonode_modules/a zmenšit velikost zálohy - Nastavte vhodné hodnoty TTL - U dočasných checkpointů používejte krátké TTL, u trvalých snapshotů delší TTL
- Externí uložení popisovačů - Zachovejte
DirectoryBackuphandles do úložiště KV, D1 nebo Durable Object pro přístup napříč požadavky - Nakonfigurujte pravidla životního cyklu R2 - Nakonfigurujte pravidla životního cyklu objektů pro automatické mazání expirovaných záloh z R2, protože TTL se vynucuje až při obnově
- Vyčistěte staré zálohy - Když již předchozí zálohovací objekty v R2 nepotřebujete, smažte je, nebo použijte vzor delete-then-write pro průběžné zálohování
- Zpracování chyb - Volání zálohování a obnovení obalte do
try...catchblokuje - Znovu obnovit po restartu - V produkčním prostředí je připojení FUSE dočasné, proto po restartu kontejneru proveďte obnovu znovu z popisovače zálohy
Související zdroje
- Referenční dokumentace Backups API - Úplná dokumentace metody
- Referenční dokumentace k Storage API - Připojení bucketů kompatibilních s S3
- Dokumentace R2 - Více o Cloudflare R2
- Pravidla životního cyklu R2 - Nakonfigurujte automatické čištění objektů