15 KiB
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.sockby default (podman's verb and podman's manners: optional, started when needed,--timeto exit when idle). A client is pointed at it withDOCKER_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 needsTESTCONTAINERS_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
logsparameters werkdock cannot honour say so with 501 and the reason:tail(the output is streamed whole),timestampsand a non-zerosince/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 setDOCKER_HOSTandTESTCONTAINERS_RYUK_DISABLEDin 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 -dstarts the instance under a background supervisor and prints its 64-hex id;--name NAMEnames it (default: image basename plus six characters of the id);-ipasses stdin (no pty:-tis 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, andinspecton 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,/runand 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--rminstance found that way is removed.
Changed
- A
runwithout--rmno longer fails ("persistent instances are not implemented yet"); the instance stays forps -a,logs,start,rm. - The command is pid 1 of its sandbox (
bwrap --as-pid-1), as in docker, sostop'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
runforwards 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; ash -cbuild interrupted with SIGTERM now ends 10 seconds later, on the SIGKILL, unless the script traps TERM. - Anonymous volumes (the image's
VOLUMEs) and/runare directories of the instance instead of tmpfs, so they survivestop/startand are visible toexec. For a--rmrun the difference is disk instead of RAM for those paths, and nothing else.
For Werkator
- Nothing to change:
run --rm …behaves as before. Thepsoutput 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/passwdand/etc/group, a number needs no entry.HOMEfollows 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--usernsmodes are refused with the reason.- An image's
USER(OCI config) applies when neither flag is given, as with podman; unknown names fail withimage USER: …. Default stays uid 0. - An image's
VOLUMEpaths are mounted as anonymous tmpfs unless a-vor--mountcovers them, sopostgres:16starts without knowing its data and socket directories by heart. /runis a tmpfs in every sandbox, beside/tmpand/root./var/runpoints there in practically every image, and services keep pid files, locks and sockets under it without declaring aVOLUME(postgresneeds/var/run/postgresqlwritable). A--tmpfs/-von a path below/runstill works, it just lands inside the tmpfs.import -cacceptsUSER user[:group]andVOLUME /path(or a JSON array);inspectshowsUserandVolumesinConfig.
For Werkator
- Nothing to change. Builds keep running as uid 0. If a build image ever sets
USER, that user applies; add--user 0to keep root.
Podman before Docker
- Where the two differ, werkdock follows podman from now on (RFC 0005):
--userns=keep-idis 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 asload -iwould: layers flattened, OCI config kept, listed byimagesunder the reference given. PrintsPulled image: REF; exit 125 with the reason if the registry refuses, the image already exists, or crane is missing.--platformdefaults tolinux/<host arch>.- The registry side is the
cranebinary (github.com/google/go-containerregistry, one static executable, no privileges). werkdock looks for it inPATH, then beside its own binary, or atWERKDOCK_CRANE. A deployment on a managed webspace ships it next towerkdock. Credentials come from~/.docker/config.json(DOCKER_CONFIG), as with docker. werkdock rmi IMAGE...removes images (docker rmi): printsUntagged: REFper 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.pullandloadnow namermiin their "already exists" message.werkdock imagestakes docker's--format: a Go template over.Repository,.Tag,.ID,.CreatedAt,.CreatedSince,.Size, ortable [TEMPLATE]for docker's column layout (--format tableprintsREPOSITORY TAG IMAGE ID CREATED SIZE);-qprints IDs; an optionalREPOSITORY[:TAG]argument filters. Without flags the output is unchanged: one short reference per line.inspectgains anIdfield. The ID is a hash of the image metadata, shared by tags of the same image; it is not docker's config digest.werkdock doctorwarns when crane is not inPATH(a warning, not a failure: everything butpullworks without it).
For Werkator
- Optional: replace "download archive,
werkdock load -i" with a singlewerkdock pull IMAGEwhere the image is a public registry image; the self-built archives keep going throughimport/load. - Deployment: put
cranebesidewerkdockifpullis to be used. - Optional: retire old build images with
werkdock rmiinstead 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 imagesstill 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 thanlatestprint asrepository:tag.werkdock load -i ARCHIVE --name NAMEstill imports a rootfs archive; it now additionally prints one note on stderr pointing towerkdock import. Exit code and stdout are unchanged (Loaded image: NAMEbecameImported 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 KEYwhose variable the host does not have is no longer passed asKEY=(it is omitted); a missing-w DIRis 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 asNAME:latest. No reload after the upgrade; new imports land inimages/REPOSITORY/TAG/. The pre-existingimage.jsonfiles 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]]replacesload -i --namefor rootfs archives (docker importsemantics). Plan:load -i --namestays accepted until Werkator has switched, then it is removed together with the stderr note.werkdock load -i ARCHIVEloads adocker savearchive (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 thezstdbinary, asimportdoes.- Image names may carry a tag:
werkator-buildenv-<hash>keeps working and equalswerkator-buildenv-<hash>:latest; a consumer may also usewerkator-buildenv:<hash>or a registry-stylegit.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.tmptag 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 IMAGEprints it as a docker-shaped JSON array (RepoTags,Created,Architecture,Os,Config, plus aWerkdocksection withSourceandRootFS). A consumer that wants to check existence likedocker image inspectcan useinspect(exit 125 when the image is missing) instead of scanningimages.werkdock run IMAGEwithout a command runs the image's ENTRYPOINT+CMD;--entrypoint PROGRAMoverrides ENTRYPOINT and drops CMD; positional arguments replace CMD;-eand-woverride the image's Env and WorkingDir.--mount type=bind,src=SRC,dst=DEST[,readonly]and--mount type=tmpfs,dst=DESTare accepted beside-vand--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/--publishremain 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.