Per-service API client with identifying headers - #13963
Conversation
Introduce composeService.serviceClient(project, service), a cached APIClient that tags requests with X-Docker-Compose-Project/-Service headers so the daemon can attribute them. Generalizes the pull-only client into shared infrastructure, closed via composeService.Close(), and routes the image pull path through it. Preserves --dry-run by re-wrapping scoped clients in DryRunClient. Signed-off-by: Nick Sieger <nick@nicksieger.com>
Use serviceClient for ContainerCreate, ContainerStart, and the create-time NetworkConnect/ContainerRemove/ContainerInspect calls so each request in the up path carries the project/service identifying headers. Dependency health inspects stay on the shared client (cross-service reads). Signed-off-by: Nick Sieger <nick@nicksieger.com>
docker-agent
left a comment
There was a problem hiding this comment.
Assessment: 🟢 APPROVE
The per-service API client implementation is well-structured and correct. Key aspects reviewed:
- Cache correctness: The mutex-guarded
serviceClientCachecorrectly handles concurrent access from concurrent pull goroutines and container lifecycle calls. Lazy map initialization is safe (inside the lock). - DryRun wrapping:
newServiceClientcorrectly re-wraps the freshly-built client inDryRunClientwhens.dryRun == true, preserving dry-run interception semantics. - Resource lifecycle:
Close()properly drains and closes all cached clients. TheDryRunClient.Close()delegates to the inner client'sClose(), so no resource leak on the dry-run path. - Header precedence: User-configured headers are copied first, then compose-specific headers are set — ensuring compose attribution headers always win, which is the intended behavior.
- API version pinning:
newServiceClientpinsClientVersion()from the shared client, ensuring consistent API version negotiation across all service clients. - Test coverage:
TestServiceClientHeadersexercises header forwarding, CLI header preservation, cache hit/miss, and close semantics.TestResolvePullPlatformscovers the extracted helper.
No bugs introduced by this PR.
|
On the client duplication in
The root cause is that the moby client only supports per-instance headers ( Since the duplication is a temporary workaround, please mark it as such, e.g.: // TODO(compose): temporary workaround duplicating docker/cli's client construction
// because custom headers are per-instance. Remove once request-scoped headers land
// upstream (https://github.com/moby/moby/pull/53347) and enrich ctx at call sites instead.Alternative achieving the same today, with no moby change and no duplication The moby client wraps its transport in
Bonus: compose already injects the global propagator into provider-plugin child processes ( Two caveats: the daemon side must read the |
What I did
Tag the Docker API requests that Compose makes on behalf of a specific service with HTTP headers identifying the project and service the request
originates from:
This lets the daemon (and anything in front of it) attribute an incoming API call to the Compose project/service it came from.
Why
Today every Compose request reaches the daemon indistinguishable from any other. Attributing container-lifecycle calls to their originating service makes daemon-side logging, auditing, and policy far easier, and gives operators a reliable signal for which service triggered a given create/start/pull.
How
The moby client applies custom HTTP headers per client instance, not per request — there is no per-request header hook or request-mutation callback (WithHTTPHeaders is the only lever; the sole hook, WithResponseHook, is response-side). So a single shared client can't carry service-specific headers.
This PR introduces a small per-service client layer:
Call sites routed through the per-service client:
Project-wide calls (networks, volumes, events, project-scoped container listings, Ping) intentionally stay on the shared client. Dependency health-poll inspects (isServiceHealthy/isServiceCompleted) also stay on the shared client, since they read a dependency's containers and don't have unambiguous service attribution.
Testing
Related issue
(not mandatory) A picture of a cute animal, if possible in relation to what you did