Skip to content

Commit 154acf7

Browse files
add support for containers in wrangler multiworker dev (#10032)
--------- Co-authored-by: emily-shen <[email protected]>
1 parent 715ca2a commit 154acf7

File tree

16 files changed

+422
-49
lines changed

16 files changed

+422
-49
lines changed

.changeset/late-taxis-switch.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
add support for containers in wrangler multiworker dev
6+
7+
currently when running `wrangler dev` with different workers (meaning that the `-c|--config` flag is used multiple times) containers are not being included, meaning that trying to interact with them at runtime would not work and cause errors instead. The changes here address the above making wrangler correctly detect and wire up the containers.

fixtures/interactive-dev-tests/multi-containers-app/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class FixtureTestContainerBase extends DurableObject<Env> {
1616
});
1717
// On the first request we simply start the container and return,
1818
// on the following requests the container can actually be accessed.
19-
// Note that we do this this way becase container.start is not awaitable
19+
// Note that we do this this way because container.start is not awaitable
2020
// meaning that we can't simply wait here for the container to be ready
2121
return new Response("Container started");
2222
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "ESNext",
5+
"lib": ["ES2020"],
6+
"types": ["@cloudflare/workers-types"],
7+
"moduleResolution": "node",
8+
"noEmit": true,
9+
"skipLibCheck": true
10+
},
11+
"include": ["**/*.ts"],
12+
"exclude": ["tests"]
13+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM node:22-alpine
2+
3+
WORKDIR /usr/src/app
4+
RUN echo '{"name": "simple-node-app", "version": "1.0.0"}' > package.json
5+
RUN npm install
6+
7+
RUN echo 'const { createServer } = require("http");\
8+
\
9+
const server = createServer(function (req, res) {\
10+
res.writeHead(200, { "Content-Type": "text/plain" });\
11+
res.write("Hello from Container A");\
12+
res.end();\
13+
});\
14+
\
15+
server.listen(8080);\
16+
' > app.js
17+
18+
EXPOSE 8080
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { DurableObject } from "cloudflare:workers";
2+
3+
export class FixtureTestContainerA extends DurableObject<Env> {
4+
container: globalThis.Container;
5+
6+
constructor(ctx: DurableObjectState, env: Env) {
7+
super(ctx, env);
8+
this.container = ctx.container;
9+
}
10+
11+
async fetch(req: Request) {
12+
if (!this.container.running) {
13+
this.container.start({
14+
entrypoint: ["node", "app.js"],
15+
enableInternet: false,
16+
});
17+
// On the first request we simply start the container and return,
18+
// on the following requests the container can actually be accessed.
19+
// Note that we do this this way because container.start is not awaitable
20+
// meaning that we can't simply wait here for the container to be ready
21+
return new Response("Container started");
22+
}
23+
return this.container
24+
.getTcpPort(8080)
25+
.fetch("http://foo/bar/baz", { method: "POST", body: "hello" });
26+
}
27+
}
28+
29+
export default {
30+
async fetch(request, env): Promise<Response> {
31+
const id = env.CONTAINER_A.idFromName("container");
32+
const stub = env.CONTAINER_A.get(id);
33+
return Response.json({
34+
containerAText: await (await stub.fetch(request)).text(),
35+
containerBText: await (await env.WORKER_B.fetch(request)).text(),
36+
});
37+
},
38+
} satisfies ExportedHandler<Env>;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
declare namespace Cloudflare {
2+
interface Env {
3+
CONTAINER_A: DurableObjectNamespace<import(".").FixtureTestContainerA>;
4+
WORKER_B: Fetcher;
5+
}
6+
}
7+
interface Env extends Cloudflare.Env {}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "worker-a",
3+
"main": "index.ts",
4+
"compatibility_date": "2025-04-03",
5+
"services": [{ "binding": "WORKER_B", "service": "worker-b" }],
6+
"containers": [
7+
{
8+
"image": "./Dockerfile",
9+
"class_name": "FixtureTestContainerA",
10+
"name": "container",
11+
"max_instances": 2,
12+
},
13+
],
14+
"durable_objects": {
15+
"bindings": [
16+
{
17+
"class_name": "FixtureTestContainerA",
18+
"name": "CONTAINER_A",
19+
},
20+
],
21+
},
22+
"migrations": [
23+
{
24+
"tag": "v1",
25+
"new_sqlite_classes": ["FixtureTestContainerA"],
26+
},
27+
],
28+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM node:22-alpine
2+
3+
WORKDIR /usr/src/app
4+
RUN echo '{"name": "simple-node-app", "version": "1.0.0"}' > package.json
5+
RUN npm install
6+
7+
RUN echo 'const { createServer } = require("http");\
8+
\
9+
const server = createServer(function (req, res) {\
10+
res.writeHead(200, { "Content-Type": "text/plain" });\
11+
res.write("Hello from Container B");\
12+
res.end();\
13+
});\
14+
\
15+
server.listen(8080);\
16+
' > app.js
17+
18+
EXPOSE 8080
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { DurableObject } from "cloudflare:workers";
2+
3+
export class FixtureTestContainerB extends DurableObject<Env> {
4+
container: globalThis.Container;
5+
6+
constructor(ctx: DurableObjectState, env: Env) {
7+
super(ctx, env);
8+
this.container = ctx.container;
9+
}
10+
11+
async fetch(req: Request) {
12+
if (!this.container.running) {
13+
this.container.start({
14+
entrypoint: ["node", "app.js"],
15+
enableInternet: false,
16+
});
17+
// On the first request we simply start the container and return,
18+
// on the following requests the container can actually be accessed.
19+
// Note that we do this this way because container.start is not awaitable
20+
// meaning that we can't simply wait here for the container to be ready
21+
return new Response("Container started");
22+
}
23+
return this.container
24+
.getTcpPort(8080)
25+
.fetch("http://foo/bar/baz", { method: "POST", body: "hello" });
26+
}
27+
}
28+
29+
export default {
30+
async fetch(request, env): Promise<Response> {
31+
const id = env.CONTAINER_B.idFromName("container");
32+
const stub = env.CONTAINER_B.get(id);
33+
return stub.fetch(request);
34+
},
35+
} satisfies ExportedHandler<Env>;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
declare namespace Cloudflare {
2+
interface Env {
3+
CONTAINER_B: DurableObjectNamespace<import(".").FixtureTestContainerB>;
4+
}
5+
}
6+
interface Env extends Cloudflare.Env {}

0 commit comments

Comments
 (0)