Skip to content

[wrangler] Allow containers to be attached to Durable Objects via exports - #15026

Open
petebacondarwin wants to merge 2 commits into
mainfrom
devx-2628-containers-via-exports
Open

[wrangler] Allow containers to be attached to Durable Objects via exports#15026
petebacondarwin wants to merge 2 commits into
mainfrom
devx-2628-containers-via-exports

Conversation

@petebacondarwin

@petebacondarwin petebacondarwin commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes DEVX-2628.

Containers can now be attached to a Durable Object from the export side, using a new container field on a durable-object export that names an entry in the containers array:

{
	"name": "my-worker",
	"main": "worker.js",
	"compatibility_date": "2026-07-01",
	"containers": [
		{ "name": "my-container", "image": "./Dockerfile", "max_instances": 1 }
	],
	"exports": {
		"MyContainerDO": {
			"type": "durable-object",
			"storage": "sqlite",
			"container": "my-container"
		}
	}
}

As a result containers[].class_name is now optional — a container referenced this way only needs a name. This decouples container configuration from the Durable Object class, which is a prerequisite for configuring containers as standalone resources.

The existing containers[].class_name direction keeps working, and either direction may be used, but a Durable Object and its container must reference each other consistently when both are set.

How the link is resolved

Wrangler sends both directions in the upload metadata exactly as configured (containers[].name and exports[Class].container) and the API does its own resolution. Wrangler only pre-resolves the class name locally where it genuinely needs it — local dev image tagging and Cloudchamber app creation — via a single shared helper (resolveContainerClassName in @cloudflare/workers-utils) used by wrangler, the Vite plugin, and unstable_getMiniflareWorkerOptions.

New validation

container is only valid on live durable-object exports (created and expecting-transfer) and requires storage: "sqlite". Cross-field validation now reports:

  1. a container reference naming a container that isn't defined
  2. two Durable Object exports claiming the same container
  3. the two directions disagreeing with each other
  4. a container that ends up linked to no Durable Object
  5. class_name pointing at a class with no live durable-object export (only when the exports flow is in use — the legacy migrations flow keeps its silent-ignore behaviour)
  6. two containers sharing a name (behaviour change: previously the duplicate was silently tolerated)

Also included

  • @cloudflare/config: container added to exports.durableObject() for the experimental cloudflare.config.ts format.
  • fixtures/container-app now uses the new shape (wrangler.registry.jsonc retains the class_name + migrations equivalent for comparison).
  • Fixed a latent bug in validateContainerApp where the name type check tested the whole containers array rather than each entry, so it never fired.
  • Hardened partitionExports against malformed/unknown export types, which previously threw rather than letting validation report them.

  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: this is the wrangler half of a feature that is not yet announced; the containers/Durable Objects config docs will be updated as one piece when the feature ships. Config reference docs are generated from the JSON schema, which is updated here.

