diff --git a/EXAMPLES.md b/EXAMPLES.md
index 112a03d5a..a403f7db7 100644
--- a/EXAMPLES.md
+++ b/EXAMPLES.md
@@ -8,6 +8,7 @@ Runnable examples live in [`examples/`](./examples).
## Table of Contents
- [Devbox From Blueprint (Run Command, Shutdown)](#devbox-from-blueprint-lifecycle)
+- [Devbox Snapshot and Resume](#devbox-snapshot-resume)
- [MCP Hub + Claude Code + GitHub](#mcp-github-tools)
@@ -39,6 +40,37 @@ yarn test:examples
**Source:** [`examples/devbox-from-blueprint-lifecycle.ts`](./examples/devbox-from-blueprint-lifecycle.ts)
+
+## Devbox Snapshot and Resume
+
+**Use case:** Create a devbox, snapshot its disk, resume from the snapshot, and demonstrate that changes in the original devbox do not affect the clone.
+
+**Tags:** `devbox`, `snapshot`, `resume`, `cleanup`
+
+### Workflow
+- Create a devbox
+- Write a file to the devbox
+- Create a disk snapshot
+- Create a new devbox from the snapshot
+- Modify the file on the original devbox
+- Verify the clone has the original content
+- Shutdown both devboxes and delete the snapshot
+
+### Prerequisites
+- `RUNLOOP_API_KEY`
+
+### Run
+```sh
+yarn tsn -T examples/devbox-snapshot-resume.ts
+```
+
+### Test
+```sh
+yarn test:examples
+```
+
+**Source:** [`examples/devbox-snapshot-resume.ts`](./examples/devbox-snapshot-resume.ts)
+
## MCP Hub + Claude Code + GitHub
diff --git a/examples/devbox-snapshot-resume.ts b/examples/devbox-snapshot-resume.ts
new file mode 100644
index 000000000..b437ebcfe
--- /dev/null
+++ b/examples/devbox-snapshot-resume.ts
@@ -0,0 +1,127 @@
+#!/usr/bin/env -S npm run tsn -T
+
+/**
+---
+title: Devbox Snapshot and Resume
+slug: devbox-snapshot-resume
+use_case: Create a devbox, snapshot its disk, resume from the snapshot, and demonstrate that changes in the original devbox do not affect the clone.
+workflow:
+ - Create a devbox
+ - Write a file to the devbox
+ - Create a disk snapshot
+ - Create a new devbox from the snapshot
+ - Modify the file on the original devbox
+ - Verify the clone has the original content
+ - Shutdown both devboxes and delete the snapshot
+tags:
+ - devbox
+ - snapshot
+ - resume
+ - cleanup
+prerequisites:
+ - RUNLOOP_API_KEY
+run: yarn tsn -T examples/devbox-snapshot-resume.ts
+test: yarn test:examples
+---
+*/
+
+import { RunloopSDK } from '@runloop/api-client';
+import { wrapRecipe, runAsCli } from './_harness';
+import type { RecipeContext, RecipeOutput } from './types';
+
+const FILE_PATH = '/home/user/welcome.txt';
+const ORIGINAL_CONTENT = 'hello world!';
+const MODIFIED_CONTENT = 'original devbox has changed the welcome message';
+
+export async function recipe(ctx: RecipeContext): Promise {
+ const { cleanup } = ctx;
+
+ const sdk = new RunloopSDK({
+ bearerToken: process.env['RUNLOOP_API_KEY'],
+ });
+
+ // Create a devbox
+ const dbxOriginal = await sdk.devbox.create({
+ name: 'dbx_original',
+ launch_parameters: {
+ resource_size_request: 'X_SMALL',
+ keep_alive_time_seconds: 60 * 5,
+ },
+ });
+ cleanup.add(`devbox:${dbxOriginal.id}`, () => sdk.devbox.fromId(dbxOriginal.id).shutdown());
+
+ // Write a file to the original devbox
+ await dbxOriginal.file.write({ file_path: FILE_PATH, contents: ORIGINAL_CONTENT });
+
+ // Read and display the file contents
+ const catOriginalBefore = await dbxOriginal.cmd.exec(`cat ${FILE_PATH}`);
+ const originalContentBefore = await catOriginalBefore.stdout();
+
+ // Create a disk snapshot of the original devbox
+ const snapshot = await dbxOriginal.snapshotDisk({
+ name: 'my-snapshot',
+ });
+ cleanup.add(`snapshot:${snapshot.id}`, () => snapshot.delete());
+
+ // Create a new devbox from the snapshot
+ const dbxClone = await sdk.devbox.createFromSnapshot(snapshot.id, {
+ name: 'dbx_clone',
+ launch_parameters: {
+ resource_size_request: 'X_SMALL',
+ keep_alive_time_seconds: 60 * 5,
+ },
+ });
+ cleanup.add(`devbox:${dbxClone.id}`, () => sdk.devbox.fromId(dbxClone.id).shutdown());
+
+ // Modify the file on the original devbox
+ await dbxOriginal.file.write({ file_path: FILE_PATH, contents: MODIFIED_CONTENT });
+
+ // Read the file contents from both devboxes
+ const catClone = await dbxClone.cmd.exec(`cat ${FILE_PATH}`);
+ const cloneContent = await catClone.stdout();
+
+ const catOriginalAfter = await dbxOriginal.cmd.exec(`cat ${FILE_PATH}`);
+ const originalContentAfter = await catOriginalAfter.stdout();
+
+ return {
+ resourcesCreated: [`devbox:${dbxOriginal.id}`, `snapshot:${snapshot.id}`, `devbox:${dbxClone.id}`],
+ checks: [
+ {
+ name: 'original devbox file created successfully',
+ passed: catOriginalBefore.exitCode === 0 && originalContentBefore.trim() === ORIGINAL_CONTENT,
+ details: `content="${originalContentBefore.trim()}"`,
+ },
+ {
+ name: 'snapshot created successfully',
+ passed: Boolean(snapshot.id),
+ details: `snapshotId=${snapshot.id}`,
+ },
+ {
+ name: 'clone devbox created from snapshot',
+ passed: Boolean(dbxClone.id),
+ details: `cloneId=${dbxClone.id}`,
+ },
+ {
+ name: 'clone has original file content (before modification)',
+ passed: catClone.exitCode === 0 && cloneContent.trim() === ORIGINAL_CONTENT,
+ details: `cloneContent="${cloneContent.trim()}"`,
+ },
+ {
+ name: 'original devbox has modified content',
+ passed: catOriginalAfter.exitCode === 0 && originalContentAfter.trim() === MODIFIED_CONTENT,
+ details: `originalContent="${originalContentAfter.trim()}"`,
+ },
+ {
+ name: 'clone and original have divergent state',
+ passed: cloneContent.trim() !== originalContentAfter.trim(),
+ details: `clone="${cloneContent.trim()}" vs original="${originalContentAfter.trim()}"`,
+ },
+ ],
+ };
+}
+
+export const runDevboxSnapshotResumeExample = wrapRecipe({ recipe });
+
+if (require.main === module) {
+ void runAsCli(runDevboxSnapshotResumeExample);
+}
diff --git a/examples/registry.ts b/examples/registry.ts
index ab58c4638..e14f0ba5f 100644
--- a/examples/registry.ts
+++ b/examples/registry.ts
@@ -4,6 +4,7 @@
*/
import type { ExampleResult } from './types';
import { runDevboxFromBlueprintLifecycleExample } from './devbox-from-blueprint-lifecycle';
+import { runDevboxSnapshotResumeExample } from './devbox-snapshot-resume';
import { runMcpGithubToolsExample } from './mcp-github-tools';
export interface ExampleRegistryEntry {
@@ -22,6 +23,13 @@ export const exampleRegistry: ExampleRegistryEntry[] = [
requiredEnv: ['RUNLOOP_API_KEY'],
run: runDevboxFromBlueprintLifecycleExample,
},
+ {
+ slug: 'devbox-snapshot-resume',
+ title: 'Devbox Snapshot and Resume',
+ fileName: 'devbox-snapshot-resume.ts',
+ requiredEnv: ['RUNLOOP_API_KEY'],
+ run: runDevboxSnapshotResumeExample,
+ },
{
slug: 'mcp-github-tools',
title: 'MCP Hub + Claude Code + GitHub',
diff --git a/llms.txt b/llms.txt
index f50958633..d09e8bdd1 100644
--- a/llms.txt
+++ b/llms.txt
@@ -10,6 +10,7 @@
## Core Patterns
- [Devbox lifecycle example](examples/devbox-from-blueprint-lifecycle.ts): Create blueprint, launch devbox, run commands, cleanup
+- [Devbox snapshot and resume example](examples/devbox-snapshot-resume.ts): Snapshot disk, resume from snapshot, verify state isolation
- [MCP GitHub example](examples/mcp-github-tools.ts): MCP Hub integration with Claude Code
## API Reference