Skip to content

Commit 84f41da

Browse files
committed
[wrangler/miniflare] Add enable_containers_privileged_mode for FUSE in local dev
Containers using FUSE work in production but break in `wrangler dev` because workerd doesn't grant CAP_SYS_ADMIN, mount /dev/fuse, or relax AppArmor on the container it creates. Adds an opt-in dev config flag — `dev.enable_containers_privileged_mode` in wrangler.json, `--enable-containers-privileged-mode` on the CLI — that, when set, has miniflare ask workerd to launch local containers with those three permissions. Off by default; only takes effect when the user explicitly opts in. Pairs with the workerd change in cloudflare/workerd#6596, which adds the matching `allowPrivileged` field to ContainerOptions and gates the FUSE injection on it.
1 parent d8c895a commit 84f41da

12 files changed

Lines changed: 76 additions & 0 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"wrangler": minor
3+
"miniflare": minor
4+
---
5+
6+
Add `dev.enable_containers_privileged_mode` (and `--enable-containers-privileged-mode`) opt-in for FUSE in local dev
7+
8+
Containers using FUSE work in production but break in `wrangler dev` because workerd doesn't grant `CAP_SYS_ADMIN`, mount `/dev/fuse`, or relax AppArmor on the container it creates. This adds a top-level dev config flag — `dev.enable_containers_privileged_mode` in `wrangler.json`, `--enable-containers-privileged-mode` on the CLI — that, when set, has miniflare ask workerd to launch local containers with those three permissions. Off by default; only takes effect when the user opts in.
9+
10+
Pairs with the workerd change in https://github.com/cloudflare/workerd/pull/6596, which adds the matching `allowPrivileged` option to `ContainerOptions` and gates the FUSE injection on it.

