You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+47-17Lines changed: 47 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,12 +14,13 @@ that hosts the devcontainers these features get installed into — see
14
14
15
15
| Feature | OCI reference | Summary |
16
16
|---|---|---|
17
-
|`claude-code`|`ghcr.io/sourecode/devcontainer-features/claude-code:2`| Installs the Claude Code CLI via the official native installer into `/usr/local/bin`, so the binary survives home-directory volume mounts. Requires Node.js — automatically pulls in the `nvm` feature via `dependsOn`. |
18
-
|`rtk`|`ghcr.io/sourecode/devcontainer-features/rtk:2`| Installs [rtk](https://github.com/rtk-ai/rtk), an LLM token-reducing CLI proxy, into `/usr/local/bin`. Auto-patches Claude Code via `postCreateCommand` so the hook is written against the mounted home, not the image. |
19
-
|`context-mode`|`ghcr.io/sourecode/devcontainer-features/context-mode:2`| Installs the [`context-mode`](https://github.com/mksglu/context-mode) Claude Code plugin via `postCreateCommand`, so the plugin lands in the mounted `~/.claude` rather than the image. |
17
+
|`claude-code`|`ghcr.io/sourecode/devcontainer-features/claude-code:2`| Installs the Claude Code CLI via the official native installer into `/usr/local/bin`. Declares `~/.claude` and `~/.claude.json` as persistence targets via the `home-persist` manifest. Requires Node.js — automatically pulls in the `nvm` feature via `dependsOn`. |
18
+
|`rtk`|`ghcr.io/sourecode/devcontainer-features/rtk:2`| Installs [rtk](https://github.com/rtk-ai/rtk), an LLM token-reducing CLI proxy, into `/usr/local/bin`. Auto-patches Claude Code via `postCreateCommand` so the hook is written against the live `~/.claude`, not the image. |
19
+
|`context-mode`|`ghcr.io/sourecode/devcontainer-features/context-mode:2`| Installs the [`context-mode`](https://github.com/mksglu/context-mode) Claude Code plugin via `postCreateCommand`, so the plugin lands in `~/.claude/plugins` (which `home-persist` symlinks into the persistence volume when installed). |
20
+
|`home-persist`|`ghcr.io/sourecode/devcontainer-features/home-persist:1`| Symlinks declared `$HOME` paths into a per-owner persistence volume at `/mnt/home-persist`. Features and users contribute paths via JSON manifests in `/etc/devcontainer-persist.d/`; an `onCreateCommand` resolver materializes the symlinks on every create. |
20
21
|`nvm`|`ghcr.io/sourecode/devcontainer-features/nvm:2`| Installs [nvm](https://github.com/nvm-sh/nvm) system-wide at `/usr/local/share/nvm` and optionally a Node version (defaults to LTS), with `node`/`npm`/`npx` symlinked into `/usr/local/bin`. No yarn. |
21
22
22
-
All binaries land in `/usr/local/bin` (or `/usr/local/share/...`) rather than the user's home, so they survive the shared home-volume pattern described in[`docs/persistence.md`](docs/persistence.md). `rtk` and `context-mode` declare `installsAfter` for both `ghcr.io/sourecode/devcontainer-features/claude-code` and `ghcr.io/anthropics/devcontainer-features/claude-code`, so the runtime orders them after whichever claude-code feature is present.
23
+
All binaries land in `/usr/local/bin` (or `/usr/local/share/...`) rather than the user's home, so they stay image-owned. Per-user state that needs to survive rebuilds is declared explicitly via the `home-persist` manifest — see[`docs/persistence.md`](docs/persistence.md). `rtk` and `context-mode` declare `installsAfter` for both `ghcr.io/sourecode/devcontainer-features/claude-code` and `ghcr.io/anthropics/devcontainer-features/claude-code`, so the runtime orders them after whichever claude-code feature is present.
23
24
24
25
## Using the features
25
26
@@ -72,12 +73,24 @@ claude-code feature as well. `installsAfter` handles ordering for either
72
73
|`version`| string |`0.40.4`| nvm release tag to install (without the leading `v`). |
73
74
|`node`| string |`lts`| Node version to install via nvm. `lts` uses `nvm install --lts`. `none` skips node install. Anything else is passed as-is to `nvm install`. |
74
75
76
+
#### `home-persist`
77
+
78
+
| Option | Type | Default | Purpose |
79
+
|---|---|---|---|
80
+
|`paths`| string |`""`| Comma-separated list of `$HOME`-relative paths to persist (e.g. `.claude,.claude.json,.gitconfig`). Written to `/etc/devcontainer-persist.d/user.json` at build time. Leave empty if you only want features to contribute paths. |
81
+
82
+
Requires a bind mount from a persistent source to `/mnt/home-persist` in
83
+
`devcontainer.json`. See [`docs/persistence.md`](docs/persistence.md) for
84
+
the full model.
85
+
75
86
### Persisting Claude Code state
76
87
77
88
Claude login (`~/.claude/.credentials.json`) and chat history (`projects/`,
78
-
`sessions/`, `session-env/`) live in the user's home. Persist them by mounting
79
-
`$HOME` as a named volume — see [`docs/persistence.md`](docs/persistence.md)
80
-
for the shared-home pattern we use across every devcontainer.
89
+
`sessions/`, `session-env/`) live in `~/.claude`. The `claude-code` feature
90
+
declares `.claude` and `.claude.json` in its manifest, so installing
91
+
`home-persist` alongside it — plus bind-mounting a persistent source to
92
+
`/mnt/home-persist` — is enough to carry state across rebuilds. See
93
+
[`docs/persistence.md`](docs/persistence.md) for the full model.
81
94
82
95
## Coder workspace template
83
96
@@ -268,6 +281,10 @@ src/
268
281
context-mode/
269
282
devcontainer-feature.json
270
283
install.sh
284
+
home-persist/
285
+
devcontainer-feature.json
286
+
install.sh
287
+
resolve.sh
271
288
nvm/
272
289
devcontainer-feature.json
273
290
install.sh
@@ -289,19 +306,32 @@ that runs as `root` inside the container during the build.
289
306
290
307
-`install.sh` starts as `root`. **Prefer system-wide install paths**
291
308
(`/usr/local/bin`, `/usr/local/share/<id>`, `/etc/profile.d`) over anything
292
-
under the remote user's home. The shared-home volume pattern
293
-
([`docs/persistence.md`](docs/persistence.md)) means writes into
294
-
`/home/<user>` at build time only appear on first-create and then get
295
-
shadowed by the named volume on every subsequent run.
296
-
- If a tool's upstream installer insists on writing to `$HOME`, run it under
297
-
a scratch `HOME` (`mktemp -d`) and relocate the resulting binary to
298
-
`/usr/local/bin` (see `src/claude-code/install.sh`). If the tool supports
299
-
an override env var (e.g. `RTK_INSTALL_DIR`), pass it directly.
309
+
under the remote user's home. Paths under `$HOME` that the feature needs
310
+
to persist across rebuilds should be declared via a `home-persist`
311
+
manifest (see below), not written at build time — the symlinks don't
312
+
exist yet during `install.sh`.
313
+
- If a tool's upstream installer insists on writing to `$HOME`, relocate
314
+
the resulting binary to `/usr/local/bin` (see `src/claude-code/install.sh`).
315
+
If the tool supports an override env var (e.g. `RTK_INSTALL_DIR`), pass
316
+
it directly.
300
317
- For anything that genuinely needs to live in the user's real home
301
318
(credentials, plugin state, shell-rc tweaks), emit a script to
302
319
`/usr/local/share/<id>/post-create.sh` and wire it via `postCreateCommand`
303
-
in `devcontainer-feature.json` so it runs against the mounted home, not
304
-
the image.
320
+
in `devcontainer-feature.json` so it runs after the `home-persist`
321
+
resolver has symlinked the target paths into place.
322
+
- If your feature writes persistent state under `$HOME`, declare those
323
+
paths by dropping a JSON manifest in `install.sh`:
0 commit comments