Skip to content

Commit 71a2b60

Browse files
aledbfclaude
andcommitted
docs: rewrite README for users + document Go-only features
The README was written for contributors (parity methodology, layout, build targets) and never made the case for *using* the binary. Rewrite it around the reader who already runs the official CLI and feels its daily friction: - Lead with a pain/benefit table (static binary vs node_modules, cold start, private-registry auth, host preflight, prebuild reuse, proxy/CA). - Honest install (build-from-source today; prebuilt binaries on Releases once tagged) + a 60-second "hello dev container" quickstart (up + exec). - A "superpowers" section surfacing the Go-only commands, with the parity and contributor material moved below and condensed. Add docs/go-only-features.md: a reference for every Go-only addition/divergence (check, setup, up --cache-image, read-configuration --cache-key, build --secrets-file, the automatic credential bridge, --override-config deep-merge, config.build.cacheFrom), each with examples and parity notes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8c269d4 commit 71a2b60

2 files changed

Lines changed: 283 additions & 105 deletions

File tree

README.md

Lines changed: 124 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,146 @@
1-
# devcontainer-cli (Go)
1+
# devcontainer (Go)
22

3-
A Go implementation of the [Dev Container CLI](https://containers.dev) — a drop-in
4-
replacement for the reference TypeScript CLI, built to be distributed as a single
5-
**static binary** and to be the primary codebase we evolve and streamline from here.
3+
**A single static binary that runs your [dev containers](https://containers.dev)
4+
a drop-in replacement for the official `@devcontainers/cli`, with none of the
5+
Node.js baggage and a few superpowers the original doesn't have.**
6+
7+
Point your tooling at this `devcontainer` binary instead of `node devcontainer.js`
8+
and it behaves the same — `up`, `build`, `exec`, `features`, `templates`, all of it —
9+
except it starts faster, ships as one file, and fixes the papercuts you hit every day.
610

711
---
812

9-
## Why
10-
11-
The reference [`devcontainers/cli`](https://github.com/devcontainers/cli) is a
12-
Node.js/TypeScript application. Re-implementing it in Go buys us:
13-
14-
- **A single static binary** — no Node.js runtime, no `node_modules`, no install
15-
step. `CGO_ENABLED=0` gives a fully static executable that runs anywhere
16-
(containers `FROM scratch`, minimal CI images, air-gapped hosts).
17-
- **Fast startup** and low overhead (no interpreter warm-up).
18-
- **Simple distribution** — cross-compiled binaries per OS/arch, or `go install`.
19-
- **A codebase we can evolve** — streamline the internals, add features, and change
20-
behavior deliberately, instead of tracking an upstream we don't control.
21-
22-
## Supported platform
23-
24-
> **Linux only, Docker only.** This CLI is supported and validated **exclusively on
25-
> Linux** (amd64 and arm64). Windows and macOS are **not** targets — there is no runtime,
26-
> E2E, or release for them. The only supported container runtime is **Docker**; **Podman
27-
> is not supported**, and only Compose **v2** (`docker compose`) is used. Any cross-platform
28-
> or Podman-detection logic that exists is kept only for parity with the TS oracle and
29-
> carries no support guarantee.
30-
31-
## What it is for
32-
33-
The goal is behavioral compatibility with the TS CLI for the commands people rely on:
34-
`up`, `build`, `exec`, `read-configuration`, `set-up`, `run-user-commands`,
35-
`features` (info/package/publish/test/…), `templates` (apply/publish/…), `outdated`,
36-
`upgrade`. If you point tooling (including the VS Code Dev Containers extension) at
37-
this binary instead of `node devcontainer.js`, it should behave the same.
38-
39-
## How parity is maintained
40-
41-
Parity is **not** assumed — it is validated against the real TypeScript CLI, kept in
42-
this repo as a git submodule.
43-
44-
- **`reference/`** is a submodule pinned to a specific upstream release (currently
45-
**v0.88.0**). It is the behavioral oracle: we compile it and run the exact same
46-
commands through both CLIs.
47-
- **`docs/migration/parity-matrix.yaml`** is a matrix of ~200 cases (flag validation,
48-
`up`/`build`/`exec`/compose/features/templates/publish scenarios). Each case runs
49-
the **same command** against the Go binary and the TS oracle and asserts the outputs
50-
match (exit code, normalized stdout/stderr, and container/registry state via
51-
`verify_cmd`).
52-
- **`internal/cli/parity_matrix_test.go`** (`TestParityMatrix`) drives the matrix. It
53-
has two lanes:
54-
- **contract** — hermetic (flag/output contract, no Docker), fast.
55-
- **runtime** — creates real containers/images (needs Docker); parallel, isolated
56-
per case (unique `--id-label` / `COMPOSE_PROJECT_NAME` / `BUILDX_BUILDER`).
57-
- **`docs/migration/GO-REWRITE-STATUS.md`** is the current parity status: what is
58-
covered and what remains (not a changelog — that lives in `git log`).
59-
- **`docs/migration/REMAINING-WORK.md`** is the single actionable backlog.
60-
- **`docs/migration/RELEASE-CHECKLIST.md`** defines the gates required before the
61-
project may claim complete parity or publish a release.
62-
63-
Unit, integration and parity tests are separate workflows. `task test:unit` only
64-
targets `cmd/` and `internal/`, so it neither discovers packages under
65-
`reference/node_modules` nor runs the TypeScript oracle, Docker, listeners or the
66-
network. Parity tasks compile the reference explicitly.
67-
68-
### Maintaining parity as we evolve
69-
70-
1. When streamlining/refactoring the Go code, run the parity matrix to confirm no
71-
*unintended* behavior change slipped in.
72-
2. When we *intentionally* change behavior, update the matrix case (or remove it) —
73-
the matrix documents deliberate divergences.
74-
3. To track a newer upstream, bump the `reference/` submodule to the new tag and
75-
re-run the matrix; new failures show where upstream moved (features/behavior added
76-
after the last validated version).
77-
78-
> Note: parity was originally validated end-to-end against **v0.74.0**. The reference
79-
> is now pinned to **v0.88.0**; running the matrix against it may surface gaps
80-
> introduced upstream between 0.74 and 0.88 — that is expected and is the signal for
81-
> what to bring over next.
82-
83-
## Layout
13+
## Why switch?
14+
15+
If you use the official CLI, you already know the friction:
16+
17+
| Daily pain with `@devcontainers/cli` | With this binary |
18+
| --- | --- |
19+
| `npm i -g @devcontainers/cli` pulls a Node runtime + hundreds of MB of `node_modules`, and drifts out of sync across machines/CI | **One static binary.** `CGO_ENABLED=0`, no runtime, no `node_modules`. Drop it in `FROM scratch`, a distroless CI image, or an air-gapped host. |
20+
| Node interpreter warm-up on every invocation | **Fast cold start**, low overhead. |
21+
| Pulling a **private base image** or `--push`/`--cache-to` to a private registry means running `docker login` by hand — sometimes in several places | **Credential bridge:** the CLI's own auth chain (`DEVCONTAINERS_OCI_AUTH`, cred helpers, `GITHUB_TOKEN`) is handed to `docker build` automatically. |
22+
| A missing buildx, no cache-export, or a wrong Compose version surfaces as a cryptic error **deep into a build** | **`devcontainer check`** preflights your host and tells you exactly what's wrong (and `setup` fixes what it safely can). |
23+
| Reusing a prebuilt image means re-implementing config hashing and clone-and-mutate hacks in your orchestrator | **`--cache-key`** emits a deterministic content hash; **`up --cache-image`** boots from a prebuilt image and skips feature install. |
24+
| A TLS-intercepting proxy breaks image pulls because the CA isn't honored everywhere | Proxy + `NODE_EXTRA_CA_CERTS`/`SSL_CERT_FILE` honored on **every** request path. |
25+
26+
Same behavior where it counts, better ergonomics where it hurts.
27+
28+
## Install
8429

30+
Build it yourself — it's one command, and the result is a single self-contained file:
31+
32+
```sh
33+
git clone https://github.com/spin-stack/devcontainer-cli
34+
cd devcontainer-cli
35+
task build # or: go build -o devcontainer ./cmd/devcontainer
36+
sudo mv devcontainer /usr/local/bin/
8537
```
86-
cmd/, internal/ the Go CLI implementation
87-
scripts/ runtime assets used by the CLI (updateUID.Dockerfile)
88-
src/test/configs/ fixtures used by the parity matrix
89-
docs/migration/ parity audit, status log, parity-matrix.yaml
90-
reference/ git submodule → devcontainers/cli @ v0.88.0 (parity oracle)
91-
.github/workflows/ CI: lint/test/build/cross-compile + parity lanes
38+
39+
Tagged releases publish prebuilt **static** binaries (Linux amd64/arm64) and a
40+
container image to the
41+
[Releases page](https://github.com/spin-stack/devcontainer-cli/releases) — grab those
42+
instead once a version is cut.
43+
44+
Confirm it's a single self-contained file:
45+
46+
```sh
47+
file ./devcontainer # → "statically linked"
48+
ldd ./devcontainer # → "not a dynamic executable"
9249
```
9350

94-
## Build & test
51+
> **Scope: Linux + Docker.** Supported and validated on **Linux** (amd64, arm64) with
52+
> **Docker** and Compose **v2**. Windows, macOS and Podman are not targets.
53+
54+
## Hello, dev container (60 seconds)
55+
56+
Create a project with a minimal dev container config:
9557

9658
```sh
97-
task build # statically-linked ./devcontainer (CGO_ENABLED=0)
98-
task test:unit # hermetic Go unit tests
99-
task test:integration # local HTTP integration tests
100-
task coverage # reproducible hermetic coverage report
101-
task lint # go vet
102-
task build:cross # static binaries for linux/darwin/windows × amd64/arm64
59+
mkdir hello-devcontainer && cd hello-devcontainer
60+
mkdir -p .devcontainer
61+
cat > .devcontainer/devcontainer.json <<'JSON'
62+
{
63+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
64+
"features": {
65+
"ghcr.io/devcontainers/features/node:1": {}
66+
},
67+
"postCreateCommand": "node --version"
68+
}
69+
JSON
70+
```
71+
72+
Bring it up, then run a command inside it:
73+
74+
```sh
75+
devcontainer up --workspace-folder .
76+
devcontainer exec --workspace-folder . bash -lc 'node --version && whoami'
10377
```
10478

105-
Verify the binary is static:
79+
That's it — a real container, your feature installed, your workspace bind-mounted.
80+
Any automation that shells out to the `devcontainer` CLI (CI pipelines, prebuild
81+
services, scripts) can call this binary instead of `node devcontainer.js`.
82+
83+
## Superpowers (Go-only)
84+
85+
These commands and flags don't exist in the upstream CLI. Full reference:
86+
**[docs/go-only-features.md](docs/go-only-features.md)**.
10687

10788
```sh
108-
file ./devcontainer # → "statically linked"
109-
ldd ./devcontainer # → "not a dynamic executable"
89+
# Preflight the host — daemon, buildx, cache export, Compose v2, disk — before you build
90+
devcontainer check
91+
devcontainer setup # apply the safe fixes (e.g. a cache-capable buildx builder)
92+
93+
# Deterministic cache key for prebuild reuse (hash of config + Dockerfile + features + proxy)
94+
devcontainer read-configuration --workspace-folder . --cache-key
95+
96+
# Boot from a prebuilt image, skipping build + feature install
97+
devcontainer up --workspace-folder . --cache-image ghcr.io/acme/app-devcontainer:sha-abc123
98+
99+
# Pass BuildKit build secrets (RUN --mount=type=secret,id=…) without baking them into a layer
100+
devcontainer build --workspace-folder . --secrets-file secrets.json
110101
```
111102

112-
## Running the parity matrix (optional, needs the submodule + Node)
103+
Everything else — `up`, `build`, `exec`, `read-configuration`, `set-up`,
104+
`run-user-commands`, `features` (info/package/publish/test/…), `templates`
105+
(apply/publish/…), `outdated`, `upgrade` — mirrors the official CLI.
106+
107+
## Is it really compatible?
108+
109+
Compatibility is **validated**, not assumed. A pinned copy of the official
110+
TypeScript CLI (`reference/`, currently **v0.88.0**) is the behavioral oracle: a
111+
matrix of ~200 cases runs the *same* command through both CLIs and asserts the
112+
outputs match (exit code, normalized stdout/stderr, and container/registry state).
113+
Deliberate divergences (like the Go-only features above) are recorded, not hidden.
114+
115+
If you're evaluating a switch, the parity methodology and current status live in
116+
[`docs/migration/`](docs/migration/).
117+
118+
## Contributing / building
119+
120+
```sh
121+
task build # statically-linked ./devcontainer (CGO_ENABLED=0)
122+
task test:unit # hermetic Go unit tests
123+
task test:integration # local HTTP integration tests
124+
task lint # linters
125+
task build:cross # static binaries for linux/{amd64,arm64}
126+
```
127+
128+
Running the parity matrix (needs the submodule + Node + Docker):
113129

114130
```sh
115131
git submodule update --init reference
116-
task reference # install + compile the TypeScript oracle
117-
task parity:contract # hermetic contract lane
118-
task parity:semantic # semantic cases without Docker/network
119-
task parity:network # cases that require external network access
120-
task parity:runtime # complete matrix; requires Docker, ~5–6 min
132+
task reference # compile the TypeScript oracle
133+
task parity:contract # hermetic contract lane (no Docker)
134+
task parity:runtime # full matrix; creates real containers/images via Docker
121135
```
122136

123-
## History
137+
- **[`docs/migration/GO-REWRITE-STATUS.md`](docs/migration/GO-REWRITE-STATUS.md)** — parity status.
138+
- **[`docs/migration/REMAINING-WORK.md`](docs/migration/REMAINING-WORK.md)** — the actionable backlog.
139+
- **[`docs/migration/parity-matrix.yaml`](docs/migration/parity-matrix.yaml)** — the case matrix.
124140

125-
The full commit history of the rewrite (18 blockers, majors, and the parity work)
126-
lives in the [`aledbf/cli`](https://github.com/aledbf/cli) `go-rewrite` branch. This
127-
repo starts from that validated state with the Go code as the primary tree.
141+
```
142+
cmd/, internal/ the Go CLI implementation
143+
docs/ user + migration docs
144+
src/test/configs/ fixtures used by the parity matrix
145+
reference/ git submodule → the official CLI @ v0.88.0 (parity oracle)
146+
```

0 commit comments

Comments
 (0)