This guide covers everything you need to set up a development environment, build, test, and submit code changes to the Dev Containers CLI. For the proposal and specification process, see CONTRIBUTING.md.
- Node.js >= 20
- Docker (required for running integration tests — they create real containers)
- Git
- yarn (used for dependency installation)
Fork and clone the repository:
git clone https://github.com/<your-username>/cli.git
cd cliThe repository includes a dev container configuration that provides a ready-to-go environment with Node.js, TypeScript, and Docker-in-Docker pre-configured.
- Open the cloned repository in VS Code.
- When prompted, select Reopen in Container (requires the Dev Containers extension). Alternatively, open the repository in GitHub Codespaces.
- The
postCreateCommandautomatically runsyarn installto install all dependencies.
You are ready to build and test.
-
Install Node.js >= 20 and Docker.
-
Install dependencies:
yarn install
Ensure Docker is running — it is needed for the integration test suite.
Some tests build containers for non-native architectures (e.g.,
linux/arm64on an x64 host, or vice versa). To run these locally, register QEMU emulators:docker run --privileged --rm tonistiigi/binfmt --install all
This is needed once per boot (or per WSL session on Windows). On macOS with Docker Desktop, cross-architecture emulation is built in and this step is not required.
-
(Optional) Install Podman if you want to run the Podman-specific tests. The CLI supports both Docker and Podman as container engines, and the test suite includes a separate set of tests (
cli.podman.test.ts) that verify Podman compatibility using--docker-path podman. These tests will fail withspawn podman ENOENTif Podman is not installed — this is expected and does not indicate a code problem. The CI GitHub workflow runs these tests onubuntu-latestwhere Podman is pre-installed.
The CLI is written in TypeScript and organized as multiple sub-projects using TypeScript project references:
| Sub-project | Path | Purpose |
|---|---|---|
spec-common |
src/spec-common/ |
Shared utilities (async helpers, CLI host, process management, shell server) |
spec-configuration |
src/spec-configuration/ |
Configuration parsing, OCI registry interactions, Features/Templates configuration |
spec-node |
src/spec-node/ |
Core CLI logic — container lifecycle, Docker/Compose integration, Feature utilities |
spec-shutdown |
src/spec-shutdown/ |
Docker CLI wrapper utilities (container inspection, execution, lifecycle management) |
spec-utils |
src/spec-utils/ |
General utilities (logging, HTTP requests, filesystem helpers) |
Key files:
devcontainer.js— Entry point that loads the bundled CLI fromdist/spec-node/devContainersSpecCLI.js.esbuild.js— Build script that bundles the TypeScript output with esbuild.src/test/— Test files and fixture configurations undersrc/test/configs/.
Start the dev build watchers — run these in separate terminals (or use the VS Code build task):
npm run watch # incremental esbuild (rebuilds on save)
npm run type-check-watch # tsc in watch mode (reports type errors)For a one-shot build instead, run npm run compile. To remove all build output, run npm run clean.
After building, invoke the CLI directly:
node devcontainer.js --help
node devcontainer.js up --workspace-folder <path>
node devcontainer.js build --workspace-folder <path>
node devcontainer.js run-user-commands --workspace-folder <path>Tests use Mocha and Chai and require Docker because they create and tear down real containers.
npm test # all tests
npm run test-container-features # Features tests only
npm run test-container-templates # Templates tests only- Place new test files in
src/test/with a.test.tssuffix. - Place test fixture
devcontainer.jsonconfigurations undersrc/test/configs/<your-config-name>/. - Use the helpers in
src/test/testUtils.ts(shellExec,devContainerUp,devContainerDown) for container lifecycle management in tests.
Before committing, run the same checks CI runs:
npm run type-check # full type-check
npm run package # production build (minified) + pack into .tgz
npm run precommit # lint, formatting, copyright headers
npm test # full test suite (may take a very long time to run, consider running a subset of tests during development)Then push your branch and open a pull request against main. Link any related repo issues or specification issues in the PR description.
The repository includes VS Code configuration in .vscode/ for building, debugging, and testing.
The default build task (Ctrl+Shift+B / Cmd+Shift+B) is Build Dev Containers CLI. It runs npm run watch and npm run type-check-watch in parallel so you get both bundled output and type errors as you edit.
Two launch configurations are provided in .vscode/launch.json:
- Launch CLI - up — Runs the CLI's
upcommand againstsrc/test/configs/example/. Edit theargsarray to point at a different config or subcommand. - Launch Tests — Runs the full Mocha test suite under the debugger.
The workspace recommends the ESLint extension for inline lint feedback. The workspace settings (.vscode/settings.json) configure format-on-save, tab indentation, and the workspace TypeScript SDK.
Tests will fail if Docker is not running. Make sure the Docker daemon is started. If using the dev container, Docker-in-Docker is configured automatically.
The node-pty dependency includes native code. If you see build errors during yarn install, ensure you have the required build tools for your platform (e.g., build-essential on Debian/Ubuntu, Xcode Command Line Tools on macOS).
If tests are interrupted, containers may be left running. Single-container tests label their containers with devcontainer.local_folder:
docker rm -f $(docker ps -aq --filter "label=devcontainer.local_folder")Compose-based tests also create sidecar containers (e.g., db services) that don't carry that label. To remove those, filter by the compose config path:
docker rm -f $(docker ps -a --format '{{.ID}} {{.Label "com.docker.compose.project.config_files"}}' | grep src/test/configs | awk '{print $1}')If you don't have Podman installed, cli.podman.test.ts will fail with spawn podman ENOENT. This is safe to ignore — CI will run them. See Local setup for details on installing Podman or skipping these tests.