From 98f7ac491840853595101c545328f9ce606feee9 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 01:38:16 +0000 Subject: [PATCH] docs: add repository README and rework feature documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repository had no README, so a visitor could not tell what it publishes, who it is for, or how it relates to the images and templates repositories. Add one that opens with the feature list and the goals behind them, then covers usage, the tags each release publishes, what a reader is trusting and can verify, and how the features are tested. Rework each feature's NOTES.md into the same section order — requirements, usage, installed software, what is left out, supply chain, tips — and regenerate the feature READMEs from them. Add the MIT license and a security policy the README links to. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Li88NWapAE1Uy9SKoEtCyF --- LICENSE | 21 ++++++ README.md | 153 ++++++++++++++++++++++++++++++++++++++ SECURITY.md | 12 +++ src/claude-code/NOTES.md | 85 +++++++++++++++------ src/claude-code/README.md | 85 +++++++++++++++------ src/node/NOTES.md | 101 +++++++++++++++++-------- src/node/README.md | 101 +++++++++++++++++-------- 7 files changed, 452 insertions(+), 106 deletions(-) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SECURITY.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..11fd5ca --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Takashi Nozawa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..22fe7d8 --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +# Bare Dev Container Features + +[![CI](https://github.com/bare-devcontainer/features/actions/workflows/ci.yml/badge.svg)](https://github.com/bare-devcontainer/features/actions/workflows/ci.yml) + +[Dev Container Features](https://containers.dev/features) for Debian and Ubuntu based +dev containers, published to `ghcr.io/bare-devcontainer/features`. Each Feature installs +a single tool from the upstream project's own distribution channel, verified against +signing material committed to this repository, and requires no other Feature. + +## Features + +| Feature | What you get | Source and verification | +|---------|--------------|-------------------------| +| [claude-code](src/claude-code)
`ghcr.io/bare-devcontainer/features/claude-code` | Claude Code, Anthropic's agentic coding CLI, with the VS Code extension requested alongside it. Credentials, settings and history live on a volume, so a rebuild does not mean signing in again. | Anthropic's APT repository. The package signature is checked with a key committed to this repository. | +| [node](src/node)
`ghcr.io/bare-devcontainer/features/node` | Node.js, with Corepack in place of npm, so the package manager comes from the project's `packageManager` field rather than from the image. | [nodejs.org](https://nodejs.org/dist/). The release checksums are checked against the Node.js release keys committed to this repository. | + +Each Feature's README documents its options, requirements, what it deliberately leaves +out, and how its supply chain works. + +## Why these features + +A dev container is part of the trusted development environment, and every Feature added +to one installs software from another upstream. These Features are built so that adding +one stays reviewable, and so that a container can be rebuilt often — which is how +security updates arrive — without that becoming expensive: + +- **Minimal trusted upstreams** — software comes from the upstream project's own + distribution channel and nowhere else, verified with the method that upstream + recommends. +- **Trusted material reviewed in this repository** — signing keys are committed here and + changed only through a pull request, so nothing is fetched and trusted while the + container is built. See [Supply chain](#supply-chain). +- **Self-contained installs** — no other Feature is required, only the prerequisites a + base image turns out to be missing are installed, and nothing from the install is left + behind in the image. +- **Minimal images stay minimal** — every Feature is installed and exercised on a + minimal Debian image as well as on Microsoft's official base image on each change, so + a Feature neither depends on tooling a larger image happens to carry nor drags such + tooling in. See [Testing](#testing). +- **Fast, frequent rebuilds** — state a rebuild would otherwise discard, such as a + tool's credentials and history, is kept in a named volume declared by the Feature, so + rebuilding to pick up an update costs nothing but the build. + +## Quick start + +Add a Feature to `.devcontainer/devcontainer.json`, then reopen the project in a +container: + +```json +{ + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "ghcr.io/bare-devcontainer/features/node:1": {} + } +} +``` + +Any [tool that supports dev containers](https://containers.dev/supporting) can install +them, onto any Debian or Ubuntu based image on `linux/amd64` or `linux/arm64`. +Requirements beyond that, such as glibc for a tool distributed as a prebuilt binary, are +stated in the Feature's own README. + +Options are passed as the value of the reference. Each Feature's README lists the +options it accepts and their defaults: + +```json +"features": { + "ghcr.io/bare-devcontainer/features/node:1": { + "version": "24", + "keepNpm": true + } +} +``` + +## Versions and pinning + +Every release publishes four tags. Using version 1.1.1 as an example: + +| Tag | Points at | Moves when | +|-----|-----------|------------| +| `1.1.1` | That exact release | Never | +| `1.1` | The newest 1.1.x release | A patch release is published | +| `1` | The newest 1.x release | A minor or patch release is published | +| `latest` | The newest release | Any release | + +A reference is resolved when the container is built, so any tag shorter than the exact +version resolves to different content over time. That is how `:1` picks up fixes +automatically, and also why it is not a reproducible reference on its own — commit +`.devcontainer/devcontainer-lock.json` to record the digest it resolved to. + +## Supply chain + +**What you are trusting.** The upstream distribution channel listed for the Feature in +the table above, and the contents of this repository at the version you pin. Nothing +else takes part in the install: the signing material is read from the Feature itself, so +no key is fetched while the container is built, and no other Feature's install script +runs on its behalf. + +**What you can check before pinning.** Every input is a file in this repository: one +install script per Feature, and the signing material sitting next to it. Each Feature's +README shows how to inspect its key. + +**How the trusted material changes.** Only through a pull request. The +`Update Trusted Material` workflow +([`.github/workflows/update-material.yml`](.github/workflows/update-material.yml)) checks +upstream weekly and opens one when a key has changed, so every rotation is visible in +the history of this repository rather than picked up silently at build time. + +**What is not covered.** The upstream projects themselves are still trusted: verification +proves a download came from them unaltered, not that what they published is sound. These +Features are published as plain OCI artifacts, with no SLSA provenance attestation and no +SBOM. And a floating tag still resolves to whatever is newest — see +[Versions and pinning](#versions-and-pinning). + +## Testing + +Every Feature ships a `smoke_test.sh` next to its `devcontainer-feature.json`. On every +change, the `CI` workflow ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)) +installs each Feature at its default options on +`mcr.microsoft.com/devcontainers/base:debian`, the official Dev Container base image +from Microsoft, and on `ghcr.io/bare-devcontainer/debian:trixie`, a minimal Debian image +with no `sudo`, no alternative shell and no CLI tooling beyond the basics, then runs +that script inside the running container as the remote user. + +The scripts use no test helper library, so each one reads as a plain description of what +a successful install looks like. + +## Related repositories + +- [bare-devcontainer/images](https://github.com/bare-devcontainer/images) — minimal + Debian based dev container images, one per language stack. When the base image can be + chosen freely, an image that already carries the tool is simpler than adding a + Feature. +- [bare-devcontainer/templates](https://github.com/bare-devcontainer/templates) — + `devcontainer.json` templates for those images, with hardened defaults and cache + volumes. Features can be added to a template's configuration after it is applied. + +## Contributing + +Bug reports, Feature requests and pull requests are welcome. [AGENTS.md](AGENTS.md) +describes the repository layout and the conventions a change is expected to follow. + +Note that `src/*/README.md` is generated by `devcontainer features generate-docs` from +`devcontainer-feature.json` and the Feature's `NOTES.md`. Put prose in `NOTES.md`; edits +made directly to a Feature's `README.md` are overwritten. + +## Security + +To report a vulnerability, see [SECURITY.md](SECURITY.md). + +## License + +[MIT](LICENSE) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..0ba209e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +## Supported Versions + +The newest published release of each feature under `src/` is supported. Fixes are +released as a new feature version and picked up by the floating tags (`1`, `1.1`, +`latest`); earlier versions are not patched in place. + +## Reporting a Vulnerability + +You can report a vulnerability privately via private vulnerability reporting on GitHub. +See [GitHub Docs](https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/report-privately) for more information. diff --git a/src/claude-code/NOTES.md b/src/claude-code/NOTES.md index 558a343..507bc85 100644 --- a/src/claude-code/NOTES.md +++ b/src/claude-code/NOTES.md @@ -1,40 +1,51 @@ -## Details +## Requirements -- Requires a Debian or Ubuntu based image. -- The repository signing key is vendored with the feature and installed to - `/usr/share/keyrings/claude-code.asc`, so no key is downloaded at build time. -- The APT source is written to `/etc/apt/sources.list.d/claude-code.list`. Set - `keepAptSource` to `false` to drop the source and key once installation - finishes. +- A Debian or Ubuntu based image: the package is installed with `apt-get`, and the + install script stops early on an image without it. -### VS Code extension +## Usage -The feature also requests the Claude Code VS Code extension, so an editor that -attaches to the container gets the sidebar, inline diffs and the IDE -integration alongside the CLI: +The defaults install the newest package and leave the APT source in place. To drop +the source and its key once the package is installed: + +```json +"features": { + "ghcr.io/bare-devcontainer/features/claude-code:1": { + "keepAptSource": false + } +} +``` + +`version` takes an APT package version rather than a release name, so read the exact +string off `apt-cache policy claude-code` in a container that has the source +configured before pinning to one. + +## Installed software + +- The `claude` CLI, from Anthropic's APT repository at + `https://downloads.claude.ai/claude-code/apt/stable`. The source is written to + `/etc/apt/sources.list.d/claude-code.list` and the signing key to + `/usr/share/keyrings/claude-code.asc`. +- The Claude Code VS Code extension (`anthropic.claude-code`), installed by clients + that read `customizations.vscode`. + +To leave the extension out, list it with a minus sign in `devcontainer.json`: ```json "customizations": { "vscode": { "extensions": [ - "anthropic.claude-code" + "-anthropic.claude-code" ] } } ``` -Extension installation is done by the attaching client, not by the install -script, so this applies to VS Code, Cursor and GitHub Codespaces, and is -ignored by clients that do not consume `customizations.vscode` (the -`devcontainer` CLI, or attaching from another editor). The CLI is installed -either way. Nothing in `devcontainer.json` can remove an extension a feature -asks for, so uninstall it in the container if it is not wanted. - -### Persisting configuration and credentials +## Configuration and credentials Claude Code keeps its credentials, settings and history in its configuration -directory. The feature declares a volume mount so those survive container -rebuilds, and points Claude Code at it: +directory. The feature declares a volume mount so those survive container rebuilds, +and points Claude Code at it: ```json "containerEnv": { @@ -83,3 +94,33 @@ The volume is per dev container (`${devcontainerId}`) and is not shared between projects. Mounts declared by a feature cannot be disabled from `devcontainer.json`, but overriding `CLAUDE_CONFIG_DIR` as shown above leaves the volume mounted and unused. + +## Supply chain + +The package is installed by APT from Anthropic's official repository, and its +signature is checked against the repository signing key. That key is vendored with +the feature and installed from the feature directory to +`/usr/share/keyrings/claude-code.asc`, so nothing is trusted that was not reviewed +in this repository — no key is downloaded at build time. It is refreshed by this +repository's `Update Trusted Material` workflow, which opens a pull request when +upstream publishes a different key. + +To see the key before pinning the feature: + +```sh +gpg --show-keys src/claude-code/claude-code.asc +``` + +`keepAptSource` decides what is left behind. Keeping the source and key, the +default, means `apt-get upgrade` inside the container can pick up newer releases. +Setting it to `false` removes both once the install finishes, so the image carries +no additional APT source. Either way the apt lists downloaded during installation +are deleted. + +## Tips + +- For the tags this feature is published under, see + [Versions and pinning](https://github.com/bare-devcontainer/features#versions-and-pinning). +- The configuration volume is per dev container, so authentication is done once per + project. Point `CLAUDE_CONFIG_DIR` at a mount of your own to share credentials + between projects. diff --git a/src/claude-code/README.md b/src/claude-code/README.md index 2f53e5c..a5fb3a5 100644 --- a/src/claude-code/README.md +++ b/src/claude-code/README.md @@ -24,43 +24,54 @@ Installs Claude Code, Anthropic's agentic coding CLI, from the official APT repo - `anthropic.claude-code` -## Details +## Requirements -- Requires a Debian or Ubuntu based image. -- The repository signing key is vendored with the feature and installed to - `/usr/share/keyrings/claude-code.asc`, so no key is downloaded at build time. -- The APT source is written to `/etc/apt/sources.list.d/claude-code.list`. Set - `keepAptSource` to `false` to drop the source and key once installation - finishes. +- A Debian or Ubuntu based image: the package is installed with `apt-get`, and the + install script stops early on an image without it. -### VS Code extension +## Usage -The feature also requests the Claude Code VS Code extension, so an editor that -attaches to the container gets the sidebar, inline diffs and the IDE -integration alongside the CLI: +The defaults install the newest package and leave the APT source in place. To drop +the source and its key once the package is installed: + +```json +"features": { + "ghcr.io/bare-devcontainer/features/claude-code:1": { + "keepAptSource": false + } +} +``` + +`version` takes an APT package version rather than a release name, so read the exact +string off `apt-cache policy claude-code` in a container that has the source +configured before pinning to one. + +## Installed software + +- The `claude` CLI, from Anthropic's APT repository at + `https://downloads.claude.ai/claude-code/apt/stable`. The source is written to + `/etc/apt/sources.list.d/claude-code.list` and the signing key to + `/usr/share/keyrings/claude-code.asc`. +- The Claude Code VS Code extension (`anthropic.claude-code`), installed by clients + that read `customizations.vscode`. + +To leave the extension out, list it with a minus sign in `devcontainer.json`: ```json "customizations": { "vscode": { "extensions": [ - "anthropic.claude-code" + "-anthropic.claude-code" ] } } ``` -Extension installation is done by the attaching client, not by the install -script, so this applies to VS Code, Cursor and GitHub Codespaces, and is -ignored by clients that do not consume `customizations.vscode` (the -`devcontainer` CLI, or attaching from another editor). The CLI is installed -either way. Nothing in `devcontainer.json` can remove an extension a feature -asks for, so uninstall it in the container if it is not wanted. - -### Persisting configuration and credentials +## Configuration and credentials Claude Code keeps its credentials, settings and history in its configuration -directory. The feature declares a volume mount so those survive container -rebuilds, and points Claude Code at it: +directory. The feature declares a volume mount so those survive container rebuilds, +and points Claude Code at it: ```json "containerEnv": { @@ -110,6 +121,36 @@ projects. Mounts declared by a feature cannot be disabled from `devcontainer.json`, but overriding `CLAUDE_CONFIG_DIR` as shown above leaves the volume mounted and unused. +## Supply chain + +The package is installed by APT from Anthropic's official repository, and its +signature is checked against the repository signing key. That key is vendored with +the feature and installed from the feature directory to +`/usr/share/keyrings/claude-code.asc`, so nothing is trusted that was not reviewed +in this repository — no key is downloaded at build time. It is refreshed by this +repository's `Update Trusted Material` workflow, which opens a pull request when +upstream publishes a different key. + +To see the key before pinning the feature: + +```sh +gpg --show-keys src/claude-code/claude-code.asc +``` + +`keepAptSource` decides what is left behind. Keeping the source and key, the +default, means `apt-get upgrade` inside the container can pick up newer releases. +Setting it to `false` removes both once the install finishes, so the image carries +no additional APT source. Either way the apt lists downloaded during installation +are deleted. + +## Tips + +- For the tags this feature is published under, see + [Versions and pinning](https://github.com/bare-devcontainer/features#versions-and-pinning). +- The configuration volume is per dev container, so authentication is done once per + project. Point `CLAUDE_CONFIG_DIR` at a mount of your own to share credentials + between projects. + --- diff --git a/src/node/NOTES.md b/src/node/NOTES.md index d9c2136..4763d2a 100644 --- a/src/node/NOTES.md +++ b/src/node/NOTES.md @@ -1,50 +1,64 @@ -## Details +The official build from [nodejs.org](https://nodejs.org/dist/) is unpacked into +`/usr/local`, Corepack is installed from npm, and npm itself is removed afterwards. -This feature installs the same Node.js setup as the -[`ghcr.io/bare-devcontainer/node`](https://github.com/bare-devcontainer/images/tree/main/node) -image, for base images that do not already carry it: the official build from -[nodejs.org](https://nodejs.org/dist/) unpacked into `/usr/local`, Corepack from -npm, and no npm left behind. +## Requirements -- Requires a glibc based `linux-x64` or `linux-arm64` image. -- The download, signature check and unpacking need `wget` (or `curl`), `gpgv`, - `tar`, `xz` and `sha256sum`. Any of those the image is missing are installed - with `apt-get`, so an image without them has to be Debian or Ubuntu based. +- A glibc based `linux-x64` or `linux-arm64` image, since that is the official + build being installed. +- `wget` (or `curl`), `gpgv`, `tar`, `xz`, `sha256sum` and a CA bundle, for the + download, signature check and unpacking. Any of those the image is missing are + installed with `apt-get`, so an image without them has to be Debian or Ubuntu + based; an image that already has them needs no package manager at all. -### Version selection +## Usage -`version` accepts `lts` (the newest long-term support release), `latest` (the -newest release of any line), a release line such as `24` or `24.19`, or an exact -version such as `24.19.0`. Everything but an exact version is resolved against -`https://nodejs.org/dist/index.json` at install time, so a rebuild picks up -newer patch releases. - -### Corepack instead of npm - -npm and npx are removed after Corepack is installed, so the package manager -comes from the project's `packageManager` field rather than from the image: +The defaults install the newest long-term support release with the newest Corepack, +and remove npm. To pin a release line and keep npm: ```json "features": { - "ghcr.io/bare-devcontainer/features/node:1": {} + "ghcr.io/bare-devcontainer/features/node:1": { + "version": "24", + "keepNpm": true + } } ``` -Corepack is installed but not enabled, matching the image. Its shims go into -`/usr/local/bin`, so enabling them needs root — a `RUN corepack enable` in a -`Dockerfile` layered on the image, for instance. Without root, `corepack enable ---install-directory` writes them somewhere the remote user owns instead. +## Version selection -Set `keepNpm` to `true` to keep npm and npx, or `corepack` to `none` to leave -Corepack out — including the copy that release lines up to Node.js 24 bundle, -which is removed along with it. `corepack: "none"` together with `keepNpm: false` -yields a Node.js with no package manager at all. +`version` accepts `lts` (the newest long-term support release), `latest` (the +newest release of any line), a release line such as `24` or `24.19`, or an exact +version such as `24.19.0`. Everything but an exact version is resolved against +`https://nodejs.org/dist/index.json` at install time, so a rebuild picks up +newer patch releases. + +## Installed software + +- Node.js, unpacked into `/usr/local`, owned by root. +- Corepack, installed globally from npm at the version `corepack` specifies. Corepack downloads package managers into `COREPACK_HOME`, which defaults to `~/.cache/node/corepack`. The feature creates that directory for the remote user so the first `corepack` call does not have to write into a root-owned cache. -### Supply chain +## Not installed + +- **No npm or npx.** Both are removed after Corepack is installed, so the package + manager comes from the project's `packageManager` field rather than from the + image. Set `keepNpm` to `true` to keep them. +- **No enabled package manager.** Corepack is installed but not enabled. Its shims go + into `/usr/local/bin`, so enabling them needs root — a `RUN corepack enable` in a + `Dockerfile` layered on the image, for instance. Without root, + `corepack enable --install-directory` writes them somewhere the remote user owns + instead. +- **No package manager at all**, if you ask for that: `corepack: "none"` leaves + Corepack out — including the copy that release lines up to Node.js 24 bundle, + which is removed along with it — and combined with `keepNpm: false` yields a + Node.js with nothing to install packages with. +- **No global JavaScript tooling.** Linters, formatters and test runners are left to + the project's own dependencies. + +## Supply chain The release tarball is downloaded from `https://nodejs.org/dist/` and its checksum is verified against `SHASUMS256.txt.asc`, signed by the Node.js Release @@ -53,3 +67,28 @@ signatures are checked against keys reviewed in this repository rather than keys fetched at install time. It is a copy of the keyring published by [nodejs/release-keys](https://github.com/nodejs/release-keys), refreshed by this repository's `Update Trusted Material` workflow. + +To see the keys before pinning the feature: + +```sh +gpg --no-default-keyring --keyring "$PWD/src/node/node-keyring.kbx" --list-keys +``` + +Everything downloaded during installation — the tarball, the checksums and npm's +cache — goes to a temporary directory that is removed when the script exits. + +## Tips + +- For the tags this feature is published under, see + [Versions and pinning](https://github.com/bare-devcontainer/features#versions-and-pinning). +- If you enable npm through Corepack, keep its download cache across rebuilds by + adding a named volume to `mounts` in `devcontainer.json`, with the target set to + `.npm` in the remote user's home directory: + + ```json + { + "source": "${devcontainerId}-npm-cache", + "target": "/home/dev/.npm", + "type": "volume" + } + ``` diff --git a/src/node/README.md b/src/node/README.md index cbd324c..c161a23 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -19,53 +19,67 @@ Installs Node.js from nodejs.org, verified against the Node.js release signing k | corepack | npm version specifier of Corepack to install globally, or "none" to leave Corepack out. | string | latest | | keepNpm | Keep the npm and npx executables that ship with the Node.js distribution. When false, both are removed so the package manager comes from the project's "packageManager" field through Corepack. | boolean | false | -## Details +The official build from [nodejs.org](https://nodejs.org/dist/) is unpacked into +`/usr/local`, Corepack is installed from npm, and npm itself is removed afterwards. -This feature installs the same Node.js setup as the -[`ghcr.io/bare-devcontainer/node`](https://github.com/bare-devcontainer/images/tree/main/node) -image, for base images that do not already carry it: the official build from -[nodejs.org](https://nodejs.org/dist/) unpacked into `/usr/local`, Corepack from -npm, and no npm left behind. +## Requirements -- Requires a glibc based `linux-x64` or `linux-arm64` image. -- The download, signature check and unpacking need `wget` (or `curl`), `gpgv`, - `tar`, `xz` and `sha256sum`. Any of those the image is missing are installed - with `apt-get`, so an image without them has to be Debian or Ubuntu based. +- A glibc based `linux-x64` or `linux-arm64` image, since that is the official + build being installed. +- `wget` (or `curl`), `gpgv`, `tar`, `xz`, `sha256sum` and a CA bundle, for the + download, signature check and unpacking. Any of those the image is missing are + installed with `apt-get`, so an image without them has to be Debian or Ubuntu + based; an image that already has them needs no package manager at all. -### Version selection +## Usage -`version` accepts `lts` (the newest long-term support release), `latest` (the -newest release of any line), a release line such as `24` or `24.19`, or an exact -version such as `24.19.0`. Everything but an exact version is resolved against -`https://nodejs.org/dist/index.json` at install time, so a rebuild picks up -newer patch releases. - -### Corepack instead of npm - -npm and npx are removed after Corepack is installed, so the package manager -comes from the project's `packageManager` field rather than from the image: +The defaults install the newest long-term support release with the newest Corepack, +and remove npm. To pin a release line and keep npm: ```json "features": { - "ghcr.io/bare-devcontainer/features/node:1": {} + "ghcr.io/bare-devcontainer/features/node:1": { + "version": "24", + "keepNpm": true + } } ``` -Corepack is installed but not enabled, matching the image. Its shims go into -`/usr/local/bin`, so enabling them needs root — a `RUN corepack enable` in a -`Dockerfile` layered on the image, for instance. Without root, `corepack enable ---install-directory` writes them somewhere the remote user owns instead. +## Version selection + +`version` accepts `lts` (the newest long-term support release), `latest` (the +newest release of any line), a release line such as `24` or `24.19`, or an exact +version such as `24.19.0`. Everything but an exact version is resolved against +`https://nodejs.org/dist/index.json` at install time, so a rebuild picks up +newer patch releases. + +## Installed software -Set `keepNpm` to `true` to keep npm and npx, or `corepack` to `none` to leave -Corepack out — including the copy that release lines up to Node.js 24 bundle, -which is removed along with it. `corepack: "none"` together with `keepNpm: false` -yields a Node.js with no package manager at all. +- Node.js, unpacked into `/usr/local`, owned by root. +- Corepack, installed globally from npm at the version `corepack` specifies. Corepack downloads package managers into `COREPACK_HOME`, which defaults to `~/.cache/node/corepack`. The feature creates that directory for the remote user so the first `corepack` call does not have to write into a root-owned cache. -### Supply chain +## Not installed + +- **No npm or npx.** Both are removed after Corepack is installed, so the package + manager comes from the project's `packageManager` field rather than from the + image. Set `keepNpm` to `true` to keep them. +- **No enabled package manager.** Corepack is installed but not enabled. Its shims go + into `/usr/local/bin`, so enabling them needs root — a `RUN corepack enable` in a + `Dockerfile` layered on the image, for instance. Without root, + `corepack enable --install-directory` writes them somewhere the remote user owns + instead. +- **No package manager at all**, if you ask for that: `corepack: "none"` leaves + Corepack out — including the copy that release lines up to Node.js 24 bundle, + which is removed along with it — and combined with `keepNpm: false` yields a + Node.js with nothing to install packages with. +- **No global JavaScript tooling.** Linters, formatters and test runners are left to + the project's own dependencies. + +## Supply chain The release tarball is downloaded from `https://nodejs.org/dist/` and its checksum is verified against `SHASUMS256.txt.asc`, signed by the Node.js Release @@ -75,6 +89,31 @@ fetched at install time. It is a copy of the keyring published by [nodejs/release-keys](https://github.com/nodejs/release-keys), refreshed by this repository's `Update Trusted Material` workflow. +To see the keys before pinning the feature: + +```sh +gpg --no-default-keyring --keyring "$PWD/src/node/node-keyring.kbx" --list-keys +``` + +Everything downloaded during installation — the tarball, the checksums and npm's +cache — goes to a temporary directory that is removed when the script exits. + +## Tips + +- For the tags this feature is published under, see + [Versions and pinning](https://github.com/bare-devcontainer/features#versions-and-pinning). +- If you enable npm through Corepack, keep its download cache across rebuilds by + adding a named volume to `mounts` in `devcontainer.json`, with the target set to + `.npm` in the remote user's home directory: + + ```json + { + "source": "${devcontainerId}-npm-cache", + "target": "/home/dev/.npm", + "type": "volume" + } + ``` + ---