Resolve the deferred docker system events subscription question. docker/system/events is now a v1 Subscription operation using the same StreamingHandler pattern already wired for logs, exec, and image/pull. The internal ownership-store subscription for stale-entry cleanup on destroy events is a follow-up refinement. Scrub hedging language from ADR-060 and docker specs: - Remove 'marginal gain' / 'future feature is additive' framing - Replace 'no reaper' / 'not promptly cleaned up' with clean statement that events subscription provides the prompt-cleanup path - Remove stale-entry policy from ADR-060's two-way door classification
166 lines
12 KiB
Markdown
166 lines
12 KiB
Markdown
---
|
|
status: draft
|
|
last_updated: 2026-07-08
|
|
---
|
|
|
|
# alknet-docker
|
|
|
|
The docker operations crate for the ALPN-as-service architecture: a
|
|
thin, single-host bollard wrapper that exposes docker container and
|
|
image operations as call-protocol operations on the shared
|
|
`alknet/call` ALPN, plus (behind a `tty` feature) a `DockerTtyBackend`
|
|
implementing `alknet-tty`'s `TtyBackend` trait for interactive terminal
|
|
sessions into containers.
|
|
|
|
## What
|
|
|
|
alknet-docker does two things:
|
|
|
|
1. **Call operations.** A set of `OperationSpec`-registered operations
|
|
(`docker/container/*`, `docker/image/*`, `docker/system/events`) on
|
|
the shared `alknet/call` ALPN, mapping bollard's docker API to the
|
|
call protocol's `Query` / `Mutation` / `Subscription` dispatch paths.
|
|
Lifecycle (create/start/stop/remove/list/inspect) is `Query`/
|
|
`Mutation`; logs, non-interactive exec, image pull, and system
|
|
events are `Subscription` (streaming via `StreamingHandler`,
|
|
ADR-049). The operations declare `AccessControl` against the
|
|
ADR-050 container-as-resource model. Decided in
|
|
[ADR-058](../../decisions/058-alknet-docker-on-alknet-call.md).
|
|
|
|
2. **`DockerTtyBackend`** (behind the `tty` feature). An
|
|
`impl TtyBackend` (ADR-053) wrapping `bollard::attach_container()`
|
|
and `bollard::exec::start_exec` with `tty: true`, for interactive
|
|
terminal sessions into containers over `alknet/tty`. This is the
|
|
`TtyBackend` row the alknet-tty spec left open ("future, out of
|
|
scope here"). Decided in
|
|
[ADR-061](../../decisions/061-docker-tty-backend-in-alknet-docker.md).
|
|
|
|
The two use cases the crate serves (per the user's brief and the system
|
|
docs):
|
|
|
|
- **Disposable dev containers** (the common case by volume) — a
|
|
coordinator spawns a container for an implementation agent or an
|
|
isolated env. alknet-docker's `create` records ownership
|
|
(`OwnershipStore::record`); the coordinator owns the container for
|
|
its lifetime; `remove` revokes. Interactive sessions into the
|
|
container go through `DockerTtyBackend` on `alknet/tty`.
|
|
- **Long-running hosted services** (the production server case) —
|
|
rarely-changing services (reverse-proxy, postgres, redis, gitea on
|
|
dev1, per `/workspace/system/dev1/docker.md`) created by an operator
|
|
via `docker compose`, not via alknet. alknet-docker's operations
|
|
(start/stop/inspect/logs) manage them; the operator role reaches them
|
|
via the static-resource fallback (ADR-060 §3).
|
|
|
|
## Documents
|
|
|
|
| Document | Status | Description |
|
|
|----------|--------|-------------|
|
|
| [overview.md](overview.md) | draft | Crate purpose, two-role design (call ops + DockerTtyBackend), dependencies, ALPN, label namespace, feature gates, assembly-layer wiring |
|
|
| [docker-operations.md](docker-operations.md) | draft | The operation surface: lifecycle (Query/Mutation), logs/exec/pull (Subscription via StreamingHandler), access control, label namespace, teardown coupling |
|
|
| [docker-tty-backend.md](docker-tty-backend.md) | draft | `DockerTtyBackend` (impl `TtyBackend`): attach vs exec mode, `TtyHandle` field mapping, `TtyControl` → bollard resize/signal, `exit_code` Drop-kill (ADR-056) |
|
|
|
|
## Applicable ADRs
|
|
|
|
| ADR | Title | Relevance |
|
|
|-----|-------|-----------|
|
|
| [058](../../decisions/058-alknet-docker-on-alknet-call.md) | alknet-docker Registers on `alknet/call` | Docker ops are call-protocol operations, not a separate ALPN; raw-carriage dissolved by alknet-tty extraction |
|
|
| [059](../../decisions/059-bollard-021-dependency-and-features.md) | bollard 0.21 Dependency and Feature Selection | Version pin (0.21, verified current); features `http`+`pipe`+`time`, no `ssl`/`ssh`/`websocket`/`buildkit` |
|
|
| [060](../../decisions/060-container-resource-model-and-label-namespace.md) | Container Resource Model and Label Namespace | ADR-050 application: `alknet.managed`/`alknet.owner` labels; `list` owned_only flag; hosted-services static-resource fallback; handler-driven revoke + autonomous-death tolerance |
|
|
| [061](../../decisions/061-docker-tty-backend-in-alknet-docker.md) | DockerTtyBackend in alknet-docker | Backend in alknet-docker behind `tty` feature; attach/exec mode split; POC `drive_attach_raw` as reference |
|
|
| [003](../../decisions/003-crate-decomposition.md) | Crate Decomposition | alknet-docker depends on alknet-core + alknet-call (ops) and alknet-tty (tty feature); no handler-depends-on-handler violation |
|
|
| [012](../../decisions/012-call-protocol-stream-model.md) | Call Protocol Stream Model | The wire format docker ops use (`EventEnvelope`, no `carriage` field) |
|
|
| [017](../../decisions/017-call-protocol-client-and-adapter-contract.md) | Call Protocol Client and Adapter Contract | `from_call` re-export — the proxy pattern's mechanism for hub→worker docker management |
|
|
| [023](../../decisions/023-operation-error-schemas.md) | Operation Error Schemas | Docker ops declare domain error codes (CONTAINER_NOT_FOUND, IMAGE_NOT_FOUND, etc.) |
|
|
| [024](../../decisions/024-operation-registry-layering.md) | Operation Registry Layering | Docker ops register in the curated Layer 0 at startup |
|
|
| [029](../../decisions/029-peer-graph-routing-model.md) | Peer-Graph Routing Model | Head→worker docker management via `PeerRef` / `invoke_peer` |
|
|
| [032](../../decisions/032-forwarded-for-identity.md) | Forwarded-For Identity | End-user identity as metadata when a coordinator proxies docker ops |
|
|
| [049](../../decisions/049-streaming-handler-for-subscriptions.md) | Streaming Handler for Subscriptions | `StreamingHandler` for logs/exec/pull; exit code on final `call.responded` |
|
|
| [050](../../decisions/050-dynamic-resource-ownership-for-runtime-spawned-resources.md) | Dynamic Resource Ownership | Containers as `AccessControl` resources; the model ADR-060 applies |
|
|
| [052](../../decisions/052-alknet-tty-wire-format-and-two-carriage.md) | alknet-tty Wire Format | The `alknet/tty` wire format `DockerTtyBackend` sessions use |
|
|
| [053](../../decisions/053-ttybackend-trait-and-ttyhandle.md) | TtyBackend Trait and TtyHandle | The trait `DockerTtyBackend` implements |
|
|
| [055](../../decisions/055-exit-code-on-control-chunk.md) | Exit Code on a Control Chunk | The exit-chunk ordering `DockerTtyBackend`'s `exit_code` feeds into |
|
|
| [056](../../decisions/056-backend-cleanup-on-session-cancel.md) | Backend Cleanup on Session Cancel | `DockerTtyBackend`'s `exit_code` future `Drop` kills the container/exec |
|
|
| [014](../../decisions/014-secret-material-flow-and-capability-injection.md) | Secret Material Flow and Capability Injection | `Capabilities` is for secret material only — the `Docker` handle is not secret (ADR-062) |
|
|
| [062](../../decisions/062-docker-client-injection-via-closure-capture.md) | Docker Client and OwnershipStore Injection via Closure Capture | Closure capture at registration; `Capabilities` for secrets only; matches `from_openapi` pattern |
|
|
| [063](../../decisions/063-exit-code-on-terminal-call-responded.md) | Exit Code on a Terminal `call.responded` for Non-Interactive Exec | `{ "exitCode": N, "terminal": true }` on final `call.responded`; `call.completed` stays empty |
|
|
|
|
## Relevant Open Questions
|
|
|
|
| OQ | Title | Status | Relevance |
|
|
|----|-------|--------|-----------|
|
|
| OQ-048 | Network and volume operation surface | deferred(scope) | Network/volume CRUD deferred; v1 is containers + images |
|
|
| OQ-049 | Image build (buildkit) scope | deferred(scope) | `buildkit` feature deferred; v1 has `image/pull` + `image/list` + `image/inspect` |
|
|
| OQ-050 | Docker system events subscription | resolved | `docker/system/events` included in v1 as a `Subscription` operation; internal ownership-store subscription for cleanup is a follow-up refinement |
|
|
| OQ-051 | Container create options surface | deferred(scope) | Full `CreateContainerOptions` (mounts, port bindings, networks) surface deferred to v1 implementation |
|
|
|
|
## Key Design Principles
|
|
|
|
1. **Single-host, bollard-specific.** alknet-docker talks to one local
|
|
docker daemon over `/var/run/docker.sock`. The fleet case (multiple
|
|
daemons on different machines) is a call-protocol concern — a
|
|
`CallClient` per remote daemon, each running alknet-docker locally
|
|
— not a bollard-feature concern. No `ssl`/`ssh` features, no remote
|
|
daemon over TLS. See [overview.md](overview.md) and
|
|
[ADR-058](../../decisions/058-alknet-docker-on-alknet-call.md).
|
|
|
|
2. **Call operations on `alknet/call`, not a separate ALPN.** Docker
|
|
ops are ordinary call-protocol operations with `OperationSpec`s,
|
|
`AccessControl`, and service discovery. They inherit `from_call`
|
|
re-export and peer routing. The one operation that didn't fit
|
|
(interactive attach) moved to `alknet/tty` via `DockerTtyBackend`.
|
|
No `carriage` field on `call.requested`, no parallel dispatch
|
|
surface. See [ADR-058](../../decisions/058-alknet-docker-on-alknet-call.md).
|
|
|
|
3. **Containers are runtime-spawned resources (ADR-050).** `create`
|
|
records ownership; `remove` revokes; `exec`/`stop`/`inspect` check
|
|
ownership via `OperationSpec.resource_id_path`. The
|
|
hosted-services case (operator-managed, pre-existing containers)
|
|
works through the static-resource fallback. Two labels
|
|
(`alknet.managed`, `alknet.owner`) mark alknet-spawned containers
|
|
for the `list` filter and the cross-check. See
|
|
[docker-operations.md](docker-operations.md) and
|
|
[ADR-060](../../decisions/060-container-resource-model-and-label-namespace.md).
|
|
|
|
4. **Interactive terminal is a tty concern, not a docker concern.**
|
|
`DockerTtyBackend` implements `alknet-tty`'s `TtyBackend` trait,
|
|
behind a `tty` feature. The wire format, the session lifecycle, and
|
|
the exit-chunk ordering live in alknet-tty; the backend produces
|
|
bollard-backed handles. This is the same inversion as
|
|
`LocalTtyBackend` (local process) and `SshTtyBackend` (SSH). See
|
|
[docker-tty-backend.md](docker-tty-backend.md) and
|
|
[ADR-061](../../decisions/061-docker-tty-backend-in-alknet-docker.md).
|
|
|
|
5. **bollard 0.21, verified current.** The POC used a local 0.21
|
|
checkout; the crate depends on published 0.21 from crates.io
|
|
(verified as latest). Features: `http` + `pipe` (default, local
|
|
daemon) + `time` (log timestamps). No `websocket` (reliable attach
|
|
only), no `ssl`/`ssh` (fleet is call-protocol), no `buildkit`
|
|
(deferred). See [ADR-059](../../decisions/059-bollard-021-dependency-and-features.md).
|
|
|
|
6. **Mechanical mapping, no feasibility risk.** The POC validated the
|
|
hard parts (raw carriage attach, logs subscription, exec with exit
|
|
code). The remaining lifecycle operations (create/start/stop/remove/
|
|
list/inspect) are mechanical bollard wrapping — `Query`/`Mutation`
|
|
with single `call.responded` responses, "the boring case" (POC §"What
|
|
the POC Does NOT Validate" #4). See
|
|
[docker-operations.md](docker-operations.md).
|
|
|
|
## References
|
|
|
|
- `docs/research/alknet-docker/poc-summary.md` — the POC that validated
|
|
the hard parts (interactive attach, logs subscription, exec with exit
|
|
code) and surfaced the open unknowns this spec set resolves
|
|
- `/workspace/alknet-docker-poc/` — the POC source (`src/ops.rs`
|
|
`DockerOps`, `src/raw.rs` chunk codec, `src/frame.rs` EventEnvelope
|
|
mirror, `tests/integration.rs` 6 tests against a live daemon)
|
|
- `/workspace/bollard/` — bollard 0.21.0 source (the local checkout the
|
|
POC used; identical API surface to the published 0.21)
|
|
- `/workspace/@alkdev/dispatch/` — the dispatch POC (bollard 0.18,
|
|
`dispatch.managed=true` labels, SSH-tunnel fleet model — the prior
|
|
art this crate generalizes and the friction it removes)
|
|
- `/workspace/system/dev1/docker.md` — the production hosted-services
|
|
use case (reverse-proxy, postgres, redis, gitea on dev1)
|
|
- `/workspace/@alkdev/reverse-proxy/deploy/docker-compose.yml` — the
|
|
reverse-proxy's docker setup (operator-created, not alknet-spawned)
|
|
- `docs/architecture/crates/tty/` — the alknet-tty spec
|
|
(`DockerTtyBackend` is the row `tty-backend.md` left open) |