npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@crvouga/mockingbird-service-docker

v1.6.0

Published

Stateful Docker Engine API v1.52 emulator with portable controls and Node transport.

Readme

@crvouga/mockingbird-service-docker

Local emulators. Real API contracts. Part of Mockingbird.

Work-in-progress Docker Engine API 1.52 emulator. It implements GET/HEAD /_ping, GET /version, /info, /containers/json, and /containers/{id}/json, plus Mockingbird's shared runtime controls. POST /containers/create persists a stopped container. Start, wait, stop, kill, and removal use explicit simulated completion. The Node entry supports non-TTY attach and scripted duplex streams. The contract is pinned in API_EVIDENCE.md; SUPPORT.md lists operations.

Install

bun add @crvouga/mockingbird-service-docker

Usage

import { DockerClient } from "@docker/node-sdk"
import { createServer } from "@crvouga/mockingbird-service-docker/server"

// Docker's Node SDK, as your application uses it (run with Node; its transport does not
// work under Bun). Only the Docker host changes.
const server = await createServer({ seed: 42 })
const docker = await DockerClient.fromDockerHost(`tcp://${server.host}:${server.port}`)
console.log(await docker.systemPing()) // 1.52

await docker.close()
await server.close()

Use DockerAPI for the provider-only Fetch surface, or createRuntime for health, admin controls, namespaces, faults, metrics, and the request journal. Neither entry imports a Node server. The Node entry provides createServer() with an ephemeral loopback port by default; close it with await server.close() after a test.

mockingbird-docker serve --port 8826

HTTP clients can target http://127.0.0.1:8826. A Docker client using DOCKER_HOST can select tcp://127.0.0.1:8826. Unversioned and /v1.52 provider routes work. Versions above 1.52 or below the simulated Engine minimum 1.44 receive provider 400 errors (plain text below 1.24, JSON otherwise). Versions 1.44–1.51 receive an explicit emulator-only 501: their wire formats are not implemented. The emulator reports a synthetic Engine 29.1.0 identity; this is not a required local Docker version. Selected API 1.52 scenarios passed against Engine 29.8.0; full client and Engine compatibility are not claimed. See verification boundaries.

Node HTTP example

Run this with Node against the built package. The server uses an ephemeral loopback port and synthetic state; no Docker installation is needed.

import { DockerClient } from "@docker/node-sdk"
import { createServer } from "@crvouga/mockingbird-service-docker/server"

const server = await createServer({ seed: 42 })
const docker = await DockerClient.fromDockerHost(`tcp://${server.host}:${server.port}`)
try {
  console.log(await docker.systemVersion())
} finally {
  await docker.close()
  await server.close()
}

For Unix sockets, pass an absolute, absent socketPath in your test directory and use Node HTTP requests as described below. The CLI uses the shared TCP adapter; use createServer for Docker's attach and Unix-socket transport.

Synthetic observations

POST /__admin/docker/seed atomically adds images and containers in the selected namespace. It never downloads an image or starts a process. For example:

import { DockerClient } from "@docker/node-sdk"
import { createServer } from "@crvouga/mockingbird-service-docker/server"

const server = await createServer({ seed: 42 })

// Test setup: seed synthetic observations.
await fetch(`${server.url}/__admin/docker/seed`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    images: [{ id: `sha256:${"a".repeat(64)}`, tags: ["synthetic:latest"] }],
    containers: [{
      id: "b".repeat(64), name: "worker", image: "synthetic:latest",
      status: "running", labels: { suite: "example" }, cmd: ["synthetic-worker"],
    }],
    daemon: { rootless: true },
  }),
})

// The application sees them through Docker's SDK.
const docker = await DockerClient.fromDockerHost(`tcp://${server.host}:${server.port}`)
const [worker] = await docker.containerList({ all: true })
console.log(worker?.Names, worker?.State) // [ '/worker' ] running

await docker.close()
await server.close()

Image IDs are immutable SHA-256 strings; container IDs are separate 64-character lowercase hexadecimal strings. Container image resolves an existing image ID or exact tag. Duplicate IDs, names and image tags conflict; unknown images return 404. Invalid seed fields return 400 and roll back the entire seed. Container fields also accept exitCode, entrypoint, env, workingDir, user, hostConfig, networkSettings, sizeRw, and sizeRootFs. Status is one of created, running, paused, restarting, removing, exited, or dead. Timestamps use the emulator clock; reported PIDs are zero, never host processes.

GET /__admin/docker/daemon reports { available, rootless, simulated: true }. POST to the same route changes either boolean. available: false drops provider requests while shared health/admin routes remain accessible and container state is retained. Rootless, security, OS, build, size, and resource metadata are synthetic observations, not host attestation or enforcement.

List defaults to running containers, including paused/restarting ones. all, positive limit, or a status filter includes stopped containers. limit selects the newest results; size includes the seeded byte counts. Like the pinned Engine's boolean parser, empty/0/no/false/none (case-insensitive) mean false and other values mean true. Inspect resolves full IDs, unique prefixes, and names.

filters accepts JSON string arrays or boolean-key sets for id, name, status, label, and exited. Categories combine with AND; values within a category combine with OR, except labels which all must match. Name matching supports literals, dots, anchors, and at most one .*; other regex constructs and other filter categories return explicit emulator-only 501 errors. Malformed filter shapes, statuses and exit codes return 400. Seeded state and daemon settings participate in shared reset and Timeline checkpoints.

Container creation

Seed images before calling POST /containers/create. Image seeds may include platform (default linux/amd64) and config containing supported image defaults. Creation resolves image IDs or exact tags, adding :latest to an untagged reference. It never pulls or executes an image. Missing images and requested platform mismatches return 404; an implicit host-platform mismatch produces a warning.

The supported body fields are Image, Cmd, Entrypoint, Env, Labels, WorkingDir, User, Hostname, Domainname, AttachStdin, AttachStdout, AttachStderr, OpenStdin, StdinOnce, Tty, NetworkDisabled, StopSignal, StopTimeout, HostConfig, and NetworkingConfig. Unsupported fields return explicit emulator-only 501 errors. Bad field types, relative working directories, invalid stop signals, invalid names, and missing commands return 400. Image defaults supply commands/entrypoints, environment, labels and selected strings. Request environment keys and labels take precedence. Entrypoint: [""] clears the image entrypoint; provide a replacement command when doing so.

Use ?name=... for a stable name. Conflicting names return 409, including concurrent creation requests. Omitted names use mockingbird_<id-prefix>. Responses contain Id and Warnings; IDs are deterministic synthetic 64-character hex strings, immutable within stored records and restored with the shared ID sequence by Timeline checkout. New records inspect as created with Running: false.

Supported HostConfig metadata includes NetworkMode, IpcMode, PidMode, CgroupnsMode, Runtime, AutoRemove, ReadonlyRootfs, Privileged, Init, Memory, MemorySwap, NanoCpus, CpuShares, PidsLimit, Binds, CapDrop, CapAdd, SecurityOpt, Dns, ExtraHosts, Mounts, Tmpfs, PortBindings, RestartPolicy, and LogConfig. NetworkingConfig.EndpointsConfig is retained under inspected NetworkSettings.Networks. These are declared configuration observations, not enforced resources, mounts, security controls or network setup. Nested host metadata is retained as supplied; full daemon-specific resource and network validation is not modelled. No host isolation claim follows from it.

Start, wait and scripted completion

POST /containers/<id>/start marks a created/exited container running (204), returns 304 when already running/restarting, and returns 409 for paused, dead, or removing records. No image is executed. The clock supplies StartedAt; restart clears the exit code but retains the prior FinishedAt until completion. Checkpoint, checkpoint-dir and detachKeys options return explicit emulator-only 501. As in the pinned Engine route, bodies longer than seven bytes and chunked bodies return 400; use an empty body.

POST /containers/<id>/wait?condition=... accepts not-running (also the default for omitted/empty values), next-exit, or removed. Headers arrive immediately; the JSON body { "StatusCode": 7 } arrives only when the condition holds. Not-running returns immediately for stopped/created containers. Next-exit waits for a future exit even if already stopped. Removed remains pending after an ordinary exit. Invalid conditions return 400 and unknown containers return 404 before opening the stream.

Use POST /__admin/docker/containers/<id>/complete with { "exitCode": 7 } to explicitly finish a running synthetic container. Completion persists exited, FinishedAt, and the supplied integer code, wakes relevant waiters, and captures a shared Timeline checkpoint. If HostConfig.AutoRemove is true, completion also removes the synthetic record and satisfies removed waits. No host resource is touched. Repeated completion while stopped returns 409. These controls operate on the selected namespace's main branch; provider operations can use shared branches.

GET /__admin/docker/waits reports the namespace's pending wait and termination-reply handles. Aborting the request or canceling its response body releases its wait handle. Reset cancels waits in the reset namespace; runtime.close(), DockerAPI.close() and createServer().close() cancel owned waits. Canceled bodies reject rather than fabricate an exit code. Wait handles are transient and never serialized. Checkout and snapshot restore cancel handles owned by the restored instance before replacing its state; subsequent completion cannot satisfy an old wait. Generated parity excludes blocking waits; deterministic Fetch and real HTTP tests cover their completion/cancellation behavior. The real Engine oracle separately checks selected wait and termination outcomes; cancellation coverage is synthetic.

Stop, signals and removal

POST /containers/<id>/stop records an accepted stop request without changing Running. The HTTP request remains pending until the explicit completion control finishes execution, then returns 204. Already-stopped containers return 304. signal selects the requested signal (default Config.StopSignal or TERM); t selects the timeout (default Config.StopTimeout or 10 seconds; negative means no escalation timeout). The emulator records these parameters for scenario inspection. It does not schedule real timers or automatically declare exit when a timeout expires: script graceful completion or forced completion, including the exit code, through /__admin/docker/containers/<id>/complete. This permits controlled delayed termination without fabricating an early Docker success response.

POST /containers/<id>/kill defaults to SIGKILL and keeps its 204 reply pending until completion; explicit KILL/9 behaves identically. Other supported Linux signal names/numbers acknowledge delivery with 204 while preserving execution state. The fixture decides the subsequent process response. No host signal is sent. Killing a stopped container returns 409. Invalid kill signals return 400; pinned Engine stop signal errors and malformed t return 500. Timeouts outside JavaScript's safe integer range return explicit emulator-only 501.

DELETE /containers/<id> removes a stopped record and releases its name (204). Running or paused records require force=true; otherwise removal returns 409. Forced removal records intent and waits for explicit completion, then removes the record and wakes both exit and removed waiters. A second removal while forced removal is pending returns 409. Seed status removing to model an existing removal conflict. Missing records return 404. v is accepted but no host volumes exist; link=true is outside this subset and returns emulator-only 501.

GET /__admin/docker/containers/<id>/termination returns the last accepted request (operation, numeric signal, optional timeout and request time), removalPending, and simulated: true. These diagnostic fields are not Docker wire fields. Canceling a stop/kill/removal reply releases only the reply handle. Accepted intent and running state survive socket loss; explicit completion still applies, including pending forced removal. Reset clears the namespace and cancels its pending replies. Closing a runtime/server cancels replies without claiming exit. Accepted-mutation history survives response loss through shared Timeline checkpoints, as described under failure scenarios below.

Controls

  • GET /__admin/health reports readiness and service identity.
  • Select independent state with x-mockingbird-namespace or /__admin/ns/<name>/….
  • POST /__admin/reset resets the selected namespace's records and Timeline; ?all=1 resets all namespaces. It preserves clock, fault configuration and journal.
  • POST /__admin/clock accepts shared set, advance, and freeze controls.
  • POST /__admin/faults configures faults by operation, path, or method. DELETE /__admin/faults clears them. Docker fault presets are described below.
  • GET /__admin/requests exposes metadata, never request bodies or query values; DELETE /__admin/requests clears the selected journal.
  • POST /__admin/checkpoints, POST /__admin/branches/<name>, and POST /__admin/branches/<name>/checkout use the shared Timeline coordinator. There is no Docker-local history manager. Pass { "checkpoint": "cp_…" } to checkout.

The shared adminKey option gates admin routes. Docker credential-based namespace selection, webhooks and provider credentials are not configured.

API

The portable @crvouga/mockingbird-service-docker entry exports:

  • DockerAPI: provider Fetch handler with fetch, reset, and close.
  • DOCKER_NAMESPACE: default storage namespace (docker).
  • createRuntime: provider plus standard Mockingbird controls and Timeline.
  • document: annotated OpenAPI contract.
  • operationIds: all inventoried operation IDs, including unsupported routes.
  • supportedOperationIds: currently implemented operation IDs.

The Node-only @crvouga/mockingbird-service-docker/server entry exports:

  • createServer: HTTP server with runtime, url, port, host, server, close, attachments(), and optional Unix socketPath.
  • DEFAULT_PORT: CLI default port, 8826 (programmatic default is ephemeral).
  • serveTarget: shared CLI server configuration.

Type exports include DockerAPIOptions, DockerRuntime, DockerRuntimeOptions, OperationId, SupportedOperationId, and the Node entry's DockerServer and DockerServerOptions, AttachStreamOptions, and DockerAttachment. The executable mockingbird-docker provides serve.

Deliberately not modelled

