Skip to content

feat(gastown): Support wrangler dev with Containers inside gastown — Docker-in-Docker for self-debugging #1984

@jrf0110

Description

@jrf0110

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:

  1. Clone the cloud repo (already works)
  2. Run wrangler dev for cloudflare-gastown (needs Docker for Containers)
  3. The dev Worker spins up a local container via Docker
  4. Run integration tests or manual validation against the local dev environment
  5. 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

  1. Rootless Docker — the dockerd-rootless binary + dependencies
  2. Docker CLIdocker command for wrangler to call
  3. A boot script that starts dockerd in the background on container startup (before the control server)
  4. DOCKER_HOST env var pointing to the rootless Docker socket
  5. 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 /sandbox entrypoint binary — we use our own bun 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/1000

Pro: 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 Dockerfile

Pro: 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.ts

Replace 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.0

The --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:

  1. Always install Docker — simplest. Accept the image size increase. Gate dockerd startup on a config flag so it doesn't run unless needed.
  2. 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.
  3. 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.ts

Image 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
  • dockerd starts conditionally via ENABLE_DOCKER flag
  • DOCKER_HOST env var set to rootless socket path
  • Agent can run docker build and docker run inside the container
  • Agent can run wrangler dev with 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Post-launchenhancementNew feature or requestgt:containerContainer management, agent processes, SDK, heartbeat

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions