Resources and isolation

Last updated Sep 22, 2026

Binding reference

Two Previews bound to the same account-level resource ID or name share its data or instances. Bind a Preview to a different resource to isolate it.

Resource bindings

This table shows which resources are automatically provisioned and which require separate resource bindings:

Resource or previews field How it behaves To isolate one Preview
Durable Objects Automatically provisions a new Durable Object namespace and storage for each Preview. Automatic.
Containers Automatically provisions a new container app and container instances for each Preview. Automatic.
kv_namespaces Binds to a KV namespace by id. Two Previews sharing the same id share namespace data. Bind to a different KV namespace.
d1_databases Binds to a D1 database by database_id. Two Previews sharing the same database_id share rows. Bind to a different D1 database.
r2_buckets Binds to an R2 bucket by bucket_name. Two Previews sharing the same bucket_name share objects. Bind to a different R2 bucket.
queues.producers Binds a Queue producer to a queue by name. Two Previews sharing the same queue name send to the same queue. Bind to a different queue.
vectorize Binds to a Vectorize index by index_name. Must be created manually before deploying. Bind to a different index.
hyperdrive Binds to a Hyperdrive configuration by id. Must be created manually. True data isolation requires a separate config pointing at a separate database or schema. Bind to a different Hyperdrive config.
analytics_engine_datasets Writes to the dataset name you specify. Datasets are created implicitly by writes. Two Previews writing to the same dataset share rows. Bind to a different dataset name.
pipelines Binds to a Pipeline stream by pipeline ID. Two Previews sharing the same stream send events to the same destination. Bind to a different stream.
workflows Binds to an existing Workflow. Calls use that Workflow's deployed code, bindings, and instances. Bind to a dedicated non-production Workflow.
secrets_store_secrets Binds to a Secrets Store secret by store_id and secret_name. Bind to a different store or secret name.
dispatch_namespaces Binds to a Workers for Platforms dispatch namespace by namespace. Bind to a different namespace.
mtls_certificates Binds to an mTLS client certificate by certificate_id. Bind to a different certificate.
vpc_services Binds to a VPC service by service_id. Bind to a different service.
ratelimits Creates a Rate Limiting binding. The runtime binding name comes from name, not binding. Use a different namespace_id.
send_email Creates a Send Email binding. The runtime binding name comes from name, not binding. Successful delivery requires valid Email Routing setup. Not applicable.

Durable Objects

Durable Objects use a stateful singleton model: within a namespace, each object ID resolves to one instance and its storage. If Previews shared a namespace, they could read and overwrite the same state. For a class defined in the same Worker without script_name, each Preview automatically gets its own namespace and storage.

State persists across deployments within the same Preview and is deleted when the Preview is deleted.

Access pattern Preview configuration Result
ctx.exports Class and migration only Recommended. Each Preview gets a separate namespace automatically.
env binding Class, migration, and a Preview binding Use when your code needs env.COUNTER.

Every Durable Object setup requires a Durable Object class exported from your Worker and a migration in your Wrangler config.

With ctx.exports (recommended, requires enable_ctx_exports compatibility flag, enabled by default for compatibility_date 2025-11-17 or later), the migration is enough for automatic Preview isolation:

{
    // Set this to today's date
    "compatibility_date": "2026-10-27",
    "migrations": [
        { "tag": "v1", "new_classes": ["Counter"] }
    ],
    "previews": {}
}
# Set this to today's date
compatibility_date = "2026-10-27"
previews = { }

[[migrations]]
tag = "v1"
new_classes = [ "Counter" ]

Production, feature-login, and redesign each get their own Durable Object namespace and storage. The empty previews block is sufficient because no Preview-specific Durable Object binding is needed.

Use an env binding

If your code uses env.COUNTER, declare the binding in the previews block so the Preview has it:

{
    "durable_objects": {
        "bindings": [
            { "name": "COUNTER", "class_name": "Counter" }
        ]
    },
    "migrations": [
        { "tag": "v1", "new_classes": ["Counter"] }
    ],
    "previews": {
        "durable_objects": {
            "bindings": [
                { "name": "COUNTER", "class_name": "Counter" }
            ]
        }
    }
}
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"

[[migrations]]
tag = "v1"
new_classes = [ "Counter" ]

[[previews.durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"

Caution

If you use env.COUNTER without declaring the binding in your previews block, the binding will not exist in the Preview and your Worker can return a 1101 error.

Containers

Containers are backed by Durable Objects. Each Preview gets its own Durable Object namespace, container app, and container instances. When you run npx wrangler preview, Wrangler builds and deploys the image for that Preview. The generated app name includes the Worker, Preview, and class names, such as my-worker_feature-login_MyContainer.

Preview container configuration is not inherited from the top level. If both production and Previews need the container, declare the container in both places:

  • Top-level containers for production.
  • previews.containers for Previews.
Access pattern Preview configuration Result
ctx.exports Class, SQLite migration, and previews.containers Recommended when your code does not need an env binding.
env binding Class, SQLite migration, previews.containers, and Preview Durable Object binding Use when your code calls env.MY_CONTAINER.

Use ctx.exports with Containers

Use this pattern when your Worker accesses the container class through ctx.exports instead of an env binding:

const container = ctx.exports.MyContainer.getByName("tenant-a");
return container.fetch(request);

Declare the SQLite-backed Durable Object migration and the container configuration. Put the container configuration under previews.containers so Wrangler creates a container app for each Preview:

{
    // Set this to today's date
    "compatibility_date": "2026-10-27",
    "migrations": [
        { "tag": "v1", "new_sqlite_classes": ["MyContainer"] }
    ],
    "containers": [
        {
            "class_name": "MyContainer",
            "image": "./Dockerfile",
            "max_instances": 10,
            "instance_type": "basic"
        }
    ],
    "previews": {
        "containers": [
            {
                "class_name": "MyContainer",
                "image": "./Dockerfile",
                "max_instances": 10,
                "instance_type": "basic"
            }
        ]
    }
}
# Set this to today's date
compatibility_date = "2026-10-27"

[[migrations]]
tag = "v1"
new_sqlite_classes = [ "MyContainer" ]

[[containers]]
class_name = "MyContainer"
image = "./Dockerfile"
max_instances = 10
instance_type = "basic"

[[previews.containers]]
class_name = "MyContainer"
image = "./Dockerfile"
max_instances = 10
instance_type = "basic"

With this setup, production and each Preview use separate Durable Object namespaces and separate container apps. No previews.durable_objects binding is needed unless your Worker code reads the container namespace from env.

Use an env container binding

If your code uses an env binding, declare the Durable Object binding at the top level for production and again under previews.durable_objects.bindings for Previews:

import { getContainer } from "@cloudflare/containers";

const container = getContainer(env.MY_CONTAINER, "tenant-a");
return container.fetch(request);
{
    // Set this to today's date
    "compatibility_date": "2026-10-27",
    "migrations": [
        { "tag": "v1", "new_sqlite_classes": ["MyContainer"] }
    ],
    "containers": [
        {
            "class_name": "MyContainer",
            "image": "./Dockerfile",
            "max_instances": 10,
            "instance_type": "basic"
        }
    ],
    "durable_objects": {
        "bindings": [
            { "name": "MY_CONTAINER", "class_name": "MyContainer" }
        ]
    },
    "previews": {
        "containers": [
            {
                "class_name": "MyContainer",
                "image": "./Dockerfile",
                "max_instances": 10,
                "instance_type": "basic"
            }
        ],
        "durable_objects": {
            "bindings": [
                { "name": "MY_CONTAINER", "class_name": "MyContainer" }
            ]
        }
    }
}
# Set this to today's date
compatibility_date = "2026-10-27"

[[migrations]]
tag = "v1"
new_sqlite_classes = [ "MyContainer" ]

[[containers]]
class_name = "MyContainer"
image = "./Dockerfile"
max_instances = 10
instance_type = "basic"

[[durable_objects.bindings]]
name = "MY_CONTAINER"
class_name = "MyContainer"

[[previews.containers]]
class_name = "MyContainer"
image = "./Dockerfile"
max_instances = 10
instance_type = "basic"

[[previews.durable_objects.bindings]]
name = "MY_CONTAINER"
class_name = "MyContainer"

Caution