Fetch attach and unsupported options return Mockingbird-specific 501 JSON errors; unknown routes return 404. The emulator never starts real containers or executes commands. The Node server supports the documented non-TTY attach handshake and scripted duplex streams; a Fetch response cannot represent that upgrade.

Image builds/pulls, registry access, exec, TTY streams, log replay, real networks, mounts, volumes, resource enforcement, peer credentials and host isolation are outside this package. Reported rootless/security settings and HostConfig fields are synthetic metadata. Logical restart controls do not reproduce real daemon restart or live-restore. No Initiative orchestration or recovery policy is verified.

Failure scenarios and logical restart

For each operation create, start, stop, kill, and remove, install a one-use preset through POST /__admin/faults with {"preset":"docker_<operation>_pre_failure"} or {"preset":"docker_<operation>_accepted_drop"}. The first returns 503 before mutation. The second lets validation and mutation run, captures the accepted state in shared Timeline, then drops the reply. Failed validation and unchanged operations do not become accepted mutations. Inspect state after a lost reply; acceptance alone does not establish container exit or removal.

The shared journal records lost replies with status 0, accepted: true, the acceptance checkpoint, container ID, and fault ID. Ordinary rejected operations do not gain an acceptance checkpoint. Pending termination intent is checkpointed before waiting for completion, including when the caller later disconnects.

POST /__admin/docker/restart requires an explicit containers choice: "preserve" retains execution state and accepted intent; "terminate" completes running containers with exitCode (default 137), applying pending removal and AutoRemove. Both cancel existing wait/reply handles, set daemon availability to true, and checkpoint the selected namespace's main branch. Invalid input changes nothing. Daemon availability changes also checkpoint independently of execution. These are synthetic scenario controls, not claims about a real daemon's restart policy or live-restore configuration. No daemon or host process is restarted.

Node Unix-socket transport

The programmatic Node entry supports createServer({ socketPath }) on Unix. Supply an absolute, absent path (at most 103 UTF-8 bytes) in a test-owned directory. It refuses existing files, symlinks and sockets, including stale sockets; it never unlinks them to make room. Do not use a host Engine path. The operating system and Node remove the bound socket when await server.close() completes. Close also cancels runtime waits and destroys owned connections; repeated close calls share one shutdown operation. Tests must close their server in a finally block.

Use Node HTTP request({ socketPath, path: "/_ping" }) for Unix requests. The returned socketPath identifies the endpoint; url is the synthetic HTTP origin http://docker.mock and port is 0. For TCP, omit socketPath and use host/port as before. Combining Unix and TCP options rejects. Retained sequential HTTP/1.1 requests work on both transports. Each connection permits one active request; pipelining another request before its response finishes closes that connection before dispatching the extra request. A deliberate drop closes the connection; the transport does not reconnect clients.

createServer bounds bodies to maxBodyBytes (default 1 MiB, including chunked input), with a bodyTimeoutMs receive deadline (default 30 seconds). Rejections return transport-specific 413 or 408 and close the connection without invoking a provider operation. Response waits have no artificial execution deadline. Header size is limited to 16 KiB, header/request receive time to 30 seconds, idle keep-alive to 5 seconds, and simultaneous connections to maxConnections (default 128). These limits are Mockingbird controls, not Docker Engine parity claims. The existing CLI/shared fleet target still uses the shared TCP adapter; Unix sockets and these Docker transport limits currently require createServer. No peer credentials, procfs provenance, host isolation or real Engine access is claimed. Attach framing and lifetime controls are described below.

Node attach handshake

Send POST /v1.52/containers/<id>/attach?stream=1&stdout=1&stderr=1 with Connection: Upgrade and Upgrade: tcp to createServer. Unversioned attach uses the same pinned contract. The response is 101 UPGRADED with Content-Type: application/vnd.docker.multiplexed-stream, Connection: Upgrade, and Upgrade: tcp. Read through the first CRLFCRLF and retain all following bytes. This duplex upgrade cannot be represented by a Fetch Response; ordinary Fetch and non-upgrade requests continue to return an explicit 501.

stream=true, logs=false, and stdin/stdout/stderr selections are accepted for handshake negotiation. TTY, replay, detachKeys, unknown query modes and non-TCP upgrades are unsupported. Missing containers and paused/restarting conflicts use the pinned pre-upgrade plain-text error envelope with raw-stream content type. Shared namespaces, branches, faults, daemon availability and request logging apply. Attach admission is journaled as 101 and does not itself checkpoint a mutation.

