Skip to content

Commit 5b7d5cc

Browse files
committed
Enable FUSE in local-dev Docker containers
In ContainerClient::createContainer(), grant the minimum capabilities needed so a FUSE mount inside the user's container succeeds: - CapAdd: ["SYS_ADMIN"] (for the mount() syscall) - Devices: [/dev/fuse] (so the kernel has something to open) - SecurityOpt: ["apparmor:unconfined"] (bypasses the default docker apparmor profile's mount block) No Privileged=true. Mirrors what Cloudflare's production container runtime effectively provides for FUSE workers, without granting all Linux capabilities or full /dev passthrough. Only affects workerd's local Docker code path; production is unaffected. Also adds missing $Json.name annotations to docker_api::DeviceMapping fields in docker-api.capnp so they serialize as PathOnHost / PathInContainer / CgroupPermissions to match the Docker API. This is load-bearing for the Devices bind above — without it, the entry serializes as camelCase and Docker silently ignores it. Before this PR no call site populated Devices, so the missing annotations were latent. Follows the existing pattern of createSidecarContainer() which already sets CapAdd=[NET_ADMIN] for its own need at container-client.c++:1958. End-to-end reproduction (libc mount("fuse", ...) syscall succeeding inside the container, with a wire-level log confirming PathOnHost etc. reach Docker correctly) at https://github.com/Ben2W/workerd-fuse-local-repro Signed-off-by: Ben Werner <bewerner23@gmail.com>
1 parent 22c1ca0 commit 5b7d5cc

2 files changed

Lines changed: 20 additions & 3 deletions

File tree

src/workerd/server/container-client.c++

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,6 +1698,23 @@ kj::Promise<void> ContainerClient::createContainer(kj::StringPtr effectiveImage,
16981698
}
16991699
}
17001700

1701+
// Docker doesn't grant FUSE access by default — enable the minimum permissions for it.
1702+
{
1703+
auto capAdd = hostConfig.initCapAdd(1);
1704+
capAdd.set(0, "SYS_ADMIN");
1705+
}
1706+
{
1707+
auto devices = hostConfig.initDevices(1);
1708+
auto fuseDev = devices[0];
1709+
fuseDev.setPathOnHost("/dev/fuse");
1710+
fuseDev.setPathInContainer("/dev/fuse");
1711+
fuseDev.setCgroupPermissions("rwm");
1712+
}
1713+
{
1714+
auto securityOpt = hostConfig.initSecurityOpt(1);
1715+
securityOpt.set(0, "apparmor:unconfined");
1716+
}
1717+
17011718
auto response = co_await dockerApiRequest(network, kj::str(dockerPath), kj::HttpMethod::POST,
17021719
kj::str("/containers/create?name=", containerName), codec.encode(jsonRoot));
17031720

src/workerd/server/docker-api.capnp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ struct Docker {
3232
}
3333

3434
struct DeviceMapping {
35-
pathOnHost @0 :Text;
36-
pathInContainer @1 :Text;
37-
cgroupPermissions @2 :Text;
35+
pathOnHost @0 :Text $Json.name("PathOnHost");
36+
pathInContainer @1 :Text $Json.name("PathInContainer");
37+
cgroupPermissions @2 :Text $Json.name("CgroupPermissions");
3838
}
3939

4040
struct DeviceRequest {

0 commit comments

Comments
 (0)