@@ -5,6 +5,11 @@ for Claude Code and friends, plus a lightweight `nvm` feature.
55
66Published to GHCR under the ` sourecode/devcontainer-features ` namespace.
77
8+ This repo also contains the Coder workspace template
9+ ([ ` Dockerfile.workspace ` ] ( Dockerfile.workspace ) and [ ` main.tf ` ] ( main.tf ) )
10+ that hosts the devcontainers these features get installed into — see
11+ [ Coder workspace template] ( #coder-workspace-template ) .
12+
813## Features
914
1015| Feature | OCI reference | Summary |
@@ -74,6 +79,181 @@ Claude login (`~/.claude/.credentials.json`) and chat history (`projects/`,
7479` $HOME ` as a named volume — see [ ` docs/persistence.md ` ] ( docs/persistence.md )
7580for the shared-home pattern we use across every devcontainer.
7681
82+ ## Coder workspace template
83+
84+ ` Dockerfile.workspace ` builds a Coder workspace image and ` main.tf ` is the
85+ Coder template that launches it. The workspace container runs its own
86+ ` dockerd ` under the [ sysbox] ( https://github.com/nestybox/sysbox ) runtime
87+ (` runtime = "sysbox-runc" ` in ` main.tf ` ), so ` @devcontainers/cli ` inside the
88+ workspace talks to a local daemon and bind-mount paths resolve against the
89+ same filesystem the daemon sees.
90+
91+ ### Architecture
92+
93+ ```
94+ host docker daemon
95+ └── workspace container (sourecode/coder-workspace:latest, runtime=sysbox-runc)
96+ ├── systemd (PID 1)
97+ ├── dockerd
98+ ├── coder-agent.service (main agent)
99+ └── @devcontainers/cli up
100+ └── devcontainer container(s) (compose, features, lifecycle all work)
101+ └── coder sub-agent (runs code-server / jetbrains here)
102+ ```
103+
104+ Editors run ** inside the devcontainer** via Coder's Dev Containers integration
105+ (` coder_devcontainer.subagent_id ` ). When you click "Open in code-server" in the
106+ Coder UI, you land in the devcontainer's filesystem with its tools, not the
107+ outer workspace.
108+
109+ One extra container over plain Coder workspaces. No DooD path translation.
110+
111+ ### Template files
112+
113+ - ` Dockerfile.workspace ` — builds the workspace image. Ubuntu + systemd +
114+ docker-ce + Node LTS + ` @devcontainers/cli ` + a ` coder ` user at UID 1000.
115+ - ` main.tf ` — Coder template. Launches the workspace container under
116+ ` runtime = "sysbox-runc" ` , injects ` CODER_AGENT_TOKEN ` via env, and uploads
117+ the agent init script to ` /etc/coder/agent-init.sh ` . The
118+ ` coder-agent.service ` systemd unit (baked into the image, see
119+ ` Dockerfile.workspace ` ) runs that script on boot.
120+
121+ ### Prerequisites (on the Docker host)
122+
123+ 1 . Linux kernel >= 5.12 (>= 6.3 ideal, avoids shiftfs entirely)
124+ 2 . Native Docker (not the snap) at ` /usr/bin/docker `
125+ 3 . Sysbox installed (see below)
126+ 4 . Your existing Coder server (this template was developed against a
127+ docker-compose-deployed Coder)
128+
129+ ### Install sysbox
130+
131+ Zero-container-deletion install, tolerates a single ` dockerd ` restart.
132+
133+ ``` bash
134+ # 1. pre-populate /etc/docker/daemon.json so sysbox's post-install step
135+ # doesn't need to touch the network config itself
136+ sudo tee /etc/docker/daemon.json > /dev/null << 'JSON '
137+ {
138+ "bip": "172.24.0.1/16",
139+ "default-address-pools": [
140+ { "base": "172.31.0.0/16", "size": 24 }
141+ ]
142+ }
143+ JSON
144+
145+ # Pick CIDRs free of your existing networks:
146+ # docker network inspect $(docker network ls -q) | grep -i subnet
147+
148+ # 2. one controlled restart so dockerd loads the keys
149+ sudo systemctl restart docker
150+
151+ # 3. install sysbox (Ubuntu/Debian amd64)
152+ wget https://downloads.nestybox.com/sysbox/releases/v0.7.0/sysbox-ce_0.7.0-0.linux_amd64.deb
153+ sudo apt-get install -y jq fuse3 ./sysbox-ce_0.7.0-0.linux_amd64.deb
154+
155+ # 4. verify
156+ docker info | grep -i runtime # should list sysbox-runc
157+ systemctl status sysbox --no-pager
158+ ```
159+
160+ Smoke test that nested Docker works under sysbox:
161+
162+ ``` bash
163+ CID=$( docker run -d --rm --runtime=sysbox-runc nestybox/ubuntu-noble-systemd-docker)
164+ sleep 15
165+ docker exec " $CID " docker run --rm hello-world # should print the hello-world greeting
166+ docker stop " $CID "
167+ ```
168+
169+ ### Build the workspace image
170+
171+ ``` bash
172+ docker build -f Dockerfile.workspace -t sourecode/coder-workspace:latest .
173+ ```
174+
175+ The image must exist in the host's local image store (or in a registry the
176+ host can pull from). It is referenced by ` var.workspace_image ` in ` main.tf ` .
177+
178+ ### Push the template to Coder
179+
180+ If your Coder runs inside a docker-compose stack and you prefer not to install
181+ ` coder ` on the host, use the CLI that's baked into the Coder server image:
182+
183+ ``` bash
184+ # copy the template files into the Coder container
185+ docker exec coder-coder-1 mkdir -p /tmp/tpl
186+ docker cp ./main.tf coder-coder-1:/tmp/tpl/main.tf
187+ docker cp ./Dockerfile.workspace coder-coder-1:/tmp/tpl/Dockerfile.workspace
188+
189+ # login once
190+ docker exec -it coder-coder-1 /opt/coder login http://localhost:7080
191+
192+ # push
193+ docker exec -it coder-coder-1 /opt/coder templates push coder-template -d /tmp/tpl --yes
194+ ```
195+
196+ Or install the ` coder ` CLI locally and push from the repo dir directly.
197+
198+ #### Important: template variables
199+
200+ If you push a newer version of the template but workspaces still launch from
201+ an old image, check ** Templates → Settings → Variables** in the Coder UI.
202+ A persisted override for ` workspace_image ` wins over the ` default ` in ` main.tf `
203+ across version bumps. Clear it or set it to ` sourecode/coder-workspace:latest ` .
204+
205+ ### Create / update workspaces
206+
207+ A workspace pinned to an older template version does not auto-upgrade. After
208+ pushing a new version, either:
209+
210+ - Click ** Update** on the workspace in the UI, or
211+ - ` coder update <workspace-name> ` (from wherever you have the CLI)
212+
213+ ### Troubleshooting
214+
215+ - ** "Agent is taking longer than expected to connect"** — the workspace
216+ container exited instead of running systemd. Check:
217+ ``` bash
218+ CID=$( docker ps -a --filter " name=coder-" -q | head -1)
219+ docker inspect " $CID " --format ' {{.HostConfig.Runtime}} {{.Config.Image}} {{.State.Status}}'
220+ docker logs " $CID " | tail -50
221+ ```
222+ Runtime must be ` sysbox-runc ` (hardcoded in ` main.tf ` ); image should match
223+ whatever ` var.workspace_image ` resolves to (default
224+ ` sourecode/coder-workspace:latest ` ). If either is wrong, fix the template /
225+ variable override and recreate.
226+
227+ - ** Agent up but nothing connects** — inspect systemd and the agent unit:
228+ ``` bash
229+ docker exec " $CID " systemctl is-system-running
230+ docker exec " $CID " systemctl status docker coder-agent --no-pager
231+ docker exec " $CID " journalctl -u coder-agent --no-pager -n 100
232+ docker exec " $CID " ls -la /etc/coder/ # expect agent-init.sh present and executable
233+ docker exec " $CID " bash -lc " tr '\0' '\n' < /proc/1/environ | grep CODER_AGENT_TOKEN"
234+ ```
235+
236+ - ** Inner ` @devcontainers/cli up ` hangs / errors** — run it by hand inside the
237+ workspace as the ` coder ` user to see the real output:
238+ ``` bash
239+ docker exec -u coder -it " $CID " bash -lc " cd ~/<repo> && devcontainer up --workspace-folder ."
240+ ```
241+
242+ ### Why this shape
243+
244+ ` @devcontainers/cli ` assumes the CLI and the Docker daemon share a filesystem.
245+ When Coder launches a workspace container and mounts the host socket in, the
246+ CLI (inside the workspace) and the daemon (on the host) see different
247+ filesystems — every bind-mount path the CLI emits is wrong from the daemon's
248+ point of view. That's the root cause of the
249+ ` bind source path does not exist ` failures.
250+
251+ The principled fixes are: path alignment (fragile), DinD (insecure),
252+ sysbox (safe DinD), envbuilder (collapse into one container), or CI pre-build
253+ (move work out of runtime). This template uses sysbox so the workspace's own
254+ ` dockerd ` runs safely inside it, paths resolve naturally, and compose-based
255+ devcontainers work unchanged.
256+
77257## Developing on this repo
78258
79259### Repository layout
97277docs/
98278 migration-guide.md
99279 persistence.md
280+ Dockerfile.workspace # Coder workspace image (Ubuntu + systemd + dockerd + @devcontainers/cli)
281+ main.tf # Coder template that launches the workspace under sysbox-runc
100282```
101283
102284Each feature directory follows the [ Dev Container Features spec] ( https://containers.dev/implementors/features/ ) :
0 commit comments