For protocol fixtures, attachHandshake: { chunkBytes, initialStreamBytes } splits header writes into 1–4096-byte chunks and appends at most 1 MiB of synthetic already-framed bytes to the last fragment. This does not promise OS packet boundaries. The bytes are caller-owned wire fixtures, not generated container output. Use the live attachment controls below for framing, channel routing and scripted stdin. No command interprets the input, and payload bytes never enter the journal. Connection loss does not stop the container. Owned sockets close on server shutdown. The emulator never connects to a real Engine; the separately invoked oracle performs the authorized provider comparison.

Scripted attach streams

server.attachments() returns current Node-owned handles. Each exposes an id, containerId, public namespace and branch, plus closed, stdinClosed and queuedBytes. Retain a handle only for that attachment; it cannot address a restored container or a new execution after its lifetime ends.

import type { DockerServer } from "@crvouga/mockingbird-service-docker/server"

// Call after a client attaches to a running container on this server.
export async function writeAttachedOutput(server: DockerServer) {
  const [attachment] = server.attachments()
  if (attachment) {
    await attachment.write("stdout", new TextEncoder().encode("synthetic output"))
    const rawInput = attachment.takeStdin()
    await attachment.end()
    return rawInput
  }
}

write("stdout" | "stderr", bytes) emits the selected channel as an eight-byte Docker header (channel 1/2, three reserved zero bytes, uint32 big-endian length) followed by the exact payload. Empty frames work; concurrent writes are serialized without frame interleaving. Unselected output is ignored. Await each write for backpressure. attachStreams.frameChunkBytes splits frames for protocol tests (default 64 KiB); OS packet boundaries remain uncontrolled.

Incoming stdin is raw, including bytes read ahead with the HTTP upgrade. Input requires stdin=1 and container OpenStdin=true; otherwise it is ignored. takeStdin() drains the bounded synthetic input buffer. No program is executed. With StdinOnce=true, input EOF closes modeled container stdin and checkpoints that state without declaring exit; output may continue until explicit completion. Without StdinOnce, effective stdin EOF ends that attachment and leaves container input reusable. Starting a new execution reopens modeled input. Transport loss alone never invokes container completion.

end() scripts output EOF after queued frames. For non-TTY StdinOnce containers, it retains the connection until explicit process completion, matching the pinned handler's wait. cancel() immediately disconnects just this attachment. Process completion ends its attachments after queued output. Reset, snapshot restore, checkout, synthetic restart and shutdown immediately invalidate the affected physical instance's handles; other namespaces/branches remain isolated. Queued stale writes reject. Socket handles and buffered payloads are never snapshotted.

attachStreams.maxQueuedBytes and maxStdinBytes default to 1 MiB. Queued output includes frame headers; exceeding the output limit rejects the write. Exceeding unread stdin capacity cancels that attachment. Stream limits must be positive integers no greater than 16 MiB. These bounds are emulator resource controls.

Verification boundaries

bun run build && bun test exercises the modeled contract without an Engine or credentials. Generated OpenAPI self-parity asserts that all eight eligible operations are planned and exercised: list, create, inspect, start, info, version, GET ping and HEAD ping. Generated walks include error paths; an exercised count alone does not establish successful creation or state transitions. A seeded nonempty-list comparison additionally rejects a deliberately wrong, schema-valid execution state. The independent wire scenarios cover successful mutations.

test/node-consumer.mjs runs under native Node against the built server entry, once over loopback TCP and once over a test-owned Unix socket. It uses Node HTTP requests on a retained connection and raw attach bytes. It verifies lost create and start responses, lookup by name, duplicate-name conflict, framed binary stdout/stderr, raw stdin, continued execution after attachment loss, and lost stop/kill/remove responses followed by completion and re-inspection. Setup and completion use public emulator admin controls; attachment handles only script output and observe synthetic stdin. Public request metadata confirms each dropped mutation was accepted and checkpointed. No client assertion imports provider handlers or internal state. The fixture never automatically retries a mutation.

Wait/stop/kill/remove have deterministic lifecycle and wire tests rather than unbounded generated walks. Attach uses dedicated raw-wire tests because Fetch cannot represent duplex HTTP upgrade. The declared Docker consumer is raw-socket, so no SDK compatibility is claimed or substituted for wire coverage. Self-parity and synthetic consumer tests are separate from real Engine differential evidence recorded by the oracle, and do not verify Initiative recovery policies or host enforcement.

The opt-in real Engine oracle requires an explicitly authorized current Engine endpoint and immutable image. The recorded Engine 29.8.0 run passed 29 selected API 1.52 comparisons; see the evidence record for scope and limitations. Engine identity fields and full API 1.56 compatibility are not claimed.