Files
werkdock/CHANGELOG.md
miandmhoennig f9c1ae835d Api socket (#5)
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #5
2026-09-10 22:09:58 +02:00

285 lines
15 KiB
Markdown

# 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://<path>`.
- 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/<id>/`: `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/<host arch>`.
- 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-<hash>` keeps working and
equals `werkator-buildenv-<hash>:latest`; a consumer may also use
`werkator-buildenv:<hash>` or a registry-style `git.javagil.de/mi/buildenv:<hash>`.
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.