Files
alknet/docs/architecture/decisions/059-bollard-021-dependency-and-features.md
T
glm-5.2 1ce11717d8 docs(arch): draft alknet-docker architecture specs (ADRs 058-063, OQs 048-051)
Greenfield architecture spec set for the alknet-docker crate — a thin,
single-host bollard wrapper exposing docker container/image operations
as call-protocol ops on the shared alknet/call ALPN, plus a
DockerTtyBackend (impl TtyBackend) behind a tty feature for interactive
terminal sessions into containers over alknet/tty.

Six ADRs:
- 058: docker ops on alknet/call (no separate ALPN; raw-carriage handoff
  dissolved by alknet-tty extraction — interactive attach moved to
  alknet/tty via DockerTtyBackend, no carriage field on call.requested)
- 059: bollard 0.21 (verified current on crates.io) + feature selection
  (http+pipe+time; no ssl/ssh/websocket/buildkit)
- 060: container resource model (ADR-050 application) — alknet.managed/
  alknet.owner labels, list owned_only flag, hosted-services operator
  role via static-resource fallback, handler-driven revoke with
  autonomous-death tolerance, resource-action vocabulary
- 061: DockerTtyBackend in alknet-docker behind tty feature (attach vs
  exec mode; POC drive_attach_raw as reference)
- 062: Docker client + OwnershipStore injection via closure capture
  (not Capabilities, not OperationContext — matches from_openapi pattern)
- 063: exit code on terminal call.responded for non-interactive exec
  (call.completed stays empty, ADR-012 unchanged)

Four spec docs (crates/docker/): README, overview, docker-operations,
docker-tty-backend. Four deferred-scope OQs (048-051): network/volume
ops, buildkit, system events subscription, create options surface.

Updates the tty-backend.md Backend implementations table (DockerTtyBackend
row now specced) and the architecture README (doc table, ADR table,
current-state paragraph, OQ count).

Grounded in the alknet-docker POC (docs/research/alknet-docker/poc-summary.md)
which validated the hard parts; the remaining lifecycle ops are mechanical
bollard wrapping. Reviewed by architecture-reviewer subagent; criticals
(the docker_client injection model conflicting with the Capabilities
contract) resolved via ADR-062/063 before commit.
2026-07-08 09:29:22 +00:00

11 KiB

ADR-059: bollard 0.21 Dependency and Feature Selection

Status

Accepted

Context

The alknet-docker POC (docs/research/alknet-docker/poc-summary.md) depended on a local bollard checkout at /workspace/bollard (version 0.21.0). Its "Open Unknowns" #5 noted: "The real crate should depend on published 0.21 from crates.io (the dispatch POC pinned 0.18 — a 3-version jump). The websocket feature is optional; the http and pipe features are needed for socket/http connect. Confirm the published 0.21 has the same API surface as the checkout."

Two concerns motivate an explicit version check:

  1. Agent training-data drift. Implementation agents tend to default to the bollard version represented in their training data unless explicitly told to check. The dispatch POC used bollard 0.18 (3 versions behind); an agent that pattern-matched dispatch would pin 0.18 and silently use a 3-version-stale API. The cost of checking is low (one crates.io lookup); the cost of using a stale version is potentially high (API mismatches, missing methods, security regressions).

  2. API surface stability across the path-vs-published boundary. The POC used a path dependency; the crate uses a published version. A version number match (0.21.0) should mean the API surface is identical, but this is worth confirming rather than assuming.

Version verification

A check against crates.io / docs.rs confirmed:

  • bollard 0.21.0 is the latest published version on crates.io (as of 2026-07-08). The local checkout at /workspace/bollard (0.21.0) matches the published version. No version jump is needed; the POC's dependency is current.
  • The published 0.21.0 Cargo.toml (verified via docs.rs source) has the same API surface the POC used: attach_container (container.rs:540), logs (container.rs:928), create_exec/start_exec/inspect_exec (exec.rs:172/225/315), AttachContainerResults (container.rs:80), StartExecResults enum (exec.rs:99), LogOutput (container.rs:96), NewlineLogOutputDecoder (read.rs:32). The POC's code-to-concept mappings hold against the published version.
  • bollard-stubs is pinned at =1.53.1-rc.29.3.1 (the Docker API 1.53 schema), matching the Docker Engine 29.2.1 / API 1.53 the POC tested against.

Feature selection

bollard 0.21's feature surface (from the published Cargo.toml):

