Skip to content

A development environment inside container workrooms #308

Closed

Description

@joelmoss

🤖 Left by Claude Code, from the eng review of the local-container workrooms plan.

Problem

The workroom-host image (vcs/scripts/ssh-fixture/Dockerfile, FIXTURE=0) installs only openssh-server git procps ca-certificates. The workroom user has no sudo, and creating a container workroom runs no setup script (docs/designs/remote-workrooms.md:1583). So a fresh container has no language toolchain and no agent CLI (claude, codex), and no way to install either. Git and a terminal work, but an agent can't run a project's tests or even start.

The local-container issue ships with the copy "git and a terminal; tooling setup comes later". This issue is that later.

Options to compare

  • Passwordless sudo plus the project's existing workroom setup script. The script runs in the base and is baked in, so derived workrooms inherit it. This reuses internal/script, which local workrooms already run, and the container is the security boundary.
  • devcontainer.json support, a format many repositories already have.
  • A per-project image.

Acceptance

One real project, inside a container workroom, installs its dependencies, runs its tests and starts an agent CLI.

Related: #253, #288, #299.

Activity

  1. joelmoss commented on Oct 23, 2026

    @joelmoss
    OwnerAuthor

    🤖 Left by Claude Code.

    Proposal: any image, reached over docker exec

    The owner wants a container workroom's image and container to be fully customisable. workroom-host can't offer that, because it is the far side the driver needs: sshd, the supervisor loop, host-key minting and the workroom user. So this proposes dropping the image and ssh from the container driver. It is written up as an amendment to docs/designs/remote-workrooms.md (the #309 "As built" section, "Not done"), committed locally as 5bd5f24 and not pushed yet.

    Shape

    • Transport. exec, openStream and attachCommand become docker exec -i (relay, bootstrap) and docker exec -it (a pane, spawned directly by libghostty, so no bridge process). ssh was only the carrier: port forwards and the credential relay already ride the daemon connection. ADR 0028's bar holds, because anyone who can reach the Docker socket already has a root shell in the container.
    • Supervisor. The entrypoint is the bundled static workroomd serve, run with --init --restart unless-stopped. The image's own ENTRYPOINT/CMD don't run, as with devcontainers' default overrideCommand.
    • Daemon in. docker create, then docker cp (or a read-only bind mount), then docker start. Apple's container can't copy into a created container and has no restart policy, so it would take the bind mount plus the existing start-on-open.
    • Configuration. devcontainer.json, which DevContainerPorts already reads for ports: image/build, mounts, containerEnv, remoteUser, runArgs. Projects without one get a default image and a setting to choose another. features come later.
    • The image must have git and a shell for panes. Nothing else.

    Measured on Docker Desktop (arm64) with the Dev build's workroomd-linux-aarch64, with no image changes. Images: debian:bookworm-slim, alpine:3.22, node:22 and python:3.13-slim.

    • The daemon served on all four.
    • 20 MB of random bytes made the round trip through docker exec -i intact.
    • hand-off --force answered handed off.
    • The daemon served again after docker restart.
    • A pane attached through docker exec -it ran a command and showed its output.
    • The bind-mount variant worked on alpine.
    • As node:22's node user it worked once its socket folder existed and was owned by node.

    Only node:22 has git. Not measured: Apple's runtime, throughput beyond 20 MB, and a stdin close mid-stream. DevPod, the devcontainers CLI and Coder reach arbitrary images the same way, and none of them needs sshd in the image.

    Costs

    • A container-specific install path. The app puts the new build in place from the Mac and points hand-off --binary at it, instead of using install.sh's shell stage-then-rename.
    • Pane status (Remote panes never reconnect after their host restarts: macOS login swallows ssh's exit 255 #392) needs a mapping for docker exec's 125–127 exit codes instead of ssh's.
    • Two daemon fixes. serve doesn't create its socket's folder, and the shell falls back to /bin/zsh when SHELL is unset.
    • Containers already made from workroom-host (Nightly and Dev only) keep the ssh path or are made again.

    Removes: workroom-host, host-image.yml and the ghcr package, the WorkroomHostImage pin, sshd hardening, host-key minting and the published ssh port.

    Rejected: layering a static ssh server onto the user's image (a second Linux artifact, and it still needs a shell), and FROM workroom-host (Debian only, and a Dockerfile per user).

    Open (owner): does stable wait for this, or first ship container workrooms on workroom-host?

  2. 35 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-triageremoteRemote workrooms: hosts, providers, the far-side agent and broker

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions