diff --git a/README.md b/README.md index a043b3c..809ad50 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ A docker-like sandbox CLI over `bwrap` — filesystem isolation only. A dock is the enclosed basin in which ships are built: the dock gate controls what passes, the water outside is shared with the whole harbor. Accordingly, network, uid, `/proc`, `/dev`, and `/tmp` come from the host by contract; that is what makes Werkdock work without root on a Hostsharing Managed Webspace. -Semantics: +Semantics — docker-compatible as far as the filesystem-only contract allows (see [RFC 0002](docs/rfcs/0002-docker-compatible-surface.md)): -- An *image* is a rootfs archive. -- An *instance* is an unpacked, writable directory tree. -- `werkdock run -- ` executes in the sandbox with uid 0 mapped to the calling user. +- An *image* is a rootfs archive; an *instance* is an unpacked, writable directory tree and corresponds to a docker container. +- `werkdock run [flags] IMAGE [CMD...]` creates an instance and executes in the sandbox with uid 0 mapped to the calling user; verbs and flags follow docker, unsupported docker flags fail loudly. - `werkdock doctor` checks the host: user-namespace capability, disk and quota headroom. +- A daemon speaking the Docker Engine API subset (for Testcontainers) is designed for but deferred. Status: bootstrap. The implementation language is Go, decided in [RFC 0001](docs/rfcs/0001-implementation-language.md). diff --git a/docs/rfcs/0002-docker-compatible-surface.md b/docs/rfcs/0002-docker-compatible-surface.md new file mode 100644 index 0000000..89c7c39 --- /dev/null +++ b/docs/rfcs/0002-docker-compatible-surface.md @@ -0,0 +1,88 @@ +# RFC 0002: Docker-Compatible Surface + +**Status:** +- proposed: 2026-09-01 +- accepted: - +- rejected: - + +**Proposal:** Werkdock's user-facing surface follows Docker wherever the filesystem-only contract allows: level 1 is a docker-compatible CLI (verbs, flags, exit codes), level 2 is pulling OCI images from registries, level 3 is a daemon offering the Docker Engine REST API subset that Testcontainers needs. +Level 1 is built in session B; levels 2 and 3 are designed for but deferred. + +## Context and Problem Statement + +The requirement (2026-09-01): the CLI — and a daemon API, if one is needed — shall be docker-compatible as far as possible, also to enable integrating Testcontainers later. + +Docker compatibility is not one thing; it comes in three separable levels, and Testcontainers forces a position on each: + +1. **CLI compatibility** — `werkdock run` takes the flags a docker user already knows. Cheap, pure design discipline, and it makes every docker tutorial partially applicable. +2. **Image compatibility** — a werkdock image today is a self-built rootfs archive; docker images are OCI images from registries. Pulling and flattening OCI images makes the world's images usable. +3. **API compatibility** — Testcontainers never invokes the CLI; it speaks the Docker Engine REST API over a unix socket (`DOCKER_HOST`). Podman achieves Testcontainers support exactly this way (`podman system service`). Without this level there is no Testcontainers, regardless of the CLI. + +## What Testcontainers Actually Needs + +From observing docker-java/testcontainers-java against real daemons: + +- `/version` and `/info` handshakes; then image pull (level 2 is a prerequisite), container create/start/inspect/logs/wait/remove. +- Port mapping: create requests an exposed container port with an empty host port, inspect must answer with the mapped ephemeral host port (`NetworkSettings.Ports`). +- The Ryuk reaper container (disableable via `TESTCONTAINERS_RYUK_DISABLED=true`). + +The port mapping is the crux for Werkdock: with filesystem-only isolation there is no network namespace, the payload binds host ports directly. +Two consequences: + +- "Mapping" degenerates to identity — inspect reports the port the service actually bound. Workable for sequential CI use. +- Two containers wanting the same fixed port collide, exactly as with docker's `--network=host`. + +The honest way out, if Testcontainers support ever becomes serious: unprivileged network namespaces are available inside a user namespace (rootless podman does networking this way, via a userspace stack — pasta/slirp4netns). +That would be a deliberate, opt-in extension of the filesystem-only contract, decided in its own RFC — not implied by this one. + +## Considered Options + +* Docker-compatible from the start on all three levels — rejected: level 3 without a consumer is speculation, and the Ryuk/port semantics need real Testcontainers runs to validate against. +* Own CLI idioms (`werkdock run -- ` as sketched in plan step 21), compatibility later — rejected: retrofitting docker semantics onto a shipped CLI breaks users; the compatibility must shape the surface from day one. +* Docker-compatible CLI now, API-ready architecture, levels 2 and 3 deferred — chosen. + +## Concrete Proposal + +### Level 1 — CLI (session B) + +Verbs and flags follow docker; unsupported docker flags fail loudly with a reason, never silently no-op: + +| Werkdock | Docker equivalent | Notes | +|---|---|---| +| `werkdock run [flags] IMAGE [CMD...]` | `docker run` | creates an instance from the image, runs CMD | +| `werkdock create` / `start` / `stop` / `rm` | same | instance lifecycle | +| `werkdock ps [-a]` | same | running/all instances | +| `werkdock images` / `rmi` | same | local image store | +| `werkdock load -i FILE` | `docker load` | imports a rootfs archive as an image | +| `werkdock exec INSTANCE CMD...` | `docker exec` | additional process in a running sandbox | +| `werkdock logs [-f] INSTANCE` | `docker logs` | | +| `werkdock inspect NAME` | `docker inspect` | JSON, docker-shaped where fields apply | +| `werkdock doctor` | *(none)* | host capability and quota check; `info` aliases the summary | + +Supported `run` flags from the start: `-v/--volume` (bind mounts), `-e/--env`, `-w/--workdir`, `--rm`, `--name`, `-d/--detach`, `--entrypoint`. +Refused with explanation: everything that promises isolation Werkdock does not provide (`-p/--publish`, `--network`, `--memory`, `--cpus`, `--user` beyond the fixed uid-0 mapping). + +Semantic shift against the step-21 sketch: `run` takes an **image** (docker semantics), not a pre-unpacked instance; instances are created per run and correspond to docker containers. +`--rm` deletes the instance tree afterwards; without it, `ps -a`/`start` see it again. + +### Level 2 — OCI images (deferred, designed for) + +`werkdock pull IMAGE[:TAG]` fetches from an OCI registry (Docker Hub et al.) and flattens the layers into a rootfs. +This is HTTP + JSON + tar with whiteout handling — implementable within the stdlib-only policy (RFC 0001), but a substantial work package (registry auth token dance included). +Until then, `werkdock load` and the self-built rootfs archives carry the image store. + +### Level 3 — daemon API (deferred, designed for) + +`werkdock daemon` serves the Docker Engine API subset from "What Testcontainers Actually Needs" on a unix socket; consumers set `DOCKER_HOST=unix://$XDG_RUNTIME_DIR/werkdock.sock`. +Architecture consequence now: the CLI must not own the lifecycle logic — verbs are thin frontends over the same internal service the daemon would expose, and instance state lives on disk in a format both can read. +Ryuk stays disabled in documentation until proven. + +## Consequences + +- Plan step 21 session B and the README change their CLI sketch to the docker-shaped surface above. +- The engine interface from RFC 0001 is unaffected — compatibility shapes the surface, engines stay swappable behind it. +- Testcontainers remains a stated goal, not a claim: it is validated the day level 3 exists, and the port-collision limitation is documented until a network-namespace RFC changes it. + +## Decision Outcome + +Pending.