11# SoureCode Coder Workspace Template
22
33A Coder workspace template plus a family of workspace images with pre-installed
4- dev tooling. Each workspace runs its own ` dockerd ` under
5- [ sysbox ] ( https://github.com/nestybox/sysbox ) , so you can build and test your
6- own ` Dockerfile ` s, run ` docker compose ` stacks, etc. inside the workspace .
4+ dev tooling. Each workspace gets the host's Docker socket bind-mounted in
5+ (Docker-out-of-Docker ), so you can ` docker build` , ` docker compose up ` , etc.
6+ from inside the workspace using the host daemon .
77
88One container per workspace — no nested devcontainer layer. IDEs (VS Code
99Desktop, JetBrains, code-server, web-shell) all attach to the single
@@ -15,7 +15,7 @@ Images are published to GHCR under `ghcr.io/sourecode/coder-workspace`.
1515
1616| Tag | Base | Adds |
1717| ---| ---| ---|
18- | ` base ` | ` debian:trixie-slim ` | systemd + dockerd + ` nvm ` + ` claude-code ` + ` rtk ` + ` web-shell ` + ` home-persist ` + ` jetbrains ` |
18+ | ` base ` | ` debian:trixie-slim ` | systemd + docker CLI (DooD) + ` nvm ` + ` claude-code ` + ` rtk ` + ` web-shell ` + ` home-persist ` + ` jetbrains ` |
1919| ` node ` | ` :base ` | named variant for future Node-specific tooling — currently identical to ` base ` (Node comes from nvm) |
2020| ` cpp ` | ` :base ` | ` llvm ` (clang + toolchain), ` cmake ` , ` sccache ` , ` /etc/profile.d/llvm-env.sh ` exporting ` CC ` /` CXX ` |
2121
@@ -43,21 +43,26 @@ goes through `home-persist`'s manifest system.
4343## Architecture
4444
4545```
46- host docker daemon (sysbox-runc runtime registered)
46+ host docker daemon
47+ ├── /var/run/docker.sock ─────────┐ (bind-mounted into every workspace)
4748 └── workspace container (ghcr.io/sourecode/coder-workspace:<tag>)
48- ├── systemd (PID 1)
49- ├── dockerd (for in-workspace docker build / docker compose )
49+ ├── systemd (PID 1) │
50+ ├── docker CLI ────────────────┘ (Docker-out-of-Docker via host socket )
5051 └── coder-agent.service (runs /etc/coder/agent-init.sh as `coder`)
5152```
5253
54+ The container runs ` --privileged ` and shares the host Docker socket. This is
55+ fine for a single-tenant box but means workspace root effectively equals host
56+ root — don't onboard untrusted users.
57+
5358## Template files
5459
55- - ` main.tf ` — Coder template. Launches the workspace container under
56- ` runtime = "sysbox-runc" ` , injects ` CODER_AGENT_TOKEN ` via env, and uploads
57- the agent init script to ` /etc/coder/agent-init.sh ` . The
58- ` coder- agent.service ` systemd unit (baked into the image) runs that script
59- on boot.
60- - ` src/base/Dockerfile ` — shared base: Debian trixie + systemd + dockerd +
60+ - ` main.tf ` — Coder template. Launches the workspace container with
61+ ` privileged = true ` , bind-mounts ` /var/run/docker.sock ` from the host,
62+ injects ` CODER_AGENT_TOKEN ` via env, and uploads the agent init script to
63+ ` /etc/ coder/agent-init.sh ` . The ` coder- agent.service` systemd unit (baked
64+ into the image) runs that script on boot.
65+ - ` src/base/Dockerfile ` — shared base: Debian trixie + systemd + docker CLI +
6166 ` coder ` user + dev-kit scripts.
6267- ` src/node/Dockerfile ` , ` src/cpp/Dockerfile ` — stack variants (` FROM :base ` ).
6368- ` scripts/<name>/install.sh ` — bound into each Dockerfile at build time via
@@ -66,51 +71,13 @@ goes through `home-persist`'s manifest system.
6671
6772## Prerequisites (on the Docker host)
6873
69- 1 . Linux kernel >= 5.12 (>= 6.3 ideal, avoids shiftfs entirely)
70- 2 . Native Docker (not the snap) at ` /usr/bin/docker `
71- 3 . Sysbox installed (see below)
72- 4 . An existing Coder server (this template was developed against a
74+ 1 . Native Docker (not the snap) at ` /usr/bin/docker `
75+ 2 . An existing Coder server (this template was developed against a
7376 docker-compose-deployed Coder)
7477
75- ## Install sysbox
76-
77- Zero-container-deletion install, tolerates a single ` dockerd ` restart.
78-
79- ``` bash
80- # 1. pre-populate /etc/docker/daemon.json so sysbox's post-install step
81- # doesn't need to touch the network config itself
82- sudo tee /etc/docker/daemon.json > /dev/null << 'JSON '
83- {
84- "bip": "172.24.0.1/16",
85- "default-address-pools": [
86- { "base": "172.31.0.0/16", "size": 24 }
87- ]
88- }
89- JSON
90-
91- # Pick CIDRs free of your existing networks:
92- # docker network inspect $(docker network ls -q) | grep -i subnet
93-
94- # 2. one controlled restart so dockerd loads the keys
95- sudo systemctl restart docker
96-
97- # 3. install sysbox (Ubuntu/Debian amd64)
98- wget https://downloads.nestybox.com/sysbox/releases/v0.7.0/sysbox-ce_0.7.0-0.linux_amd64.deb
99- sudo apt-get install -y jq fuse3 ./sysbox-ce_0.7.0-0.linux_amd64.deb
100-
101- # 4. verify
102- docker info | grep -i runtime # should list sysbox-runc
103- systemctl status sysbox --no-pager
104- ```
105-
106- Smoke test that nested Docker works under sysbox:
107-
108- ``` bash
109- CID=$( docker run -d --rm --runtime=sysbox-runc nestybox/ubuntu-noble-systemd-docker)
110- sleep 15
111- docker exec " $CID " docker run --rm hello-world # should print the hello-world greeting
112- docker stop " $CID "
113- ```
78+ No sysbox or special runtime is required — workspaces run under the default
79+ runc with ` --privileged ` and the host's ` /var/run/docker.sock ` bind-mounted
80+ in.
11481
11582## Build the workspace images
11683
@@ -162,29 +129,42 @@ pushing a new version, either:
162129 container exited instead of running systemd. Check:
163130 ``` bash
164131 CID=$( docker ps -a --filter " name=coder-" -q | head -1)
165- docker inspect " $CID " --format ' {{.HostConfig.Runtime}} {{. Config.Image}} {{.State.Status}}'
132+ docker inspect " $CID " --format ' {{.Config.Image}} {{.State.Status}}'
166133 docker logs " $CID " | tail -50
167134 ```
168- Runtime must be ` sysbox-runc ` . Image should match whatever the template's
169- ` workspace_image ` parameter resolved to.
135+ Image should match whatever the template's ` workspace_image ` parameter
136+ resolved to.
170137
171138- ** Agent up but nothing connects** — inspect systemd and the agent unit:
172139 ``` bash
173140 docker exec " $CID " systemctl is-system-running
174- docker exec " $CID " systemctl status docker coder-agent --no-pager
141+ docker exec " $CID " systemctl status coder-agent --no-pager
175142 docker exec " $CID " journalctl -u coder-agent --no-pager -n 100
176143 docker exec " $CID " ls -la /etc/coder/ # expect agent-init.sh present + executable
177144 docker exec " $CID " bash -lc " tr '\0' '\n' < /proc/1/environ | grep CODER_AGENT_TOKEN"
178145 ```
179146
180- ## Why sysbox
147+ - ** ` docker ` from inside the workspace says "permission denied"** — the
148+ bind-mounted host socket has the host's ` docker ` group GID, which may not
149+ match the in-image ` docker ` group. The entrypoint aligns them at boot;
150+ if it didn't run (or you exec'd a fresh shell before it finished), check:
151+ ``` bash
152+ docker exec " $CID " stat -c ' %g' /var/run/docker.sock
153+ docker exec " $CID " getent group docker
154+ ```
155+ The two GIDs must match for ` coder ` to use the socket without sudo.
156+
157+ ## Why DooD (and not DinD or sysbox)
181158
182- The workspace bakes ` dockerd ` in so you can ` docker build ` , ` docker compose up ` ,
183- or run a project's own Dockerfile straight from inside your workspace without
184- going through the host daemon. Running an inner dockerd safely inside a
185- container is exactly what sysbox provides — plain ` runc ` would require
186- ` --privileged ` and you'd still fight shared-kernel artefacts. Sysbox handles
187- it with proper namespace isolation.
159+ Bind-mounting the host socket lets ` docker build ` , ` docker compose up ` , and
160+ project Dockerfiles run from inside the workspace without nesting a second
161+ Docker engine. The previous setup ran an inner ` dockerd ` under sysbox for
162+ isolation; we dropped it because the maintenance cost (sysbox install,
163+ persistent ` /var/lib/docker ` volume per workspace, dockerd shutdown
164+ ordering) outweighed the benefit on a single-tenant deployment. The trade
165+ is that workspaces share image cache and network namespace with the host
166+ daemon, and ` --privileged ` means workspace root can reach host root — only
167+ do this where you trust every workspace owner.
188168
189169## Developing on this repo
190170
@@ -205,7 +185,7 @@ scripts/
205185 sccache/install.sh
206186 web-shell/install.sh
207187src/
208- base/Dockerfile # debian-trixie + systemd + dockerd + dev-kit
188+ base/Dockerfile # debian-trixie + systemd + docker CLI + dev-kit
209189 cpp/Dockerfile # FROM :base + llvm/cmake/sccache
210190 node/Dockerfile # FROM :base
211191main.tf # Coder template
0 commit comments