Test coverage added:

  • packages/workers-utils/tests/config/containers.test.ts — unit tests for the shared resolver.
  • packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts — all six validation rules, plus container shape and legacy-kv rejection.
  • packages/wrangler/src/__tests__/containers/{config,deploy,schema}.test.ts and create-worker-upload-form/metadata.test.ts — resolution, deploy, and upload-metadata coverage.
  • packages/wrangler/src/__tests__/dev.test.ts and packages/vite-plugin-cloudflare/src/__tests__/containers.spec.ts — local dev paths.
  • packages/config/src/__tests__/{convert,schema}.test.ts.
  • packages/wrangler/e2e/durable-objects-exports.test.ts — a live wrangler deploy e2e that asserts ctx.container is present on the deployed Durable Object (so it covers the API's half of the resolution end to end), plus a case that moves the link to class_name and re-deploys to confirm the two directions are interchangeable. Skipped when Docker is unavailable.

A picture of a cute animal (not mandatory, but encouraged)

a shipping container, but make it a cat

Note

This is a contribution from an AI agent: OpenCode, claude-opus-5.


Open in Devin Review

…ports`

A container can now be linked to its Durable Object from the export side,
using a new `container` field that names an entry in the `containers`
array:

    "containers": [{ "name": "my-container", "image": "./Dockerfile" }],
    "exports": {
      "MyContainerDO": {
        "type": "durable-object",
        "storage": "sqlite",
        "container": "my-container"
      }
    }

As a result `containers[].class_name` is now optional — a container that
is referenced this way only needs a `name`. This decouples container
configuration from the Durable Object class, which is a prerequisite for
configuring containers as standalone resources.

The link is resolved by a shared helper in `@cloudflare/workers-utils`,
used by wrangler's container normalization, the Vite plugin's dev/preview
container builds, and `unstable_getMiniflareWorkerOptions`. Upload
metadata sends both directions as configured (`containers[].name` and
`exports[].container`) so that the API can do its own resolution;
wrangler only pre-resolves for local dev and Cloudchamber app creation.

Cross-field validation now reports dangling `container` references, two
exports claiming the same container, the two directions disagreeing, a
container linked to no Durable Object, `class_name` with no live export,
and duplicate container names.
…exports`

Deploys a Worker whose container is linked only from the Durable Object's
`exports` entry, then asserts `ctx.container` is present on the deployed
Durable Object. That is only true when the API resolved the link from the
export side, so it covers the server half of the feature end to end.

A second case moves the same link back to `containers[].class_name` and
re-deploys, verifying the container stays attached and that the two
directions really are interchangeable.

The image is pushed once up front under a known tag so that teardown can
delete it deterministically, and so neither deploy has to build it. The
block is skipped when Docker is unavailable.
@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 69c5d21

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@cloudflare/config Minor
wrangler Minor
@cloudflare/vite-plugin Minor
@cloudflare/build-output-utils Patch
@cloudflare/vitest-pool-workers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk Aug 4, 2026
@workers-devprod
workers-devprod requested review from a team and emily-shen and removed request for a team August 4, 2026 21:24
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/cloudchamber
  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/config-export-container-field.md: [@cloudflare/wrangler]
  • .changeset/containers-attached-via-exports.md: [@cloudflare/wrangler]
  • fixtures/container-app/wrangler.jsonc: [@cloudflare/wrangler]
  • packages/config/src/tests/convert.test.ts: [@cloudflare/wrangler]
  • packages/config/src/tests/schema.test.ts: [@cloudflare/wrangler]
  • packages/config/src/convert.ts: [@cloudflare/wrangler]
  • packages/config/src/exports.ts: [@cloudflare/wrangler]
  • packages/config/src/schema.ts: [@cloudflare/wrangler]
  • packages/deploy-helpers/src/deploy/helpers/create-worker-upload-form.ts: [@cloudflare/wrangler]
  • packages/vite-plugin-cloudflare/src/tests/containers.spec.ts: [@cloudflare/wrangler]
  • packages/vite-plugin-cloudflare/src/containers.ts: [@cloudflare/wrangler]
  • packages/vite-plugin-cloudflare/src/miniflare-options.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/config/containers.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/config/environment.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/config/exports.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/config/index.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/config/validation.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/index.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/types.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/worker.ts: [@cloudflare/wrangler]
  • packages/workers-utils/tests/config/containers.test.ts: [@cloudflare/wrangler]
  • packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/e2e/durable-objects-exports.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/config.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/deploy.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/schema.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/create-worker-upload-form/metadata.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/dev.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/helpers/mock-upload-worker.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/integrations/platform/index.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/containers/config.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

⚠️ Issues found

  1. .changeset/config-export-container-field.md
    The changeset correctly identifies containers as an experimental feature, but it does not explain how users can opt in. Per the changeset guidelines, experimental changesets must include a note on how users can enable or opt into the feature.

  2. .changeset/containers-attached-via-exports.md
    This changeset introduces new containers functionality but does not explicitly call it out as experimental or provide opt-in instructions. Since the companion changeset identifies containers as experimental, this one should also include that context and tell users how to opt in.

@ask-bonk

ask-bonk Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Here rawConfig is the top-level raw config. rawConfig?.containers !== undefined — the parameter is named topLevelDeclaresContainers. For the top-level environment call, rawConfig is the top-level config, so this correctly reflects whether containers were declared at top level. For a named env call, rawConfig is still the top-level config (that's the guard semantics they want). This matches the documented behavior.

I've completed a thorough review. The changes are well-structured, the validation is comprehensive, edge cases (tombstones, inheritance, duplicate names, both-direction consistency) are handled, and test coverage is extensive. I found no logic bugs, security issues, backward-compat violations, or incorrect API behavior.

LGTM

github run

@pkg-pr-new

pkg-pr-new Bot commented Aug 4, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@15026

@cloudflare/build-output-utils

npm i https://pkg.pr.new/@cloudflare/build-output-utils@15026

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@15026

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@15026

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@15026

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@15026

miniflare

npm i https://pkg.pr.new/miniflare@15026

@cloudflare/pages-functions

npm i https://pkg.pr.new/@cloudflare/pages-functions@15026

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@15026

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@15026

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@15026

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@15026

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@15026

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@15026

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@15026

wrangler

npm i https://pkg.pr.new/wrangler@15026

commit: 69c5d21

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

2 participants