Installs @devcontainers/cli as /usr/local/bin/devcontainer, backed by a private, sha256-verified Node.js under /opt/devcontainer-cli that is NOT added to PATH - so devcontainer up/build/exec/features test work inside a container without a system-wide Node.js. Pair with docker-in-docker (needed for features test and nested up on Windows/macOS hosts) or docker-outside-of-docker (Linux hosts, same paths).
"features": {
"ghcr.io/nabil-becker/devcontainer-features/devcontainer-cli:1": {}
}| Options Id | Description | Type | Default Value |
|---|---|---|---|
| installTaskfile | Also install this Feature's Taskfile into /usr/local/share/go-task/includes.d so its tasks appear under the : namespace of a root Taskfile that includes /usr/local/share/go-task/features.yml (see the go-task Feature). | boolean | true |
| version | @devcontainers/cli npm version to install (e.g. '0.89.0'), or 'latest'. | string | latest |
| nodeVersion | Node.js release used privately by the CLI. Downloaded from nodejs.org and verified against its SHASUMS256.txt. | string | 24.21.0 |
- FUSE is not granted on wslc.
wslc run(WSL Containers 3.0.3) has no--device,--cap-addor--security-optoption and refused the whole start ("Option name was not recognized for the current command: '--device=/dev/fuse'") whenDCF_HOST_FUSE=1filled therunArgs. On thewindows-wslchost type, or withDEVCONTAINER_DOCKER_PATHnamingwslc, the launcher now withholds the grant: the threeDCF_HOST_FUSE_*variables stay unset (therunArgsfall back to their labels),DCF_HOST_FUSEreaches the container empty,upwarns once with the reason, and the container starts without FUSE, so the cloud-folder Feature'sautomode takes sync.task devcontainer:hostprintsDCF_HOST_FUSE : 1 asked for, not granted on windows-wslc: .... Docker Desktop and the other engines are unchanged. UnsetDCF_HOST_FUSEin.devcontainer/.envon a wslc machine to drop the warning.
DCF_HOST_MACHINE: the launcher sets the workstation's machine name forupandrebuild, like the otherDCF_HOST_*values, so agents' session names can say which machine they run on. PowerShell reads$env:COMPUTERNAMEon Windows (else the host name before its first dot); bash readshostname -s(elseuname -nbefore its first dot). The name is cut to a safe set: letters, digits, dot, underscore and hyphen; any other character becomes a hyphen, runs collapse, no leading or trailing hyphen or dot, at most 63 characters. It is always the launcher's own value, never one inherited from the environment. The bootstrap'sdevcontainer.jsonpasses it on with"DCF_HOST_MACHINE": "${localEnv:DCF_HOST_MACHINE}".upandhostwarn once (never fail) when the repository'sdevcontainer.jsonlacks that line, as for the FUSE slots; add the line to thecontainerEnvof an existing repository. Empty means the container renderscontainer.
task devcontainer:lockandtask devcontainer:outdated(aliasesdc:lock,dc:outdated): update the lock file to the requested Feature versions, or list what is behind, instead of deleting the lock file by hand.
task cloud-folder:loginfrom the workstation. A new host command,run cloud-folder-login(bash) andInvoke-CloudFolderLogin(PowerShell), behindtask devcontainer:cloud-folder-loginand, when the root Taskfile includes the vendoredcloud-folder.yml(the bootstrap's template does),task cloud-folder:login: the cloud-folder Feature's (1.3.0) sign-in through the running devcontainer. See "Cloud-folder sign-in" below. The bootstrap vendorscloud-folder.ymland prints the include lines for a Taskfile that lacks them. Nothing changes for the other tasks; the PowerShell module gainsGet-DevcontainerCliInvocation, the argument list every CLI call already built, now shared with the login.
The @devcontainers/cli npm package, run on a private Node.js build, so
devcontainer up, build, exec and features test work inside a
container with no system-wide Node.js. install.sh runs
devcontainer-cli.sh install, which:
- Downloads
node-v<nodeVersion>-linux-<arch>.tar.gzandSHASUMS256.txtfromhttps://nodejs.org/dist/v<nodeVersion>/, checks the tarball's SHA-256 against that file, and unpacks it to/opt/devcontainer-cli/node. Both downloads go throughdcf_curl(dcf-curl.sh, the collection's download helper, vendored fromcommon), which retries every error on curl 7.71+, a TLS reset or a connection cut mid-transfer included. - Runs
npm install -g --prefix /opt/devcontainer-cli @devcontainers/cli(with@<version>unlessversionislatest), using that Node. - Writes a two-line wrapper at
/usr/local/bin/devcontainerthat runs the CLI with the privatenode, then runsdevcontainer --versionso the version is in the build log.
node and npm are deliberately not on PATH afterwards. Only the
devcontainer wrapper knows about them, so the Feature cannot collide with
the Node.js toolchain your project pins. It installs no Docker engine
(see Composition). The directory /opt/devcontainer-cli is removed and
rebuilt on every install, and npm's cache goes to a temporary directory that
is deleted, so it does not stay in the image layer.
What is verified, and what is not:
- The private Node.js is verified: a missing line in
SHASUMS256.txtor a mismatch fails the build. - The CLI itself is NOT checked against a checksum. It comes from
npm install -gand is protected only by npm's own integrity metadata for the package and its dependencies. This Feature adds no checksum, no signature check and no lockfile for it. The root README names this as the one exception to "verifies what it downloads".
curl, ca-certificates and tar are installed with apt-get only when
missing. The install must run as root.
version(defaultlatest): the@devcontainers/clinpm version, for example0.89.0; no leadingv.latestis whatever npm calls latest at image build time (not thereleases/latestredirect the other installers use), so two builds a month apart can differ. Pin it when you wantdevcontainer features testorupto behave the same in CI and on every laptop, or when a new CLI release breaks something.nodeVersion(default24.21.0): the Node.js release the CLI runs on, asx.y.zwithoutv. Change it only when a pinned CLI version needs a newer or older Node than the default (the CLI states its Node range), or when nodejs.org no longer serves the default. It does not change the Node.js your project uses; that one is separate.installTaskfile(defaulttrue): set it tofalsewhen you do not use the go-task registry and do not want/usr/local/share/go-task/includes.d/devcontainer-cli.ymlin the image.
There is no run-time override; both versions are fixed at build.
No state: installs binaries only. No volume, no containerEnv, no
post-create hook.
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/nabil-becker/devcontainer-features/devcontainer-cli:1": {
"version": "0.89.0"
}
}Drop version to follow npm's latest at each image build.
devcontainer --version # prints the CLI version, for example 0.89.0
task devcontainer-cli:versionThe build log ends the install with the same devcontainer --version
line. command -v node finds nothing, on purpose. There is no post-create
line: nothing runs after the build.
installsAfterorders it behind this collection'sgo-taskFeature (so the Taskfile registry exists and is refreshed), the officialcommon-utils, and the two official Docker Features (docker-in-docker,docker-outside-of-docker) so a Docker engine is in place before it when you use one.- It needs a Docker daemon for anything that builds or runs containers
(
up,build,features test);read-configurationand--versiondo not. It does not install one. Pair it with one of the official Features:
| Host | Use | Why |
|---|---|---|
| Windows / macOS (Docker Desktop) | docker-in-docker |
features test and nested devcontainer up bind-mount paths from inside this container; only a daemon that shares its filesystem can resolve them. |
| Linux (same paths on host and container) | docker-outside-of-docker |
Lighter, shares the host's image cache; works while the workspace is mounted at the same absolute path. |
- It augments no official Feature and replaces none. It is independent of
kube-state,gitand the other Features here.
With installTaskfile on, and a root Taskfile that includes the go-task
registry, these appear under devcontainer-cli::
devcontainer-cli:version: show the installed CLI version.devcontainer-cli:config: print the resolved and merged configuration ofWORKSPACE(default.) withdevcontainer read-configuration.devcontainer-cli:features:test: rundevcontainer features testfor the Features repository atPROJECT(default.); extra flags go after--(for example-f <id>). Needs a Docker daemon.
These are for using the CLI inside the container. To drive a devcontainer
from a host, use the devcontainer:* tasks (next section).
- Linux only in the container. The architectures are
x86_64/amd64,aarch64/arm64andarmv7l; any otheruname -mfails withunsupported architecture. ... not found in SHASUMS256.txtorSHA-256 mismatch: thenodeVersiondoes not exist for your architecture, or the download was corrupted. Fix the version or retry. The Node.js is never unpacked unchecked.- A failing
npm install(no network, or aversionthat npm does not have) fails the build. Check the version withnpm view @devcontainers/cli versionson a machine that has npm. features testand nestedupfail with a Docker error when no daemon is reachable; adddocker-in-docker(ordocker-outside-of-dockeron Linux). Withdocker-outside-of-docker, a workspace mounted at a different path on the host than inside the container cannot be resolved.- The CLI's own dependencies are resolved by npm at build time and are not locked here, so supply-chain trust in them is npm's, not this Feature's.
The same devcontainer-cli.sh that installs this Feature also has init
and run modes for a Linux/macOS host, where it caches a portable Node.js
under ~/.cache/devcontainer-features (or just uses a devcontainer already
on PATH when the version is latest). Together with the PowerShell flavour
for Windows it backs the task devcontainer:* tasks that
bootstrap/bootstrap.ps1 / bootstrap.sh vendor into a repo's
.devcontainer/cli_bootstrap.ignore/ (with dcf-curl.sh, its download helper,
beside it in host/; git-ignored; BOOTSTRAP_DIR picks another
repo-relative folder, BOOTSTRAP_GITIGNORE=false keeps it committable, and the
root Taskfile include is optional: true: a fresh clone runs the bootstrap
first; the old ./bootstrap/ form keeps working until the bootstrap is re-run,
see the README); nothing is
installed globally on any host. Its host settings are
DEVCONTAINER_DOCKER_PATH, DEVCONTAINER_NODE_VERSION,
DEVCONTAINER_CLI_VERSION, HOST_TYPE, HOST_TYPE_PRIORITY and HOST_MOUNTS (see "Host type" and "Host mounts"); the cloud-folder sign-in reads CLOUD_FOLDER_LOGIN_TIMEOUT (see "Cloud-folder sign-in").
task devcontainer:up (alias start) and task devcontainer:stop (alias
down) are idempotent, so other tasks can require them. up starts a stopped
container and leaves a running one alone. exec, shell, pwsh, agent and claude
run up first; rebuild runs stop first (shutdown check included; FORCE=1 and
STOP_CHECK pass through) and then recreates the container; a stop that fails
(the check refuses, or any error) stops the rebuild too, so FORCE=1 is the
one way to rebuild past a refusing check, as for stop.
build, config, lock and outdated touch no container. task devcontainer:lock rewrites
.devcontainer/devcontainer-lock.json to the Feature versions devcontainer.json asks for
(devcontainer upgrade): run it after changing a Feature version, then commit the lock file;
task devcontainer:outdated lists what is behind. Features referenced by a local path are never
locked.
One build at a time. up (so rebuild too) and build hold a host-wide lock
while they run: the named mutex Global\devcontainer-features-build on Windows,
an flock on ~/.cache/devcontainer-features/build.lock on Linux/macOS (none
on a host without flock, such as stock macOS). Why: when the engine has no
BuildKit build context (wslc), @devcontainers/cli tags the Feature content
image with the fixed name dev_container_feature_content_temp for every
repository, so two builds at once overwrite each other's image and the first
fails with "/tmp/build-features/<feature>_N": not found. A second build
prints waiting for another devcontainer build to finish... and then runs;
the lock is released when the build ends or its process dies (an abandoned lock
counts as free). It applies on every engine, harmless with Docker Desktop;
exec, run-user-commands, set-up and the other subcommands take no lock,
and devcontainer features test takes it only with
DEVCONTAINER_BUILD_LOCK_FEATURES_TEST=true (default false, so a long test run
does not hold up every rebuild). DEVCONTAINER_BUILD_LOCK=0 turns the lock off.
stop is the one host task that is not a CLI subcommand (the CLI has none),
and it is one host-script command, the same in run stop and
Stop-Devcontainer (running? check? stop?), not nested tasks. The workspace's
container is found by the devcontainer.local_folder label that devcontainer up sets, through the configured engine (DEVCONTAINER_DOCKER_PATH). The
engine's label filter is case-sensitive and the CLI writes a lowercase drive
letter (c:\...) where the task has C:\..., so the lookup tries the folder as
given, then with the drive letter lowercased, then uppercased (only for an X:\
path). With no running container stop prints "already stopped" and succeeds,
without running the check and without starting anything. Otherwise it runs the
STOP_CHECK command inside the container (task var, environment variable or
.devcontainer/.env) through the same exec path, without up, from the
workspace folder (the CLI's exec starts in the remote workspace folder), and
when the check fails it prints "Not stopping: ..." and exits with the check's
code, before stopping anything; exit 127 from it prints that the check is not
installed in the container. FORCE=1 skips the check. With STOP_CHECK unset
the task passes one resolution snippet as the command: the repository's
task shutdown:check when its Taskfile defines it, else the devhost Feature's
devhost-shutdown-check (ADR 0015), else the line "no shutdown check defined;
stopping" and exit 0, so a repository without either stops without a check
(bootstrap/SHUTDOWN.md in the collection is the contract). prepshutdown
(devcontainer:prepshutdown) is the same command without the stop: it runs the
check and exits with its code, or says there is no container and succeeds. The engine's ps --filter label=... and
stop are assumed to behave as Docker's; this is not tested against wslc or
Podman. Stopping the container any other way is not guarded.
run running (and Get-RunningDevcontainerId) is the same lookup as a silent
test: exit 0 when a container runs, 1 when none does, 2 when the engine cannot
be asked. Task reports a failed command as 201 itself; task --exit-code devcontainer:running passes the script's 0/1/2 through.
A failing exec (what exec, shell, pwsh and claude run) asks the same
lookup which of three things failed and says so, instead of one guess: the
container runs, so the command failed (The command exited with code <n> inside the devcontainer: <command>, exit with that code); none runs (No running devcontainer for <folder>. Start it with: task dc:start, exit 1); or the
engine could not be reached (its own error plus the container engine (<path>) could not be reached, exit 2). Other subcommands keep their generic failure
message.
task cloud-folder:login (or task devcontainer:cloud-folder-login) on the workstation signs the
cloud-folder Feature's remote in. rclone's sign-in sends the browser back to the fixed
http://localhost:53682/ (its app registration allows nothing else), where rclone listens inside
the container; the workstation's localhost:53682 is not the container's. So, by default, the
launcher:
- finds the running container by its
devcontainer.local_folderlabel (none:task dc:start); - opens a listener on
127.0.0.1:53682on the workstation, for this sign-in only (a python3 program in the bash twin, aTcpListenerin PowerShell); - starts
cloud-folder-setup login --relayin the container throughdevcontainer exec, which runsrclone authorize "onedrive" --auth-no-open-browserthere and prints its link; - opens the default browser at that link (
DCF_BROWSERnames another command; without an opener the address is printed); - relays each request of the browser into the container:
<engine> exec -i <id> bash -copens/dev/tcp/127.0.0.1/53682there, writes the request (made HTTP/1.0 withConnection: close) and copies rclone's answer back. No port mapping, no socat, nothing indevcontainer.json; - closes the listener when the login ends, fails, or after
CLOUD_FOLDER_LOGIN_TIMEOUTseconds (default 600), and says so.
When 53682 is taken on the workstation, or (bash twin) there is no python3, it says so and runs
the container's login on this terminal instead: paste back the address the browser fails to open
(-- --paste chooses that). -- --workstation-rclone signs in with rclone on the workstation
instead: the version is the repository's cloud-folder rcloneVersion in devcontainer.json, else
the Feature's default (both twins carry it; a test keeps it equal), latest asks the publisher;
the release zip is downloaded once into the launcher cache (%LOCALAPPDATA%\devcontainer-features\rclone,
~/.cache/devcontainer-features/rclone) with the retrying download helper, checked against the
release's SHA256SUMS (saved to a file and read as text on Windows), and its rclone authorize "onedrive" opens the browser itself; the token goes to cloud-folder-setup login --token in the
container on stdin, never as an argument, and is never printed. Not verified on a real host: the
relay through Docker Desktop, wslc exec -i and a WSL distro's localhost forwarding.
The launcher (the PowerShell flavour and the bash twin, the same rules in both)
knows what kind of host it runs on, so a repository writes nothing host-specific.
HOST_TYPE names it; the design is in docs/proposals/host-profile.md and ADR 0024.
HOST_TYPE |
Detected by | Engine | DCF_HOST_HOME |
|---|---|---|---|
windows-docker |
launcher on Windows; docker on PATH answers docker info |
docker |
%USERPROFILE% |
windows-wsl-docker |
inside a WSL distro (WSL_DISTRO_NAME, or /proc/version names Microsoft); or, from Windows, wsl -e docker info answers |
docker |
the distro's $HOME (asked through wsl.exe from Windows) |
windows-wslc |
launcher on Windows; wslc.exe on PATH or in C:\Program Files\WSL |
wslc |
%USERPROFILE% |
linux |
uname -s is Linux and not WSL |
docker |
$HOME |
macos |
uname -s is Darwin |
docker |
$HOME |
HOST_TYPEdefaults toauto; an explicit id skips detection (and must fit the launcher's OS: the bash twin runs on Linux, macOS and inside WSL, so it takeslinux,macosandwindows-wsl-docker; the Windows ids belong to the PowerShell launcher). An unknown id is an error that lists the valid ones.HOST_TYPE_PRIORITYis the ordered, comma-separated listautowalks, taking the first id whose detection passes. Default per OS the launcher runs on: Windowswindows-docker,windows-wsl-docker,windows-wslc; inside a WSL distrowindows-wsl-docker; Linuxlinux; macOSmacos. An organisation that wants Docker in WSL first writesHOST_TYPE_PRIORITY=windows-wsl-docker,windows-docker,windows-wslcin.devcontainer/.env.DEVCONTAINER_DOCKER_PATHstill names the engine and always wins, whatever the host type (podman, a full path). In the PowerShell launcher, withHOST_TYPE=auto, nothing passing andDEVCONTAINER_DOCKER_PATHset, the first id of the priority list that fits the OS is taken, because the engine is given. Unset, the engine follows the host type, so on Windowsdockerthat is on PATH but whose daemon is down no longer wins over an installedwslc.exe.- Before it runs the CLI (
up,build,execand the other subcommands that take--docker-path) the launcher setsDCF_HOST_TYPE(the resolved id),DCF_HOST_HOME(the host user's home),DCF_HOST_SSH_AGENT(socket,noneorkey, see "The SSH agent" below),DCF_HOST_CLOUD_FOLDER(the user's own value, passed on unchanged, see "The cloud folder" below) andDCF_HOST_MACHINE(the machine name, see "Changes in 1.16.2") in its own environment. A repository maps them into the container itself withcontainerEnv,"DCF_HOST_TYPE": "${localEnv:DCF_HOST_TYPE}"and so on (the bootstrap'sdevcontainer.jsontemplate does). It declares no host mount: the launcher adds those (next section). Opening the folder straight in VS Code skips the launcher: the variables are then empty. task devcontainer:hostprints the result without starting anything:HOST_TYPEas resolved and why, the priority list used, the engine command, theDCF_HOST_*values (with the machine-line hint),, the FUSE grant whenDCF_HOST_FUSEasks for it, and the host mountsupwould add or skip (next section).HOST_TYPE=<id>orHOST_TYPE_PRIORITY=<ids>on the command line try other choices.
Not verified on real hosts: wsl -e docker info answering for the default distro; the WSL markers on every WSL version
(a container on a WSL 2 kernel also reads as WSL); windows-wsl-docker driven from Windows, where --docker-path takes
one executable, so it needs a docker on PATH that reaches the distro or DEVCONTAINER_DOCKER_PATH; and Docker
Desktop's context. The evidence is replaceable: the bash twin reads DCF_HOST_UNAME and DCF_HOST_PROC_VERSION
instead of uname -s and /proc/version, and an explicit HOST_TYPE skips detection altogether.
A repository should need no host-specific mount line. devcontainer up takes --mount type=bind,source=<host path>,target=<container path> (repeatable), so the launcher (both flavours) adds the mounts
the host type needs; ADR 0024 decides it, docs/proposals/host-profile.md is the design.
-
What is added. Two entries. The first, for every host type: the host home's
.git-dirsfolder at/git-dirs(source<DCF_HOST_HOME>/.git-dirs:%USERPROFILE%/.git-dirson Windows, in the same mixed-slash form${localEnv:USERPROFILE}/.git-dirshas always produced; the distro's$HOME/.git-dirsforwindows-wsl-docker;$HOMEelsewhere). It is what the git Feature's wrapper maps a.gitpointer onto, so a checkout whose git dir lives outside the checkout (OneDrive) works without a line indevcontainer.json. The list is one function per launcher (Get-HostMountinDevcontainerCli.psm1,host_mount_specsindevcontainer-cli.sh), keyed by host type. The second is the SSH agent socket where the host type has one (next section). -
The SSH agent (slice 4, devcontainer-cli 1.8.0). The launcher bind-mounts the host's agent socket at
/var/lib/devcontainer-features/ssh-agent/agent.sock, the fixed path of thessh-agentFeature'sSSH_AUTH_SOCK, and setsDCF_HOST_SSH_AGENTto say what it did:socketwhen it adds the mount,noneotherwise,keywhenSSH_AGENT_MODE=key(the user's opt-in to a key kept in the container, which also suppresses the mount).Host type Source mounted DCF_HOST_SSH_AGENTlinux$SSH_AUTH_SOCKof the launcher, only when set and a socketsocket, elsenonemacos/run/host-services/ssh-auth.sock(Docker Desktop's, inside its VM; cannot be checked from the host)socketwindows-wsl-dockerthe distro's $SSH_AUTH_SOCK, only when the launcher (bash twin) runs inside the distrosocket, elsenone; alwaysnonefrom Windowswindows-docker,windows-wslcnone: the Windows agent is a named pipe and a relay would be a process left running on the host noneThe mount's source is a socket, so the launcher never creates it as a folder. It is skipped like the others when the repository's
devcontainer.jsonalready mounts that target, andHOST_MOUNTS=falseremoves it. Loading keys:task devcontainer:ssh-add -- <key>runs the host'sssh-addonce (no arguments:-l), for the host agent that socket mode forwards; on Windows it says the container cannot reach that agent.task devcontainer:ssh-key-import FILE=<private key on the host>is the opt-in key mode's import: the file goes to the running container throughdevcontainer execon stdin (never an argument), into the ssh-agent Feature's key volume with mode 0600 and its.pubbeside it, then the Feature loads it; it refuses when the container is not running, key mode is off there, or FILE is missing, and prints the fingerprint, never the key. Since 1.10.0 it also warns, on the host and never blocking, when FILE has a default name (id_ed25519and the like) or its public key (FILE.pub, or derived withssh-keygen -yonly when the key has no passphrase) is also in~/.ssh/authorized_keysor the same as another~/.ssh/*.pub: such a key is general-purpose and should not live in a container volume (it prints fingerprints, never key material). Both are one-shot commands; nothing stays running on the host. Not verified on a real host:devcontainer execforwarding its stdin on every platform (the tests use a stub). -
The cloud folder (1.12.0). When
DCF_HOST_CLOUD_FOLDERnames a host folder (one a sync client keeps, such as a SharePoint or OneDrive folder),upbind-mounts it at one fixed path,/mnt/cloud-folder-host, thecloud-folderFeature's host mount (a constant in both twins and in the Feature). The Feature links itsmountPointoption to that path at build time, so a consumer names the path in the container once, in the option, and the launcher needs no target setting. The variable is a setting like the others: a Task var,.devcontainer/.envor the environment (PowerShell:-CloudFolder); the path is shell-quoted on its way, so spaces and quotes in a library's name survive. Unset means no folder and no mount. The folder is the user's, so it is never created: a path this launcher can check (a Windows path from Windows, an absolute path elsewhere) that is not a folder is skipped with one warning and the container starts without it (the Feature'stask cloud-folder:statusthen fails); another form, such as a Windows path seen from Linux, goes to the engine as is. A source with a comma is skipped the same way.HOST_MOUNTS=falseand a repository line for the same target suppress it like the others.DCF_HOST_CLOUD_FOLDERitself is passed on to the CLI unchanged, socontainerEnvcan map it (the bootstrap's template does) and the Feature's report can name the source. Not verified on a real host: Docker Desktop and wslc taking a Windows path with spaces from--mount. -
FUSE on request (1.15.0). The
cloud-folderFeature'smountmode (rclone mount) needs/dev/fuse,CAP_SYS_ADMINand an AppArmor profile that allows mount. The launcher grants them per machine, only when the project asks, never by default (ADR 0031): withDCF_HOST_FUSE=1(ortrue,yes,on; a Task var,.devcontainer/.envor the environment; PowerShell-Fuse) it setsDCF_HOST_FUSE_DEVICE=--device=/dev/fuse,DCF_HOST_FUSE_CAP=--cap-add=SYS_ADMINandDCF_HOST_FUSE_SECURITY=--security-opt=apparmor=unconfinedbefore it runs the CLI, and passesDCF_HOST_FUSE=1on forcontainerEnv. Unset, the three are removed from the CLI's environment, so a value left in a shell or.envnever reaches the container.devcontainer uphas no option for run arguments and a Feature'scapAdd/securityOptwould reach every consumer, so the repository'srunArgsread the three with a harmless default,"${localEnv:DCF_HOST_FUSE_DEVICE:--label=dcf.fuse.device=off}"and so on (the bootstrap'sdevcontainer.jsoncarries the three lines; checked against@devcontainers/cli0.90.0'sread-configuration).upandtask devcontainer:hostwarn once when FUSE is asked for and the repository'sdevcontainer.jsonlacks a line. A change takes effect at the nexttask devcontainer:rebuild(run arguments belong to the container). Not onwindows-wslc(1.16.3):wslc runhas no--device,--cap-addor--security-optoption and refuses a start that carries one, so there the launcher withholds the grant, warns once with the reason and starts the container without FUSE (autotakes sync);hostprints the refusal. Not verified on a real host: Docker Desktop and a Kubernetes cluster taking the three arguments (proposal section 11, item 6). -
Downloads retry (1.14.1). The Node.js tarball and
SHASUMS256.txtgo throughdcf_curl(dcf-curl.sh, vendored fromcommon, and vendored by the bootstrap next tohost/devcontainer-cli.sh): a TLS reset or a cut connection is retried on curl 7.71+. A host whose bootstrap folder predates it re-runs the bootstrap; withoutdcf-curl.shbeside it the script stops with that hint when it has to download Node.js, and every other verb works. -
Reset the agents (1.14.0).
task devcontainer:agent:reset -- <name>... | --all [--purge] [--include-unlisted] [--yes] [--force]runs theagentsFeature's reset in the container (agents 1.0.1; a dry run unless--yes), the same way asdevcontainer:agent;AGENTS_FILE=<path>before the--picks another list. -
One verb for every agent (1.13.0).
task devcontainer:agent -- <name> [--resume|--new] [extra CLI args]runs theagentsFeature's start verb in the container (agents start, throughexecwith a terminal, starting the container first; directly when already inside), andtask devcontainer:agent:list [-- --json]its list.AGENTS_FILE=<path>on the command line (before the--) picks another agent list; it reaches the container asAGENTS_FILE_TASK_VAR, and theagentscommand resolves it against.envand the environment there. Taskfile tasks only: neither launcher twin changes.devcontainer:claudeanddevcontainer:claude:fleetstill work and print one line saying they are deprecated and go with claude-code 2.0; the migration is indocs/migrations/agents.mdof the collection. -
Fleet waits, reports and stops duplicates (1.11.0).
task devcontainer:claude:fleet -- --no-wait|--stop-duplicatesreachclaude-fleet(claude-code 1.6.0); the task description says so. -
Several Claude sessions (1.9.0).
task devcontainer:claude:fleetpassesSESSIONS(set by the including Taskfile) and its arguments to the claude-code Feature'sclaude-fleetin the container (throughexec, or directly inside); see that Feature's notes.devcontainer:claudeitself gains nothing: itsclaude-hostnow refuses to start a session already running in the folder (and says when the pinned id is stale). -
Which subcommands.
uponly, and sorebuildandstart.buildmounts nothing (it builds an image, no container),exec,shell,pwshandclauderun in a container that exists, and the other subcommands take no mounts. They add nothing to the argument list. -
The source folder exists. Before
upthe launcher creates<host home>/.git-dirswhen missing (idempotent), so a start never fails on a missing bind source. (devcontainer:homesstill does the same for a repository that opens the folder in VS Code directly.) Forwindows-wsl-dockerdriven from Windows the folder is a distro path Windows cannot create; there the distro's owndevcontainer:homes, ormkdir -p, applies. A home with a comma in it cannot be carried by--mount(it separates the fields): the mount is skipped with a warning. -
No duplicate mount. When the repository's
devcontainer.jsonalready mounts a target the launcher would add (its own/git-dirsline, as this collection's own does, because opening it from VS Code bypasses the launcher), the launcher adds nothing for that target and prints one line,host mount skipped: /git-dirs is already mounted by this repository's devcontainer.json. So keeping the line is harmless, and a repository can drop it once it starts through the tasks. -
How "already mounted" is known. From the file, not from the CLI: the launcher strips the comments of
--config <file>, else.devcontainer/devcontainer.json, else.devcontainer.json, and reads the targets of themountsarray (thesource=...,target=/xstrings and objects with"target"; a trailing slash does not matter; no JSON parser, so trailing commas and//inside strings are fine). Measured: about 8 to 10 ms in either flavour. The alternative,devcontainer read-configuration, costs a second Node start (0.23 to 0.47 s here) and asks the engine (docker ps), so it fails when the engine is down, whichtask devcontainer:hostmust survive. The limit: only the repository's own file is read; mounts that Features declare, docker-compose services and a--mountthe engineer passes themselves are not seen (none of them declares the host mounts). -
Off.
HOST_MOUNTS=false(also0,no) as a Task var,.devcontainer/.envvalue or environment variable (PowerShell:-HostMounts) turns all of the launcher's mounts off; the repository then declares what it needs. -
See it.
task devcontainer:hostlistshost mount (add)andhost mount (skip)lines (oroff) after theDCF_HOST_*values, and starts nothing and creates nothing. -
Not covered. A container started by VS Code's Reopen in Container never passes through the launcher, so it gets the repository's own line only; there
${localEnv:DCF_HOST_HOME}is empty and the line must use${localEnv:USERPROFILE}(Windows host) or${localEnv:HOME}.
The Dev Container CLI is published by the Dev Containers project as an npm
package, and its documented install is npm install -g @devcontainers/cli,
which needs a Node.js on PATH. This repository knows of no published Dev
Container Feature that installs it, so there is none to compare. This Feature
exists so that a container can run the CLI without a system-wide Node.js, and
so the same script serves the container and the host-side tasks.
Note: This file was auto-generated from the devcontainer-feature.json. Add additional notes to a NOTES.md.