# Changelog Release notes for consumers of the `werkdock` CLI, Werkator first among them. Each entry says what changed, what still works unchanged, and what a consumer should adapt — and by when. A release is a merge to main; there is no separate release step. So a branch that changes behavior sets the version in `internal/cli/cli.go` (what `werkdock version` prints) and titles its section here with that version and the merge date, before it is merged. Nothing on main is ever "unreleased". ## 0.6.0 (2026-09-10) Theme: the API socket — RFC 0005 milestone 3, and with it milestone 4: Testcontainers starts a real PostgreSQL in werkdock. Purely additive; no CLI verb changes behaviour. ### New - `werkdock system service [--time D] [unix://PATH]` serves the Docker Engine API subset a client actually uses, on `$XDG_RUNTIME_DIR/werkdock.sock` by default (podman's verb and podman's manners: optional, started when needed, `--time` to exit when idle). A client is pointed at it with `DOCKER_HOST=unix://`. - Testcontainers (validated with 1.19.3, `PostgreSQLContainer`) works against it: pull, create, start, logs with the wait strategy, exec, kill, remove, and the session cleanup. It needs `TESTCONTAINERS_RYUK_DISABLED=true`, because Ryuk wants the API socket inside a container of its own. - The endpoints are those measured from a real run, written down in `docs/testcontainers-api.md`. Everything outside the subset answers **501 with the reason**, never 404, so a client that needs more says so plainly. - Instances carry docker labels (`config.json`), and the instance list filters on them, which is how a client finds what it left behind. - An exec is a record of its own with an id and an exit code, because the API creates, starts, and inspects it in three calls. ### Refused rather than ignored - `logs` parameters werkdock cannot honour say so with 501 and the reason: `tail` (the output is streamed whole), `timestamps` and a non-zero `since`/`until` (no clock is recorded beside the output). What a client actually sends — `stdout`, `stderr`, `follow`, `since=0`, `tail=all` — is served. ### Ports, the deliberate deviation Werkdock shares the host's network and cannot map a port. Where a client asks for a published port, werkdock therefore picks a free one and tells the *service* to bind it, through the environment variable the image honours (`PGPORT`, `MARIADB_TCP_PORT`, `MYSQL_TCP_PORT`, `REDIS_PORT`). An image outside that small table keeps its own port, which is right as long as one instance of it runs at a time; asking to move it fails with the reason instead of silently reporting a port nothing listens on. The same table carries the user an image needs when its entrypoint expects to drop privileges itself (`postgres`), which werkdock cannot do with one uid. ### Version reporting `/version` and `/info` report the Docker Engine version whose API is served (25.0.0), not werkdock's: clients compare that field against a minimum, and 0.x fails it forever. Werkdock's own version is beside it in `Components` and in `/info`'s `OperatingSystem`. ### For Werkator - Nothing to change. The service is not started unless someone asks for it, and no CLI verb behaves differently. - New possibility: an integration test in a build sandbox can get a real database. Bind the socket into the sandbox (`-v $XDG_RUNTIME_DIR/werkdock.sock:/run/docker.sock`) and set `DOCKER_HOST` and `TESTCONTAINERS_RYUK_DISABLED` in the build. ## 0.5.0 (2026-09-10) Theme: the instance lifecycle — RFC 0005 milestone 2. A service image runs in the background, can be stopped, restarted, queried, and talked to. Additive for `run --rm`; two small semantic changes below. ### New - `run -d` starts the instance under a background supervisor and prints its 64-hex id; `--name NAME` names it (default: image basename plus six characters of the id); `-i` passes stdin (no pty: `-t` is refused). - `ps [-a] [-q] [--format FMT]` lists instances with docker's columns (`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES`; PORTS stays empty, the network is the host's); `stop [-t SECONDS]`, `kill [-s SIG]`, `rm [-f]`, `wait`, `logs [-f]`, `create`, `start`, and `inspect` on an instance id, unique prefix, or name. Each prints what docker prints (the argument on success, `werkdock: …` and exit 125 per failure). - `exec [-e K=V] [-w DIR] [-u USER] [-i] INSTANCE CMD...` runs a second command in the instance's sandbox: same rootfs, mounts, uid, env, `/run` and volumes; not the same pid namespace. - docker's management form is accepted: `container ls|start|stop|...`, `image ls|rm|pull|...`. - State lives in `$WERKDOCK_HOME/instances//`: `config.json` (`version: 1`), `state.json`, `stdout`, `stderr`, `run/`, `volumes/`. A state left "running" by a dead supervisor (reboot, `kill -9`) is repaired to exited 255 on the next read; an exited `--rm` instance found that way is removed. ### Changed - A `run` without `--rm` no longer fails ("persistent instances are not implemented yet"); the instance stays for `ps -a`, `logs`, `start`, `rm`. - The command is pid 1 of its sandbox (`bwrap --as-pid-1`), as in docker, so `stop`'s SIGTERM can reach it. As in docker, an entrypoint without a SIGTERM handler (`sh -c '…'`) is only ended by the SIGKILL after the timeout, and orphaned children are its own to reap. - A foreground `run` forwards the first SIGINT/SIGTERM/SIGHUP to the command and kills it on the second, or after 10 seconds; bwrap runs in its own process group, so Ctrl-C no longer kills bwrap (and with it the command) outright. Werkator's builds see the same exit codes as before; a `sh -c` build interrupted with SIGTERM now ends 10 seconds later, on the SIGKILL, unless the script traps TERM. - Anonymous volumes (the image's `VOLUME`s) and `/run` are directories of the instance instead of tmpfs, so they survive `stop`/`start` and are visible to `exec`. For a `--rm` run the difference is disk instead of RAM for those paths, and nothing else. ### For Werkator - Nothing to change: `run --rm …` behaves as before. The `ps` output is new and Werkator does not parse it. ## 0.4.0 (2026-09-10) Theme: service images — the first milestone of RFC 0005 (PostgreSQL in the sandbox, Testcontainers as the destination). Additive; a `run` without the new flags on an image without `USER`/`VOLUME` behaves exactly as before. ### New - `run --user USER[:GROUP]` (`-u`) makes the caller appear as that user inside the sandbox: a name is resolved in the image's `/etc/passwd` and `/etc/group`, a number needs no entry. `HOME` follows the passwd entry when there is one. Still one uid only — the caller is mapped to it; there is no second user. - `run --userns=keep-id` (podman's spelling) maps the caller to their own uid and gid. Other `--userns` modes are refused with the reason. - An image's `USER` (OCI config) applies when neither flag is given, as with podman; unknown names fail with `image USER: …`. Default stays uid 0. - An image's `VOLUME` paths are mounted as anonymous tmpfs unless a `-v` or `--mount` covers them, so `postgres:16` starts without knowing its data and socket directories by heart. - `/run` is a tmpfs in every sandbox, beside `/tmp` and `/root`. `/var/run` points there in practically every image, and services keep pid files, locks and sockets under it without declaring a `VOLUME` (`postgres` needs `/var/run/postgresql` writable). A `--tmpfs`/`-v` on a path below `/run` still works, it just lands inside the tmpfs. - `import -c` accepts `USER user[:group]` and `VOLUME /path` (or a JSON array); `inspect` shows `User` and `Volumes` in `Config`. ### For Werkator - Nothing to change. Builds keep running as uid 0. If a build image ever sets `USER`, that user applies; add `--user 0` to keep root. ### Podman before Docker - Where the two differ, werkdock follows podman from now on (RFC 0005): `--userns=keep-id` is the first case. ## 0.3.0 (2026-09-10) Theme: `werkdock pull` — images come straight from a registry, no docker on any machine needed (RFC 0002 level 2, RFC 0004). Additive; nothing a consumer calls today changes. ### New - `werkdock pull [--platform OS/ARCH] [REGISTRY/]REPOSITORY[:TAG]` fetches an image and stores it exactly as `load -i` would: layers flattened, OCI config kept, listed by `images` under the reference given. Prints `Pulled image: REF`; exit 125 with the reason if the registry refuses, the image already exists, or crane is missing. `--platform` defaults to `linux/`. - The registry side is the `crane` binary (github.com/google/go-containerregistry, one static executable, no privileges). werkdock looks for it in `PATH`, then beside its own binary, or at `WERKDOCK_CRANE`. A deployment on a managed webspace ships it next to `werkdock`. Credentials come from `~/.docker/config.json` (`DOCKER_CONFIG`), as with docker. - `werkdock rmi IMAGE...` removes images (`docker rmi`): prints `Untagged: REF` per image, tries every argument and exits 125 if one failed. Works on the legacy flat layout too, so Werkator's pre-0.2.0 build image can be removed with it. `pull` and `load` now name `rmi` in their "already exists" message. - `werkdock images` takes docker's `--format`: a Go template over `.Repository`, `.Tag`, `.ID`, `.CreatedAt`, `.CreatedSince`, `.Size`, or `table [TEMPLATE]` for docker's column layout (`--format table` prints `REPOSITORY TAG IMAGE ID CREATED SIZE`); `-q` prints IDs; an optional `REPOSITORY[:TAG]` argument filters. Without flags the output is unchanged: one short reference per line. `inspect` gains an `Id` field. The ID is a hash of the image metadata, shared by tags of the same image; it is not docker's config digest. - `werkdock doctor` warns when crane is not in `PATH` (a warning, not a failure: everything but `pull` works without it). ### For Werkator - Optional: replace "download archive, `werkdock load -i`" with a single `werkdock pull IMAGE` where the image is a public registry image; the self-built archives keep going through `import`/`load`. - Deployment: put `crane` beside `werkdock` if `pull` is to be used. - Optional: retire old build images with `werkdock rmi` instead of deleting store directories. ## 0.2.0 (2026-09-10) Theme: the `run` surface, image names, and the image store move closer to `docker run --rm` (RFC 0002, RFC 0004). Everything Werkator invokes today keeps working; two of its calls now have a preferred docker-shaped form. ### Compatible — nothing to change - `werkdock images` still prints one image per line and still prints a bare name for images loaded without a tag. Werkator's exact-line existence check (`image in loaded`) matches as before. Images that carry a tag other than `latest` print as `repository:tag`. - `werkdock load -i ARCHIVE --name NAME` still imports a rootfs archive; it now additionally prints one note on stderr pointing to `werkdock import`. Exit code and stdout are unchanged (`Loaded image: NAME` became `Imported image: NAME` — adjust if the stdout line is parsed). - `werkdock run --rm -v ... --tmpfs ... -e ... -w ... IMAGE CMD...` is unchanged in syntax and mount order. Two semantic corrections match docker: a bare `-e KEY` whose variable the host does not have is no longer passed as `KEY=` (it is omitted); a missing `-w DIR` is created when it lies in a writable area (tmpfs or a read-write bind) instead of failing with bwrap's "Can't chdir". - Existing stores: the flat layout `images/NAME/` written by earlier versions is still read as `NAME:latest`. No reload after the upgrade; new imports land in `images/REPOSITORY/TAG/`. The pre-existing `image.json` files lack the new metadata fields and read back as an image without ENTRYPOINT/CMD/Env, which is exactly what they were. - `werkdock doctor`, `version`, exit codes (125 for werkdock's own errors, the command's code otherwise): unchanged. ### Preferred new forms — adapt at the next convenient change - `werkdock import ARCHIVE [REPOSITORY[:TAG]]` replaces `load -i --name` for rootfs archives (`docker import` semantics). Plan: `load -i --name` stays accepted until Werkator has switched, then it is removed together with the stderr note. - `werkdock load -i ARCHIVE` loads a `docker save` archive (legacy and OCI layout) or a plain OCI image layout: layers flattened into one rootfs, whiteouts applied, the OCI config kept as image metadata, the image named by its RepoTags (every tag becomes a hardlinked copy). A multi-arch index picks the host platform. This is the intended path for build environments from now on: `docker build`, `docker save`, upload, `werkdock load` — with ENTRYPOINT, ENV, and WORKDIR intact (RFC 0004). Layers compressed with zstd need the `zstd` binary, as `import` does. - Image names may carry a tag: `werkator-buildenv-` keeps working and equals `werkator-buildenv-:latest`; a consumer may also use `werkator-buildenv:` or a registry-style `git.javagil.de/mi/buildenv:`. Name grammar: `[registry/]repository[:tag]`, repository components lowercase `[a-z0-9]` with `.`, `_`, `-` between them, tag `[A-Za-z0-9_][A-Za-z0-9_.-]*`, max 128 characters. The `.tmp` tag suffix is reserved. - `import -c 'ENTRYPOINT [...]'`, `-c 'CMD ...'`, `-c 'ENV K=V'`, `-c 'WORKDIR /d'`, `-c 'LABEL k=v'` store OCI metadata with the image; `werkdock inspect IMAGE` prints it as a docker-shaped JSON array (`RepoTags`, `Created`, `Architecture`, `Os`, `Config`, plus a `Werkdock` section with `Source` and `RootFS`). A consumer that wants to check existence like `docker image inspect` can use `inspect` (exit 125 when the image is missing) instead of scanning `images`. - `werkdock run IMAGE` without a command runs the image's ENTRYPOINT+CMD; `--entrypoint PROGRAM` overrides ENTRYPOINT and drops CMD; positional arguments replace CMD; `-e` and `-w` override the image's Env and WorkingDir. - `--mount type=bind,src=SRC,dst=DEST[,readonly]` and `--mount type=tmpfs,dst=DEST` are accepted beside `-v` and `--tmpfs`; all three feed one ordered mount list (the git-metadata mask ordering still holds across them). - `--network host` (and `--net host`) is accepted as a no-op, so a build script can pass a docker-compatible command line unchanged. Other modes fail with: `network mode "X" is not available in unprivileged shared-webspace environments; only "host" is supported`. `-p/--publish` remain refused. ### Incompatible - None for the calls Werkator makes. For everyone else: an image name with an uppercase letter or a leading `.`/`-`/`_` in the repository was accepted by the old name check and is refused now (docker's grammar); `..`-style names were refused before and still are.