Phase 5: Docker Compose runtime
Implemented
- Added driver-neutral runtime plans, observations, logs, metrics, and event
contracts under
internal/runtime/domainand application orchestration underinternal/runtime/application. - Added trusted catalog-to-runtime resolution; pending projects cannot execute repository-derived runtime configuration.
- Added Compose config normalization, root-contained file construction, Docker context resolution, API negotiation, and clear disconnected-engine behavior.
- Added shell-free start, stop, restart, pause, unpause, rebuild, and teardown plans using the installed Compose CLI.
- Added trusted optional-profile selection for start/rebuild. Project-wide stop and teardown use Compose’s all-profile selector so optional services cannot be orphaned.
- Added Engine SDK observation keyed exclusively by canonical Compose labels, including external-origin recognition, health, exit state, restart count, container metadata, and published ports.
- Compose observation, log, and metric membership is intersected with the normalized default-profile service set, so stale containers from inactive profiles cannot degrade or pollute the active project runtime. Active optional-profile containers remain visible.
- A successful stop is treated as deliberate lifecycle intent, preventing a
forced container exit code from turning a stopped project into
failed. Durable Docker OOM metadata distinguishes a forced stop from a real signal-137 memory failure after daemon restart. - Added bounded Docker stdout/stderr streaming with service/run identity and current CPU, memory, and network sampling.
- Added dynamically maintained Engine event subscriptions; a project-labelled
event triggers targeted live observation and a durable
runtime.observedevent. - Added REST/OpenAPI and CLI status, plan, operation, log, and metric contracts. Runtime mutations use the durable per-project operation coordinator.
- Added a deterministic offline Docker fixture that cross-compiles a static health server and builds a scratch image.
Files and modules added
internal/runtime/domaininternal/runtime/applicationinternal/runtime/composeinternal/bootstrap/runtime_events.gointernal/transport/httpapi/runtime_handler.gointernal/transport/cli/runtime_commands.gotest/fixtures/compose-runtimetest/integration/compose_runtime_test.godocs/architecture/docker-compose-runtime.md
Architecture decisions
No new ADR was required. The implementation realizes accepted ADR-0006. The
supported Docker modules are github.com/moby/moby/client and
github.com/moby/moby/api; the deprecated pre-Docker-29 monolithic SDK path is
not used.
The Compose CLI remains the lifecycle authority. The Engine SDK remains the observation/stream authority. Runtime state is derived rather than persisted as truth, and current-session ownership is conservative.
Tests added
- Runtime action validation and application driver routing.
- Dynamic watcher identity attachment.
- Compose action/risk/effect command construction and root-containment checks.
- Trusted profile allowlisting, all-profile stop/teardown commands, and deliberate-stop state derivation.
- Context/config normalization and deterministic service ordering.
- Label-only membership, one-off exclusion, external recognition, disconnected observations, inactive-profile exclusion, state derivation, and published-port mapping.
- Docker log demultiplexing, timestamp/level handling, identity preservation, and CPU/network calculations.
- Event label filtering and durable operation progress-state compatibility.
- HTTP durable-operation and side-effect-free plan authorization behavior.
- Full opt-in Docker integration lifecycle with unconditional cleanup.
Commands run and results
make qualityPASS: generated drift, formatting, vet, golangci-lint, architecture checks,frontend lint/typecheck/tests, Go tests and race tests, migration smoke,govulncheck, Playwright E2E/visual tests, production web build, packaged binary
go test ./internal/runtime/... ./internal/bootstrap ./internal/transport/httpapi ./internal/transport/cliPASS
SWITCHYARD_DOCKER_INTEGRATION=1 go test -tags=integration ./test/integration \ -run TestComposeRuntimeLifecycleObservationLogsMetricsAndExternalRecognition \ -count=1 -vPASS: full lifecycle and cleanup in 22.03 seconds
GOOS=linux GOARCH=amd64 go build -trimpath -o /tmp/switchyard-linux-amd64 ./cmd/switchyardGOOS=windows GOARCH=amd64 go build -trimpath -o /tmp/switchyard-windows-amd64.exe ./cmd/switchyardPASS: Linux and Windows amd64 cross-buildsPackaged CLI/daemon verification used a fresh SQLite data directory and private Unix socket. It completed add, trust, plan, durable start, healthy status, JSONL logs, metrics, pause, unpause, restart, rebuild, stop, restart-from-stop, and teardown-with-volumes. An initial start exposed an invalid progress-state word, which was corrected and locked down by a regression test; every rerun operation succeeded. The final fixture resource query returned no containers, networks, or volumes.
Acceptance criteria status
- A Compose fixture starts, becomes healthy, stops, restarts, rebuilds, and tears down through both the driver integration and packaged durable path.
- Stop preserves containers/volumes; teardown removes exactly the resources shown in its plan, including opt-in volumes.
- A Compose project started outside Switchyard is reported as
running_externalafter health convergence. - Membership uses canonical Compose project/service labels and excludes one-off containers; no container-name parsing determines membership.
- Docker disconnection produces a bounded unknown observation or typed unavailable result and cannot crash the daemon or block other drivers.
Known limitations
- SSH Docker contexts are rejected with an actionable error because the Engine SDK does not natively load Docker CLI SSH connection helpers.
- Contexts that explicitly disable TLS verification are refused. Local Unix and verified TCP/TLS contexts are supported.
- Runtime ownership is intentionally session-local until durable run identity is added with native-process run records.
Deferred work
- Persistent rotating log segments, redaction, retention, WebSocket log follow, historical metrics, and health scheduling are Phase 7.
- Native process runtimes and persistent run fingerprints are Phase 6.
- Rich runtime UI states are Phase 9.
Manual verification
make buildswitchyard add test/fixtures/compose-runtimeswitchyard project trust compose-runtime --yesswitchyard plan teardown compose-runtime --volumesswitchyard start compose-runtimeswitchyard status compose-runtime --jsonswitchyard logs compose-runtime --service web --jsonlswitchyard metrics compose-runtime --service web --jsonswitchyard stop compose-runtimeswitchyard teardown compose-runtime --volumes --yes