API-Docker

 view release on metacpan or  search on metacpan

.claude/skills/docker-engine-api/SKILL.md  view on Meta::CPAN

`attach` and `exec/start` additionally accept `Upgrade: tcp` +
`Connection: Upgrade`, to which the daemon answers **101 Switching Protocols**
and hands over a bidirectional connection. Without those headers it answers 200
and streams the same frames one-way.

## Filters are JSON, and the shape is specific

`filters` is a query parameter holding a JSON-encoded **map of string to array
of string** — the values are arrays of *strings*, even for booleans:

```
?filters={"dangling":["true"],"label":["stage=build"]}     correct
?filters={"dangling":true}                                 matches nothing
```

Wrong-shaped filters do not error. The daemon accepts them and returns an
unfiltered or empty list, so the bug surfaces as "my prune deleted too much" or
"my list is empty", never as a 400.

Query-string booleans elsewhere are strings: `?all=1` / `?all=true`. Booleans
in a **JSON request body** must be real JSON booleans — a language that encodes
`1` where the daemon expects `true` gets a type error from the API.

## Registry auth

`X-Registry-Auth` carries **base64url of a JSON object**, and the padding is
required — the daemon decodes with Go's `base64.URLEncoding`, not
`RawURLEncoding`. Stripping `=` produces
`failed to parse "X-Registry-Auth" header ... unexpected EOF`.

The header is mandatory on **every** push, anonymous included; the anonymous
form is the encoding of `{}`, which is `e30=` — three characters and one pad,
the shortest case and the one that proves padding matters. Payload keys:
`username`, `password`, `serveraddress`, or `identitytoken`.

`/build` uses a different header for the same job: `X-Registry-Config`,
base64url of a map from registry hostname to auth object, because a build may
pull from several registries.

## Bodies and paths

`POST /build` is the odd one: the request body is the **tar build context**
(`Content-Type: application/x-tar`), and every option — `t`, `dockerfile`,
`buildargs`, `target`, `platform` — rides in the query string. `buildargs` and
`labels` are themselves JSON-encoded strings inside that query.

Container endpoints accept a name or any unambiguous ID prefix. Image
references keep their slashes and tags inside the path
(`/images/myrepo/app:v1/push`) — percent-encoding them breaks the reference.
Names from `GET /containers/json` arrive with a leading `/`.

`exec` is two calls: `POST /containers/{id}/exec` creates the instance and
returns an `Id`, `POST /exec/{id}/start` runs it. The exit status comes from
`GET /exec/{id}/json` afterwards (`ExitCode`), never from the start call.

## Other engines

Podman serves this API on a compat socket — enable with
`systemctl --user enable --now podman.socket`, reach it at
`unix://$XDG_RUNTIME_DIR/podman/podman.sock`, and it announces API 1.41.
Multi-stage builds including `target` pass through unchanged, and the frame
format above is byte-identical. It is a reimplementation, not Docker: treat
anything beyond the documented surface — event payload details, healthcheck
fields, error message text — as unverified until measured against the engine
you actually target.

Clients differ in how they *find* the daemon: `DOCKER_HOST` is the one
mechanism all of them honour. Docker contexts
(`~/.docker/config.json` `currentContext` plus
`~/.docker/contexts/meta/*/meta.json`) are resolved by the `docker` CLI and
docker-java but not by most library clients, so "works in the terminal, fails
in my program" usually means a context the program never read.

## Probing by hand

```bash
curl --unix-socket /var/run/docker.sock http://localhost/v1.47/containers/json
curl --unix-socket /var/run/docker.sock -X POST \
  'http://localhost/v1.47/images/create?fromImage=alpine&tag=3'
```

`curl` writes the raw stream, frame headers included — that is the fastest way
to confirm what a client should be seeing before blaming the client.



( run in 2.053 seconds using v1.01-cache-2.11-cpan-6736b670a1e )