88 lines
4.0 KiB
Markdown
88 lines
4.0 KiB
Markdown
# devcontainer-stack
|
|
|
|
A template for one dev container holding several repos at once, so a single
|
|
Claude Code session can work across a whole stack (a backend and its frontend,
|
|
a service and its client library) instead of one repo at a time.
|
|
|
|
Copy this directory next to the repos it should hold, rename it, and edit the
|
|
four files marked below.
|
|
|
|
## Layout
|
|
|
|
The repos live next to this directory on the host and are bind mounted into
|
|
the container as subdirectories of the workspace root:
|
|
|
|
/workspaces/ <- workspace root
|
|
├── my-backend/ (bind mount)
|
|
├── my-frontend/ (bind mount)
|
|
└── devcontainer-stack/ (this repo, bind mount)
|
|
├── .devcontainer/
|
|
├── stack.conf
|
|
├── dev.sh
|
|
└── stack.code-workspace
|
|
|
|
All of them are siblings under the workspace root, so one Claude Code session
|
|
started there reaches every repo with no `--add-dir` and no
|
|
`additionalDirectories` permission entry.
|
|
|
|
They are deliberately not nested inside this repo's own bind mount: mount
|
|
points created inside a bind mount are made by the Docker daemon as root and
|
|
write through to the host, which leaves root-owned empty directories behind.
|
|
|
|
## Adapting it
|
|
|
|
1. Rename the directory. Nothing reads its name: the scripts locate themselves,
|
|
and `devcontainer.json` uses `${localWorkspaceFolderBasename}`.
|
|
2. `stack.conf`: one line per repo, `directory|setup command|run command`.
|
|
Setup runs once at container create, run starts that repo's dev server.
|
|
3. `.devcontainer/devcontainer.json`: one `mounts` entry per repo, matching
|
|
those directories. Then `containerEnv` for whatever the servers read out of
|
|
the environment, and the VS Code extension list.
|
|
4. `.devcontainer/Dockerfile`: the toolchains. It starts from the plain
|
|
`base:bookworm` image, with commented patterns for adding a language, apt
|
|
packages and pinned linters.
|
|
5. `stack.code-workspace`: one folder entry per repo.
|
|
|
|
Steps 2 to 5 repeat the repo directories in four files. Keeping them in sync by
|
|
hand is the price of `devcontainer.json` not being able to read a config file.
|
|
|
|
## Use
|
|
|
|
1. Open this folder in VS Code. When prompted, choose **Reopen in Container**,
|
|
not "Open Workspace": the latter opens the workspace with host paths and
|
|
bypasses the container entirely.
|
|
2. Once inside the container, `File > Open Workspace from File...` on
|
|
`stack.code-workspace`. The separate roots make each language server root
|
|
itself at the manifest it expects. Skipping this step still works; the
|
|
language servers are just less precise.
|
|
3. `./dev.sh` starts every server declared in `stack.conf`. Ctrl-C stops all of
|
|
them.
|
|
|
|
The container uses host networking, so the host browser reaches the servers on
|
|
`localhost` at whatever ports they bind, with no `forwardPorts` list to keep in
|
|
sync.
|
|
|
|
## Notes
|
|
|
|
**Signals.** `dev.sh` sends `SIGTERM` to the process it started. A wrapper that
|
|
does not forward signals leaves the real server running and holding its port
|
|
after Ctrl-C. `go run` is one: build a binary and run that. Calling a dev
|
|
server's binary directly (`./node_modules/.bin/vite`) rather than through a
|
|
package manager script is one process less to orphan.
|
|
|
|
**pnpm.** `pnpm` records the absolute install path in `node_modules` and asks
|
|
to purge it when that path changes, which it does when the same repo was
|
|
installed on the host or in a differently-mounted container. `post-create.sh`
|
|
has no TTY, so the prompt aborts the whole create. Pass
|
|
`--config.confirmModulesPurge=false`.
|
|
|
|
**Dev state.** Point the servers at paths inside their own repo (`.dev/` is the
|
|
convention here, and `stack.code-workspace` hides it) rather than at the
|
|
`/data`, `/cache` paths their release images expect. The state then disappears
|
|
with the repo and survives container rebuilds.
|
|
|
|
**Per-repo containers.** A repo that has its own `.devcontainer/` keeps it, for
|
|
single-repo work and to stay independently buildable. This container supersedes
|
|
them when working across the stack; don't run both at once, since they bind the
|
|
same ports.
|