packages/miniflare/src/plugins/do/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import type {
1717
// Options for a container attached to the DO
1818
export const DOContainerOptionsSchema = z.object({
1919
imageName: z.string(),
20+
// When true, workerd creates the container with the elevated privileges
21+
// needed for FUSE (CAP_SYS_ADMIN, /dev/fuse, AppArmor unconfined).
22+
allowPrivileged: z.boolean().optional(),
2023
});
2124
export type DOContainerOptions = z.infer<typeof DOContainerOptionsSchema>;
2225

packages/miniflare/src/runtime/config/workerd.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,14 @@ export type Worker_DurableObjectNamespace = {
195195
className?: string;
196196
preventEviction?: boolean;
197197
enableSql?: boolean;
198+
container?: Worker_ContainerOptions;
198199
} & ({ uniqueKey?: string } | { ephemeralLocal?: Void });
199200

201+
export type Worker_ContainerOptions = {
202+
imageName?: string;
203+
allowPrivileged?: boolean;
204+
};
205+
200206
export type ExternalServer = { address?: string } & (
201207
| { http: HttpOptions }
202208
| { https: ExternalServer_Https }

packages/workers-utils/src/config/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,15 @@ export interface DevConfig {
254254
*/
255255
enable_containers: boolean;
256256

257+
/**
258+
* When developing, whether to launch containers with the elevated privileges
259+
* required for FUSE mounts (CAP_SYS_ADMIN, /dev/fuse, AppArmor unconfined).
260+
* Off by default — only enable when your container needs FUSE locally.
261+
*
262+
* @default false
263+
*/
264+
enable_containers_privileged_mode: boolean;
265+
257266
/**
258267
* Either the Docker unix socket i.e. `unix:///var/run/docker.sock` or a full configuration.
259268
* Note that windows is only supported via WSL at the moment
@@ -312,6 +321,7 @@ export const defaultWranglerConfig: Config = {
312321
host: undefined,
313322
// Note this one is also workers only
314323
enable_containers: true,
324+
enable_containers_privileged_mode: false,
315325
container_engine: undefined,
316326
generate_types: false,
317327
},

packages/workers-utils/src/config/validation.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ export type NormalizeAndValidateConfigArgs = {
227227
upstreamProtocol?: string;
228228
script?: string;
229229
enableContainers?: boolean;
230+
enableContainersPrivilegedMode?: boolean;
230231
generateTypes?: boolean;
231232
};
232233

@@ -681,6 +682,7 @@ function normalizeAndValidateDev(
681682
upstreamProtocol: upstreamProtocolArg,
682683
remote: remoteArg,
683684
enableContainers: enableContainersArg,
685+
enableContainersPrivilegedMode: enableContainersPrivilegedModeArg,
684686
generateTypes: generateTypesArg,
685687
} = args;
686688
assert(
@@ -698,6 +700,10 @@ function normalizeAndValidateDev(
698700
enableContainersArg === undefined ||
699701
typeof enableContainersArg === "boolean"
700702
);
703+
assert(
704+
enableContainersPrivilegedModeArg === undefined ||
705+
typeof enableContainersPrivilegedModeArg === "boolean"
706+
);
701707
assert(
702708
generateTypesArg === undefined || typeof generateTypesArg === "boolean"
703709
);
@@ -720,6 +726,7 @@ function normalizeAndValidateDev(
720726
: local_protocol,
721727
host,
722728
enable_containers = enableContainersArg ?? true,
729+
enable_containers_privileged_mode = enableContainersPrivilegedModeArg ?? false,
723730
container_engine,
724731
generate_types = generateTypesArg ?? false,
725732
...rest
@@ -767,6 +774,14 @@ function normalizeAndValidateDev(
767774
"boolean"
768775
);
769776

777+
validateOptionalProperty(
778+
diagnostics,
779+
"dev",
780+
"enable_containers_privileged_mode",
781+
enable_containers_privileged_mode,
782+
"boolean"
783+
);
784+
770785
validateOptionalProperty(
771786
diagnostics,
772787
"dev",
@@ -792,6 +807,7 @@ function normalizeAndValidateDev(
792807
upstream_protocol,
793808
host,
794809
enable_containers,
810+
enable_containers_privileged_mode,
795811
container_engine,
796812
generate_types,
797813
};

packages/wrangler/src/api/integrations/platform/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ async function getMiniflareOptionsFromConfig(args: {
259259
),
260260
containerBuildId: undefined,
261261
enableContainers: config.dev.enable_containers,
262+
enableContainersPrivilegedMode:
263+
config.dev.enable_containers_privileged_mode,
262264
},
263265
remoteProxyConnectionString
264266
);
@@ -432,6 +434,8 @@ export function unstable_getMiniflareWorkerOptions(
432434
containerDOClassNames,
433435
containerBuildId: options?.containerBuildId,
434436
enableContainers,
437+
enableContainersPrivilegedMode:
438+
config.dev.enable_containers_privileged_mode,
435439
},
436440
options?.remoteProxyConnectionString
437441
);
@@ -490,6 +494,8 @@ export function unstable_getMiniflareWorkerOptions(
490494
doClassName: binding.class_name,
491495
containerDOClassNames,
492496
containerBuildId: options?.containerBuildId,
497+
allowPrivileged:
498+
config.dev.enable_containers_privileged_mode,
493499
})
494500
: undefined,
495501
} satisfies DurableObjectDefinition,

packages/wrangler/src/api/startDevWorker/ConfigController.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ async function resolveDevConfig(
158158
multiworkerPrimary: input.dev?.multiworkerPrimary,
159159
enableContainers:
160160
input.dev?.enableContainers ?? config.dev.enable_containers,
161+
enableContainersPrivilegedMode:
162+
input.dev?.enableContainersPrivilegedMode ??
163+
config.dev.enable_containers_privileged_mode,
161164
dockerPath: input.dev?.dockerPath ?? getDockerPath(),
162165
containerEngine: useContainers
163166
? (input.dev?.containerEngine ??

packages/wrangler/src/api/startDevWorker/LocalRuntimeController.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ export async function convertToConfigBundle(
144144
containerBuildId: event.config.dev?.containerBuildId,
145145
containerEngine: event.config.dev.containerEngine,
146146
enableContainers: event.config.dev.enableContainers ?? true,
147+
enableContainersPrivilegedMode:
148+
event.config.dev.enableContainersPrivilegedMode ?? false,
147149
// Zone for CF-Worker header - extracted from routes/host configuration
148150
zone: event.config.dev?.origin?.hostname,
149151
sendMetrics: event.config.sendMetrics,

packages/wrangler/src/api/startDevWorker/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@ export interface StartDevWorkerInput {
178178
/** Whether to build and connect to containers during local dev. Requires Docker daemon to be running. Defaults to true. */
179179
enableContainers?: boolean;
180180

181+
/**
182+
* Whether to launch local containers with the elevated privileges needed for FUSE
183+
* (CAP_SYS_ADMIN, /dev/fuse, AppArmor unconfined). Defaults to false.
184+
*/
185+
enableContainersPrivilegedMode?: boolean;
186+
181187
/** Path to the dev registry directory */
182188
registry?: string;
183189

packages/wrangler/src/dev.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ export const dev = createCommand({
142142
describe: "Whether to build and enable containers during development",
143143
hidden: true,
144144
},
145+
"enable-containers-privileged-mode": {
146+
type: "boolean",
147+
describe:
148+
"Launch local containers with the elevated privileges required for FUSE (CAP_SYS_ADMIN, /dev/fuse, AppArmor unconfined)",
149+
hidden: true,
150+
},
145151
site: {
146152
describe: "Root folder of static assets for Workers Sites",
147153
type: "string",

0 commit comments

Comments
 (0)