-
Notifications
You must be signed in to change notification settings - Fork 26
feat(gastown): Support wrangler dev with Containers inside gastown — Docker-in-Docker for self-debugging #1984
Description
Summary
Enable gastown agents to run wrangler dev with Cloudflare Containers support inside the gastown container itself. This allows gastown to debug, develop, and test its own Worker + Container stack — a polecat can spin up a local wrangler dev environment, make changes to the gastown Worker/Container code, and validate them before pushing.
Use Case
A gastown convoy working on gastown itself needs to:
- Clone the cloud repo (already works)
- Run
wrangler devfor cloudflare-gastown (needs Docker for Containers) - The dev Worker spins up a local container via Docker
- Run integration tests or manual validation against the local dev environment
- Push the changes
Step 2 currently fails because the gastown container has no Docker daemon. wrangler dev detects the containers config in wrangler.jsonc and tries to use Docker to build/run the dev container image.
Architecture
Cloudflare Containers support rootless Docker-in-Docker (docs). Gastown uses raw Containers (extends Container<Env>), NOT the Sandbox SDK, so we have more control than the DinD guide assumes — no /sandbox entrypoint requirement, no @cloudflare/sandbox SDK constraints.
Cloudflare Container VM (gastown production container)
├── bun run src/main.ts (gastown control server — existing)
├── dockerd --rootless (background process)
│ └── Docker socket at $XDG_RUNTIME_DIR/docker.sock
└── Agent runs `wrangler dev --env dev`
└── wrangler builds + runs inner container via Docker
└── Inner container (gastown dev container from Dockerfile.dev)
What we need in the gastown container image
- Rootless Docker — the
dockerd-rootlessbinary + dependencies - Docker CLI —
dockercommand for wrangler to call - A boot script that starts
dockerdin the background on container startup (before the control server) DOCKER_HOSTenv var pointing to the rootless Docker socket- Wrangler — already available via
npx wrangler(Node.js is installed)
What we do NOT need
- The Sandbox SDK (
@cloudflare/sandbox) — we manage Docker ourselves - The
/sandboxentrypoint binary — we use our ownbun run src/main.ts - Privileged mode — rootless Docker works without it
Dockerfile Changes
Two approaches:
Option A: Install rootless Docker into the existing Debian image (recommended)
FROM oven/bun:1-slim
# ... existing apt-get installs (git, node, gh, ripgrep, build-essential, etc.)
# Install rootless Docker
RUN apt-get update && \
apt-get install -y --no-install-recommends \
uidmap \ # for rootless user namespaces
dbus-user-session \
fuse-overlayfs \ # for rootless overlay storage
slirp4netns \ # for rootless networking
&& curl -fsSL https://get.docker.com/rootless | sh \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Set Docker environment for the agent user
ENV DOCKER_HOST=unix:///run/user/1000/docker.sock
ENV XDG_RUNTIME_DIR=/run/user/1000Pro: Extends the existing image with minimal changes. All existing tooling (bun, node, git, kilo) remains as-is.
Con: Larger image. Rootless Docker on Debian requires more setup than Alpine-based docker:dind-rootless.
Option B: Multi-stage — copy Docker from dind-rootless
FROM docker:dind-rootless AS docker-src
FROM oven/bun:1-slim
# Copy Docker binaries from the dind-rootless image
COPY --from=docker-src /usr/local/bin/docker /usr/local/bin/docker
COPY --from=docker-src /usr/local/bin/dockerd /usr/local/bin/dockerd
COPY --from=docker-src /usr/local/bin/dockerd-rootless.sh /usr/local/bin/dockerd-rootless.sh
COPY --from=docker-src /usr/local/bin/rootlesskit /usr/local/bin/rootlesskit
COPY --from=docker-src /usr/local/bin/containerd* /usr/local/bin/
COPY --from=docker-src /usr/local/bin/runc /usr/local/bin/runc
# Install runtime deps for rootless Docker
RUN apt-get update && \
apt-get install -y --no-install-recommends uidmap fuse-overlayfs slirp4netns && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# ... rest of existing DockerfilePro: Copies pre-built binaries, avoids the get.docker.com install script.
Con: More fragile (binary compatibility between Alpine-built and Debian).
Recommendation: Option A — simpler, more maintainable.
Boot Sequence
The container entrypoint needs to start dockerd before the control server:
#!/bin/bash
set -eu
# Start rootless dockerd in the background
export XDG_RUNTIME_DIR=/run/user/$(id -u)
mkdir -p $XDG_RUNTIME_DIR
dockerd-rootless.sh --iptables=false --ip6tables=false &
# Wait for Docker readiness
until docker version >/dev/null 2>&1; do sleep 0.2; done
echo "Docker is ready"
# Start the gastown control server (existing entrypoint)
exec bun run src/main.tsReplace the Dockerfile CMD:
COPY boot.sh /app/boot.sh
CMD ["/app/boot.sh"]Cloudflare Container Constraints
| Constraint | Impact | Mitigation |
|---|---|---|
| No iptables | Inner containers can't have isolated networks | Use --network=host on all docker run / docker build commands |
| Rootless only | Can't use privileged features | Rootless Docker is sufficient for wrangler dev |
| Ephemeral storage | Docker images lost on container sleep/eviction | Agents should docker pull / docker build as needed. Consider using a registry cache. |
| linux/amd64 only (production) | Dev Dockerfile.dev uses arm64 for Apple Silicon | The inner container must build for amd64. Update Dockerfile.dev to support amd64 or add --platform linux/amd64 |
Wrangler Configuration
When an agent runs wrangler dev inside the container, it needs:
# Set Docker socket for wrangler to discover
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
# Or configure in wrangler.jsonc:
# "dev": { "container_engine": "$XDG_RUNTIME_DIR/docker.sock" }
# Run wrangler dev
npx wrangler dev --env dev --ip 0.0.0.0The --network=host constraint means the inner container's exposed ports are available on the outer container's network. The agent can access the dev Worker at http://localhost:8787 and the dev container at http://localhost:8080.
Conditional Docker (Don't penalize normal agents)
Docker adds ~200-300MB to the image and the dockerd boot process adds 2-3 seconds to cold start. Most towns don't need Docker — only towns working on gastown itself or other containerized projects.
Options:
- Always install Docker — simplest. Accept the image size increase. Gate
dockerdstartup on a config flag so it doesn't run unless needed. - Separate "docker-enabled" image — a second Dockerfile (
Dockerfile.docker) that extends the base with Docker. Towns that need DinD are configured to use this image. - Install on demand — the agent runs a setup script that installs Docker at runtime. Slow (~30s) but zero image size impact for normal agents.
Recommendation: Option 1 with a gated boot. Install Docker in the image (it's just binaries). Only start dockerd if a flag is set (e.g., ENABLE_DOCKER=1 env var from town config). Normal agents pay the image size cost but not the boot time cost.
# In boot.sh:
if [ "$ENABLE_DOCKER" = "1" ]; then
dockerd-rootless.sh --iptables=false --ip6tables=false &
until docker version >/dev/null 2>&1; do sleep 0.2; done
echo "Docker is ready"
fi
exec bun run src/main.tsImage Size Impact
| Component | Size |
|---|---|
| Current image (base + tools) | ~350MB |
| Rootless Docker binaries | ~200MB |
| Runtime deps (uidmap, fuse-overlayfs, slirp4netns) | ~10MB |
| Total with Docker | ~560MB |
Combined with #1976 (dev tools expansion, +250MB), the full image would be ~810MB. This is within reason for a dev container — GitHub Codespaces images are 5-10GB.
Acceptance Criteria
- Rootless Docker binaries installed in the container image
-
dockerdstarts conditionally viaENABLE_DOCKERflag -
DOCKER_HOSTenv var set to rootless socket path - Agent can run
docker buildanddocker runinside the container - Agent can run
wrangler devwith container support (builds + runs inner container) - Inner containers use
--network=host(enforced by agent prompt or wrapper script) - Normal agents (ENABLE_DOCKER=0) have zero boot time impact from Docker
- Dockerfile.dev updated to support amd64 architecture for inner container builds
References
- Cloudflare DinD guide — rootless Docker approach (Sandbox SDK specific, but the Docker parts apply to raw Containers)
- Cloudflare Containers local dev — how wrangler dev uses Docker
- chore(gastown): Expand container Dockerfile with build tools, ripgrep, and common dev dependencies #1976 — Dockerfile expansion with dev tools (complementary)
cloudflare-gastown/wrangler.jsonc:122-130— dev container configcloudflare-gastown/container/Dockerfile.dev— current dev container image