Feature What it pulls Needed for alknet-docker?
default = ["http", "pipe"] hyper-util (http), hyperlocal + hyper-named-pipe (pipe) Yes — the default. pipe connects to the unix socket (/var/run/docker.sock); http connects to a TCP/HTTP daemon. Both are the standard local-daemon paths.
ssl rustls TLS for remote daemon connections No — alknet-docker talks to a local daemon; remote daemons are reached over the call protocol (the fleet layer, not bollard's TLS).
ssh openssh for SSH-tunneled daemons No — the dispatch POC's SSH-tunnel model is exactly what alknet removes; the worker dials the hub over the call protocol instead of the hub SSHing into the worker.
websocket tokio-tungstenite for the websocket attach endpoint No — the POC deliberately used the reliable attach_container() (HTTP upgrade to TCP), not the websocket path (bollard's own docs warn of RFC 6455 compatibility issues). The websocket feature is for browser-attach scenarios alknet-docker doesn't have.
buildkit / buildkit_providerless tonic + bollard-buildkit-proto for buildkit image builds No — image build is deferred (POC "What the POC Does NOT Validate" #5; OQ-049 scopes this).
chrono / time timestamp parsing for log timestamps Optional — docker/container/logs sets timestamps: true; the chrono or time feature types the timestamp field. The choice between them is a two-way-door implementation detail (both are serde-compatible). time is the lighter dependency.
json_data_content serde for body content types No — alknet-docker serializes its own JSON shapes from bollard's types; it doesn't need bollard's content-type tagging.
aws-lc-rs / webpki / test_* TLS provider variants and test-only features No — not for production deps.

The POC's Cargo.toml depended on bollard with default features (path dependency). The crate does the same: bollard = { version = "0.21", default-features = true }, with the time feature added for log timestamp typing. No ssl, ssh, websocket, or buildkit features.

Decision

1. Depend on published bollard 0.21 from crates.io

[dependencies]
bollard = { version = "0.21", features = ["time"] }

default-features = true (the default) pulls http + pipe, the two local-daemon connect paths. The time feature adds timestamp typing for docker/container/logs. No other features are enabled.

The local-checkout path dependency the POC used is retired. The published 0.21.0 has the identical API surface (confirmed via docs.rs source — same version number, same method signatures, same stubs pin).

2. Connect via connect_with_local_defaults() (unix socket)

The standard deployment talks to a local docker daemon over /var/run/docker.sock (Unix) or the named pipe on Windows. Docker::connect_with_local_defaults() handles both. The Docker client is constructed once at assembly-layer startup and injected into the docker ops registration. See overview.md §"Assembly Layer Wiring".

A TCP daemon path (connect_with_http_defaults) is available if a deployment runs the docker daemon on a different host — but this is the exception, not the default. The fleet case (multiple docker daemons on different machines) is handled at the call-protocol layer (a CallClient per remote daemon, each running alknet-docker locally), not by pointing one bollard client at a remote daemon over TCP. See ADR-058 §"Why shared alknet/call" and the POC §6 (the normalization crate boundary).

3. Do not enable websocket, ssl, ssh, or buildkit

  • websocket — the reliable attach_container() (HTTP upgrade to TCP) is the attach path for DockerTtyBackend (ADR-061). The websocket attach endpoint has documented reliability issues (bollard container.rs:577) and is for browser-attach scenarios alknet-docker doesn't have. The raw chunk format (ADR-052) is layered on top of the reliable attach, not the websocket.
  • ssl — remote daemons are reached over the call protocol (the fleet layer), not bollard's TLS. alknet-docker is single-host by construction (POC §6).
  • ssh — the SSH-tunnel model is what the call-protocol fleet layer replaces. The dispatch POC's DockerProvider SSHed into workers; alknet-docker's workers dial the hub over alknet/call and expose their local docker ops directly.
  • buildkit — image build is deferred (OQ-049). The build_image method is in bollard but the buildkit feature pulls tonic + bollard-buildkit-proto, a heavy dependency for an out-of-scope feature.

4. time feature for log timestamps

docker/container/logs sets timestamps: true on the bollard logs() query. Each LogOutput then carries a docker timestamp; the call.responded frame separates timestamp and text into distinct JSON fields (the POC noted this refinement over its single-text-field POC shape). The time feature types the timestamp field as time::OffsetDateTime (serde-compatible). The chrono feature is the alternative; time is lighter and sufficient.

The timestamp-feature choice (time vs chrono) is a two-way-door implementation detail within the one-way version-pin decision. Switching features is a Cargo.toml change, not an API break.

Consequences

Positive:

  • bollard 0.21 is current (verified against crates.io); no stale-version risk. The POC's API-surface mappings hold against the published version.
  • The feature set is minimal: http + pipe (default) + time. No ssl/ssh/websocket/buildkit weight. The compile-time and dependency surface stays small.
  • The single-host contract is clear: alknet-docker talks to one local daemon over the unix socket. The fleet case is a call-protocol concern, not a bollard-feature concern.

Negative:

  • The websocket feature being disabled means the browser-attach scenario (bollard's websocket attach for a browser that speaks docker's websocket framing directly) is not available. This is intentional — browsers reach docker through alknet/tty (via DockerTtyBackend) or the call protocol, not bollard's websocket endpoint. If a future use case forces the websocket path, enabling the feature is a Cargo.toml addition (two-way door).
  • The ssh feature being disabled means bollard's SSH-tunneled daemon path is not available. This is intentional — the dispatch POC's SSH-tunnel model is what alknet's call-protocol fleet layer replaces (POC §6, "Prior art"). Enabling ssh would reintroduce the friction (SSH key injection, port binding) the fleet layer exists to remove.

Door type

One-way (version pin) + two-way (feature set). The version pin (bollard 0.21, not 0.18 or a future 0.22) is a one-way commitment for the implementation: the operation handlers are written against 0.21's API surface, and a major-version bump is a migration. The feature set (http + pipe + time, no ssl/ssh/websocket/buildkit) is two-way-door: features can be added or removed in Cargo.toml without an API break. The decision to not enable ssl/ssh/buildkit is a scope decision (not needed for the current scope), not a capability closure — the features can be added later if a use case forces them.

The time-vs-chrono choice is a two-way-door implementation detail.

References

  • docs/research/alknet-docker/poc-summary.md §"Open Unknowns" #5 (bollard version pinning)
  • docs/research/alknet-docker/poc-summary.md §"What the POC Does NOT Validate" #5 (image management / buildkit deferred)
  • bollard 0.21.0 published Cargo.toml (verified via docs.rs source)
  • bollard source: src/container.rs (attach_container :540, logs :928), src/exec.rs (create_exec :172, start_exec :225, inspect_exec :315), src/read.rs (NewlineLogOutputDecoder :32)
  • ADR-058 — the single-host / call-protocol-fleet contract this feature set reflects
  • ADR-061 — the DockerTtyBackend that uses the reliable (non-websocket) attach path
  • OQ-049 (build/image scope, deferred)
  • Spec: overview.md §"Dependencies"