From 25253bbc72b4cbd7ca9a38dcde799e12e524801a Mon Sep 17 00:00:00 2001 From: Greg Anders Date: Thu, 26 Mar 2026 16:05:32 -0500 Subject: [PATCH] Add docs and changelog for containers snapshots --- .../containers/2026-03-26-snapshots.mdx | 72 +++++++ src/content/docs/containers/faq.mdx | 6 +- .../platform-details/architecture.mdx | 9 +- .../containers/platform-details/snapshots.mdx | 181 ++++++++++++++++++ .../docs/durable-objects/api/container.mdx | 88 +++++++++ 5 files changed, 347 insertions(+), 9 deletions(-) create mode 100644 src/content/changelog/containers/2026-03-26-snapshots.mdx create mode 100644 src/content/docs/containers/platform-details/snapshots.mdx diff --git a/src/content/changelog/containers/2026-03-26-snapshots.mdx b/src/content/changelog/containers/2026-03-26-snapshots.mdx new file mode 100644 index 00000000000000..d746dfb485dcee --- /dev/null +++ b/src/content/changelog/containers/2026-03-26-snapshots.mdx @@ -0,0 +1,72 @@ +--- +title: Snapshot and restore Container state +description: Persist point-in-time container filesystem with experimental snapshot APIs. +products: + - containers +date: 2026-03-26 +--- + +import { TypeScriptExample, WranglerConfig } from "~/components"; + +[Containers](/containers/) now support experimental snapshot APIs for saving and restoring point-in-time filesystem state. Create a snapshot first, then pass it back to `start()` to restore files after container sleep, restart, or handoff to another Durable Object. + +Turn on the `experimental` [compatibility flag](/workers/configuration/compatibility-flags/) before you use snapshot APIs: + + + +```toml +compatibility_flags = ["experimental"] +``` + + + +You can snapshot a single directory with `snapshotDirectory()` or the full container with `snapshotContainer()`. The example uses the [Container class](/containers/container-package/). Create snapshots from a container that is already running: + + + +```ts +import { Container } from "@cloudflare/containers"; + +export class MyContainer extends Container { + async saveSnapshots() { + const directorySnapshot = await this.ctx.container.snapshotDirectory({ + dir: "/app/foo", + }); + + const containerSnapshot = await this.ctx.container.snapshotContainer({}); + + await this.ctx.storage.put("directorySnapshot", directorySnapshot); + await this.ctx.storage.put("containerSnapshot", containerSnapshot); + } +} +``` + + + +Later, if you saved both snapshot types, load the saved snapshot handles and restore them with `start()`: + + + +```ts +import { Container } from "@cloudflare/containers"; + +export class MyContainer extends Container { + async restoreSnapshots() { + const directorySnapshot = + await this.ctx.storage.get("directorySnapshot"); + const containerSnapshot = + await this.ctx.storage.get("containerSnapshot"); + + this.ctx.container.start({ + containerSnapshot, + directorySnapshots: [{ snapshot: directorySnapshot }], + }); + } +} +``` + + + +Snapshots are immutable. Directory snapshots can be restored to a different `mountPoint`. You can restore multiple directory snapshots in one `start()` call, but only one container snapshot. + +For more information, refer to [Snapshots](/containers/platform-details/snapshots/) and [Durable Object Container](/durable-objects/api/container/). diff --git a/src/content/docs/containers/faq.mdx b/src/content/docs/containers/faq.mdx index 071b2d44f65c44..38e4919825d770 100644 --- a/src/content/docs/containers/faq.mdx +++ b/src/content/docs/containers/faq.mdx @@ -89,11 +89,9 @@ See [image management documentation](/containers/platform-details/image-manageme ## Is disk persistent? What happens to my disk when my container sleeps? -All disk is ephemeral. When a Container instance goes to sleep, the next time -it is started, it will have a fresh disk as defined by its container image. +All disk is ephemeral by default. When a Container instance goes to sleep, the next time it starts, it uses a fresh disk from the container image. -Persistent disk is something the Cloudflare team is exploring in the future, but -is not slated for the near term. +If you need point-in-time filesystem state, create and restore an experimental snapshot. Snapshots are immutable, so later file changes require a new snapshot. For more information, refer to [Snapshots](/containers/platform-details/snapshots/). ## What happens if I run out of memory? diff --git a/src/content/docs/containers/platform-details/architecture.mdx b/src/content/docs/containers/platform-details/architecture.mdx index c970c73cb10860..b0768f736ea789 100644 --- a/src/content/docs/containers/platform-details/architecture.mdx +++ b/src/content/docs/containers/platform-details/architecture.mdx @@ -98,12 +98,11 @@ When a container instance is going to be shut down, it is sent a `SIGTERM` signa and then a `SIGKILL` signal after 15 minutes. You should perform any necessary cleanup to ensure a graceful shutdown in this time. -#### Persistent disk +#### Use snapshots -All disk is ephemeral. When a Container instance goes to sleep, the next time -it is started, it will have a fresh disk as defined by its container image. -Persistent disk is something the Cloudflare team is exploring in the future, but -is not slated for the near term. +All disk is ephemeral by default. When a Container instance goes to sleep, the next time it starts, it uses a fresh disk from the container image. + +If you need point-in-time filesystem state, create and restore an experimental snapshot. Snapshots are immutable, so later file changes require a new snapshot. For more information, refer to [Snapshots](/containers/platform-details/snapshots/). ## An example request diff --git a/src/content/docs/containers/platform-details/snapshots.mdx b/src/content/docs/containers/platform-details/snapshots.mdx new file mode 100644 index 00000000000000..4c8d61a4159fc9 --- /dev/null +++ b/src/content/docs/containers/platform-details/snapshots.mdx @@ -0,0 +1,181 @@ +--- +title: Use snapshots +pcx_content_type: how-to +sidebar: + order: 55 +description: Persist container filesystem across restarts. +--- + +import { TypeScriptExample, WranglerConfig } from "~/components"; + +Snapshots let you save point-in-time filesystem state from a running [Container](/containers/). The examples on this page use the [Container class](/containers/container-package/). + +:::caution[Experimental] +Snapshot APIs require the `experimental` [compatibility flag](/workers/configuration/compatibility-flags/). These APIs may change in backward-incompatible ways. +::: + +## Turn on the compatibility flag + +Add `experimental` to your Worker's Wrangler configuration before you use snapshot APIs: + + + +```toml +compatibility_date = "$today" +compatibility_flags = ["experimental"] +``` + + + +## Choose a snapshot type + +Use `snapshotDirectory()` to capture one directory. Use `snapshotContainer()` to capture the full container filesystem. + +Both snapshot types are immutable. If you restore a snapshot and then change files, create a new snapshot to persist those changes. + +- You can restore multiple directory snapshots in one `start()` call. +- You can restore only one container snapshot in one `start()` call. +- You can restore directory snapshots on top of a container snapshot. +- Snapshot handles are plain data objects, so you can store them and restore them later, even from another Durable Object. + +## Create a directory snapshot + +Use `snapshotDirectory()` to capture the current contents of a directory in a running container: + + + +```ts +import { Container } from "@cloudflare/containers"; + +export class MyContainer extends Container { + async saveWorkspace() { + return await this.ctx.container.snapshotDirectory({ + dir: "/app/workspace", + name: "workspace-checkpoint", + }); + } +} +``` + + + +The `dir` value must be an absolute path. The API returns a serializable snapshot handle that you can store and restore later. + +## Create a container snapshot + +Use `snapshotContainer()` to capture the full container filesystem: + + + +```ts +import { Container } from "@cloudflare/containers"; + +export class MyContainer extends Container { + async saveContainer() { + return await this.ctx.container.snapshotContainer({ + name: "before-upgrade", + }); + } +} +``` + + + +The API returns a serializable snapshot handle that you can store and restore later. + +## Restore snapshots + +First, create snapshots from a container that is already running. You can store the returned handles in Durable Object storage and use them later: + + + +```ts +import { Container } from "@cloudflare/containers"; + +export class MyContainer extends Container { + async saveSnapshots() { + const directorySnapshot = await this.ctx.container.snapshotDirectory({ + dir: "/app/foo", + }); + + const containerSnapshot = await this.ctx.container.snapshotContainer({}); + + await this.ctx.storage.put("directorySnapshot", directorySnapshot); + await this.ctx.storage.put("containerSnapshot", containerSnapshot); + } +} +``` + + + +Later, if you saved both snapshot types, load the saved snapshot handles and pass them to `this.ctx.container.start()` when you start another container: + + + +```ts +import { Container } from "@cloudflare/containers"; + +export class MyContainer extends Container { + async restoreSnapshots() { + const directorySnapshot = + await this.ctx.storage.get("directorySnapshot"); + const containerSnapshot = + await this.ctx.storage.get("containerSnapshot"); + + this.ctx.container.start({ + containerSnapshot, + directorySnapshots: [{ snapshot: directorySnapshot }], + }); + } +} +``` + + + +If you do not set `mountPoint`, the directory snapshot is restored to the same path that was snapshotted. You can also restore it to a different path: + + + +```ts +import { Container } from "@cloudflare/containers"; + +export class MyContainer extends Container { + async restoreWorkspaceSnapshot() { + const directorySnapshot = + await this.ctx.storage.get("directorySnapshot"); + + if (!directorySnapshot) { + return; + } + + this.ctx.container.start({ + directorySnapshots: [ + { + snapshot: directorySnapshot, + mountPoint: "/app/restored-workspace", + }, + ], + }); + } +} +``` + + + +## Understand restore behavior + +- Directory snapshots cannot be restored to `/`. +- You can snapshot `/`, but you must restore it to a subdirectory by setting `mountPoint`. +- Nested directory snapshots restore in depth order. If you restore snapshots to `/app` and `/app/data`, `/app` is always restored first. +- Directory snapshots can overlay files from a container snapshot. + +## Understand retention + +Snapshots currently have an implicit 90-day time-to-live. Each restore refreshes that time-to-live. + +You cannot set a custom time-to-live yet. + +## Related resources + +- [Durable Object Container](/durable-objects/api/container/) - Full `ctx.container` API reference +- [Lifecycle of a Container](/containers/platform-details/architecture/) - Understand startup, sleep, and shutdown behavior diff --git a/src/content/docs/durable-objects/api/container.mdx b/src/content/docs/durable-objects/api/container.mdx index 614ef1634b3a8e..0c052b119eeb0d 100644 --- a/src/content/docs/durable-objects/api/container.mdx +++ b/src/content/docs/durable-objects/api/container.mdx @@ -13,6 +13,7 @@ import { Type, MetaInfo, TypeScriptExample, + WranglerConfig, } from "~/components"; ## Description @@ -20,6 +21,18 @@ import { When using a [Container-enabled Durable Object](/containers), you can access the Durable Object's associated container via the `container` object which is on the `ctx` property. This allows you to start, stop, and interact with the container. +:::caution[Experimental snapshot APIs] +`snapshotDirectory()`, `snapshotContainer()`, `directorySnapshots`, and `containerSnapshot` require the `experimental` [compatibility flag](/workers/configuration/compatibility-flags/). These APIs may change in backward-incompatible ways. + + + +```toml +compatibility_flags = ["experimental"] +``` + + +::: + :::note It is likely preferable to use the official `Container` class, which provides helper methods and a more idiomatic API for working with containers on top of Durable Objects. @@ -76,11 +89,83 @@ this.ctx.container.start({ - `env`: An object containing environment variables to pass to the container. This is useful for passing configuration values or secrets to the container. - `entrypoint`: An array of strings representing the command to run in the container. - `enableInternet`: A boolean indicating whether to enable internet access for the container. + - `directorySnapshots`: An array of objects describing directory snapshots to restore before the container starts. You can restore more than one directory snapshot in a single call. + - `snapshot`: The `ContainerDirectorySnapshot` to restore. + - `mountPoint` (optional): The absolute path where the snapshot is restored. If omitted, the snapshot is restored to its original `dir` path. + - `containerSnapshot`: A full container snapshot to restore before the container starts. You can restore only one container snapshot in a single call. #### Return values - None. +#### Understand snapshot restore behavior + +- Directory snapshots are restored before the container starts. +- If `mountPoint` is not set, the snapshot is restored to the original `dir` path. +- Directory snapshots cannot be restored to `/`. +- You can snapshot `/`, but that snapshot must be restored to a subdirectory by setting `mountPoint`. +- Nested directory snapshots are restored in depth order. For example, `/app` is restored before `/app/data`. +- Directory snapshots can be restored on top of a container snapshot. + +### `snapshotDirectory` + +`snapshotDirectory` creates a point-in-time snapshot of a directory in a running container. + + + +```ts +const snapshot = await this.ctx.container.snapshotDirectory({ + dir: "/app/workspace", + name: "workspace-checkpoint", +}); +``` + + + +#### Parameters + +- `options`: An object with the following properties: + - `dir` (string): An absolute path for the directory to snapshot. + - `name` (optional string): A human-friendly name for the snapshot. + +#### Return values + +- A promise that resolves to a `ContainerDirectorySnapshot` object with `id`, `size`, `dir`, and optional `name` properties. + +#### Notes + +- Snapshots are immutable. If you change restored files, create a new snapshot to persist those changes. +- You can serialize the returned snapshot handle and restore it later, including from another Durable Object. +- Snapshot handles currently have an implicit 90-day time-to-live that refreshes when you restore them. + +### `snapshotContainer` + +`snapshotContainer` creates a point-in-time snapshot of the full running container filesystem. + + + +```ts +const snapshot = await this.ctx.container.snapshotContainer({ + name: "before-upgrade", +}); +``` + + + +#### Parameters + +- `options`: An object with the following properties: + - `name` (optional string): A human-friendly name for the snapshot. + +#### Return values + +- A promise that resolves to a `ContainerSnapshot` object with `id`, `size`, and optional `name` properties. + +#### Notes + +- Container snapshots are immutable. +- Snapshot handles currently have an implicit 90-day time-to-live that refreshes when you restore them. + ### `destroy` `destroy` stops the container and optionally returns a custom error message to the `monitor()` error callback. @@ -207,6 +292,7 @@ await this.ctx.container.interceptOutboundHttp("123.123.123.123/23", worker); `interceptAllOutboundHttp` routes all outbound HTTP requests from the container through a `WorkerEntrypoint`, regardless of destination. await this.ctx.container.interceptAllOutboundHttp(worker); + ``` #### Parameters @@ -229,3 +315,5 @@ await this.ctx.container.interceptAllOutboundHttp(worker); - [Containers](/containers) - [Get Started With Containers](/containers/get-started) +- [Snapshots](/containers/platform-details/snapshots/) +```