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" + } + ``` + ---