// STRYKE-DOCKER — ENGINEERING REPORT

Opt-in stryke connector package · cdylib libstryke_docker.{dylib,so} (crate-type = cdylib) · dlopen'd in-process on use Docker · JSON-over-FFI envelope

>_EXECUTIVE SUMMARY

stryke-docker is one of the opt-in connector packages in the stryke ecosystem. Docker client for stryke. Containers, images, networks, volumes, logs, exec, prune against any reachable Docker daemon (Docker Desktop, Linux daemon, Podman with the docker-API socket, remote DOCKER_HOST). The heavy bollard / tokio code ships as a Rust cdylib that stryke dlopens in-process on first use Docker.

bollard + tokio for full daemon-API coverage — opt-in so the stryke core binary stays under 40 MB.

0.19.0
Version
cdylib
Crate type
opt-in
Tier
dlopen
Load model
78
FFI exports
JSON-over-FFI
Envelope

~ARCHITECTURE

In-process cdylib design: the stryke side is a thin .stk wrapper that calls FFI symbols on the dlopen'd library. No subprocess, no pipe — calls return on the same thread that made them.

LayerImplementation
stryke wrappers (lib/*.stk)Thin .stk wrapper (Docker) exposing typed helpers across containers, images, networks, volumes, logs, exec, and prune; each serializes args to JSON and parses the response
cdylib (libstryke_docker.{dylib,so})Single Rust cdylib crate; every public surface fn is an extern "C" fn docker__<verb>(*const c_char) -> *mut c_char
Process modelLibrary is dlopen'd once per stryke session on first use Docker; a shared tokio runtime + a bollard::Docker client cache keyed by DOCKER_HOST held in OnceCell reuse the same client + HTTP pool across calls; lives until the runtime exits
BuildCargo with [lib] crate-type = ["cdylib"]; publish = false; the package itself ships via s pkg install -g .
Install path~/.stryke/store/docker@<version>/ after make install; use Docker from any stryke script resolves it
Testszunit-style under t/ with live-service variants when applicable
CIGitHub Actions .github/workflows/ci.yml — cargo fmt + clippy + test, plus stryke pkg install verification

$WHY OPT-IN (NOT BUILTIN)

bollard + tokio for full daemon-API coverage — opt-in so the stryke core binary stays under 40 MB.

The trade-off is intentional. The core stryke binary stays under ~40 MB precisely because each connector ships separately. Daily-driver work (one-liners, awk replacement, data scripting) doesn't need MongoDB drivers, AWS SDKs, or Spark Connect bindings linked in.


&FFI ENVELOPE

JSON in / JSON out across the FFI boundary. Each Docker::* wrapper serializes its args to a JSON dict and calls the matching docker__* symbol resolved out of libstryke_docker.{dylib,so}; the cdylib returns a JSON CString the wrapper parses. Dispatch is by symbol name — there is no verb field in the payload; the wrapper sends the call-specific keys (container, image, tail, …) plus an optional host connection override. The exports cover containers (including network attach/detach, TTY resize, exec inspect), images (including search + registry inspect), networks, volumes, exec, logs, and prune, plus the daemon-free parse/validate/build helpers; the authoritative list is [ffi].exports in stryke.toml (78 exported symbols). The returned CString is freed via the cdylib-exported stryke_free_cstring, wired automatically by stryke's cdylib loader.

# request shape (built by the .stk wrapper; dispatched by symbol name)
docker__stop({"container": "web", "timeout": 5})

# success — the per-fn result object, returned as-is (NOT wrapped in {"result": ...})
{"ok": true}

# failure — wrapper _decode() die()s with the message
{"error": "<msg>"}

A handler panic is caught (catch_unwind) and returned as the same {"error": "…"} shape, so a bad call cannot abort the host process. Null or malformed arguments decode to Value::Null rather than segfaulting — both paths are pinned by the crate's FFI safety tests.


+PERSISTENT STATE

Two process-lifetime singletons replace the v1 fork-per-call helper:

SingletonRole
RUNTIMEOne shared tokio multi-thread runtime drives every async daemon call; the blocking FFI entrypoints block_on against it.
CLIENTSbollard::Docker cache keyed by DOCKER_HOST (socket path / tcp url). The same client + underlying HTTP connection pool is reused across calls instead of reconnecting per operation.

=SCOPE: DAEMON OPS vs PURE HELPERS

The surface splits cleanly. Daemon ops need a reachable socket and die on daemon errors. The pure helpers — the parse_* / valid_* / build_* / normalize_* / format_* family for Docker's CLI spec grammars (image refs, ports, mounts, env, labels, devices, tmpfs, restart policies, platforms, memory, durations, cpus, signals, ulimits) — touch no socket, are deterministic, and are what cargo test exercises without a live daemon.

CapabilityStatus in 0.19.0
Snapshot ops (ps, inspect, logs, exec, lifecycle, stats --no-stream, prune)shipped
Pure parse/validate/build helpersshipped
Streaming (logs_follow, events)deferred — needs a callback FFI shape
Registry-auth / tar (push, build)deferred — needs auth / tar-stream design
TLS client certs (DOCKER_CERT_PATH / DOCKER_TLS_VERIFY)roadmap

Calling a deferred op raises a die naming the op and the missing substrate — scripts fail loudly rather than returning partial data.


/SCOPE

See the README's "Why this is a package" + "API reference" sections for the authoritative scope. The package is intentionally narrower than its underlying SDK / driver — the goal is "useful from a shell pipeline", not "complete API coverage".


#PROJECT METADATA

ItemValue
Version0.19.0 (stryke.toml / Cargo.toml; publish = false)
FFI exports78 docker__* symbols ([ffi].exports in stryke.toml)
LicenseMIT
AuthorMenkeTechnologies
Repositorygithub.com/MenkeTechnologies/stryke-docker
Parent languagestrykelang
Meta umbrellaMenkeTechnologiesMeta
Issuesgithub.com/MenkeTechnologies/stryke-docker/issues