DockerCompose: From Versioned Inputs to Typed Observations
Docker Compose connects service configuration with engine operations. Its output can mix resource progress, BuildKit status and application logs, so the command, Environment, version and channel all participate in interpretation.
A Compose service observation is useful for orchestration visibility. It is not automatically a deployment acceptance result.
Acquisition and build inputs
The Design executable collects docker/compose releases, prepares version images, parses Cobra help and supports cached replay. Its current default minimum is 2.20.0.
DockerComposeEnvironmentCatalog.ReadProcessEnvironment configures the Design runner. The Contracts project consumes the same catalogue through Roslyn and receives generated commands; implementation generation supplies builders/client.
The executable identifier in these examples is docker-compose. A host using docker compose needs an appropriate invocation adapter or binding arrangement; changing an executable string to two words is not enough to produce correct process arguments.
A complete event example
This offline example requests container-event JSON and feeds a fixture-shaped event to the real decoder. It starts no services.
using FrenchExDev.Net.BinaryWrapper;
using FrenchExDev.Net.BinaryWrapper.Lib;
using FrenchExDev.Net.BinaryWrapper.Testing;
using FrenchExDev.Net.DockerCompose;
var version = SemanticVersion.Parse("5.1.0");
var binding = new BinaryBinding
{
Identifier = new BinaryIdentifier("docker-compose"),
ExecutablePath = "unused",
DetectedVersion = version,
};
var command = new DockerComposeEventsCommand(version)
{
Json = true,
Environment = new DockerComposeEnvironment { ProjectName = "example" },
};
var source = new OutputLine(
"""{"time":"2026-01-01T00:00:00Z","type":"container","service":"web","id":"abc","action":"start","attributes":{"name":"project-web-1"}}""",
OutputSource.StdOut);
var executor = new CommandExecutor(new DictionaryBinaryResolver(binding),
new FakeProcessRunner([source]));
await foreach (var evt in executor.StreamAsync(
binding.Identifier, command, DockerComposeOutputParsers.Default))
{
if (evt is DockerComposeContainerStarted started)
Console.WriteLine($"{started.Service}: {started.ContainerName}");
if (evt.OutputLine is { } original)
Console.WriteLine($"{original.Source}: {original.Text}");
if (evt is DockerComposeCommandExited exited)
Console.WriteLine($"Compose CLI exit: {exited.ExitCode}");
}using FrenchExDev.Net.BinaryWrapper;
using FrenchExDev.Net.BinaryWrapper.Lib;
using FrenchExDev.Net.BinaryWrapper.Testing;
using FrenchExDev.Net.DockerCompose;
var version = SemanticVersion.Parse("5.1.0");
var binding = new BinaryBinding
{
Identifier = new BinaryIdentifier("docker-compose"),
ExecutablePath = "unused",
DetectedVersion = version,
};
var command = new DockerComposeEventsCommand(version)
{
Json = true,
Environment = new DockerComposeEnvironment { ProjectName = "example" },
};
var source = new OutputLine(
"""{"time":"2026-01-01T00:00:00Z","type":"container","service":"web","id":"abc","action":"start","attributes":{"name":"project-web-1"}}""",
OutputSource.StdOut);
var executor = new CommandExecutor(new DictionaryBinaryResolver(binding),
new FakeProcessRunner([source]));
await foreach (var evt in executor.StreamAsync(
binding.Identifier, command, DockerComposeOutputParsers.Default))
{
if (evt is DockerComposeContainerStarted started)
Console.WriteLine($"{started.Service}: {started.ContainerName}");
if (evt.OutputLine is { } original)
Console.WriteLine($"{original.Source}: {original.Text}");
if (evt is DockerComposeCommandExited exited)
Console.WriteLine($"Compose CLI exit: {exited.ExitCode}");
}The generated boolean preserves explicit JSON selection. Event projections retain service, container identity, action, attributes and timestamp. A container's exit attribute is distinct from the Compose CLI exit.
Selecting JSON progress through Environment
Lifecycle/build commands can request progress using a typed environment. This is a construction excerpt from the supported pattern:
var command = new DockerComposeStartCommand(version)
{
Environment = new DockerComposeEnvironment
{
Progress = DockerComposeProgress.Json,
},
};var command = new DockerComposeStartCommand(version)
{
Environment = new DockerComposeEnvironment
{
Progress = DockerComposeProgress.Json,
},
};The serializer emits COMPOSE_PROGRESS=json. The factory inspects the command environment and explicit progress options, then selects the relevant reviewed parser.
Progress configured only through inherited/binding variables is not necessarily visible in the command inspected by the factory. Supply the explicit format/progress factory in that case. It selects interpretation and does not itself set the environment.
Compose 2 and Compose 5 differ
Profiles cover reviewed 2.40.3 and 5.1.0 stable patches. Resource progress and BuildKit status do not have one universal channel assignment.
Compose 5 resource progress uses stderr while reviewed BuildKit output can use stdout. Compose 2 can redirect status through COMPOSE_STATUS_STDOUT. A parser needs the actual selected behavior to interpret channel provenance correctly.
The v5 profile additionally projects BuildKit vertices, status, logs and warnings. A vertex completion is a build observation, not the process's final success.
Service output and ambiguity
Attached service logs can resemble core tool output. The parser retains service text and producing logical lines, but cannot prove that an application never printed a familiar core-looking shape.
Compose can forward a service's stderr to Compose stdout. OutputLine.Source therefore records the channel received from the Compose process, not an invented reconstruction of the original service channel.
Separate monitoring commands or health probes may be preferable when a workflow needs a strong postcondition rather than mixed progress.
Environment and configuration boundaries
The catalogue exposes project name, files, profiles, separators, parallel limit, ANSI/progress settings, status channel selection and Docker connection/configuration variables.
List and boolean converters have tool-specific wire encodings. Newer settings carry their own Since baselines, such as Progress and DisableEnvFile. These dates are separate from the output-profile versions.
Wrapper-process environment, Compose interpolation inputs and service-container environment are different contracts. A typed wrapper setting does not replace the typed Compose configuration layer used to declare services.
A known generated-surface limitation
The historical help catalogue currently generates Up.Detach as a string. Passing "true" emits --detach true, where the second token can become a service argument. The output guide therefore demonstrates start without that option for JSON progress.
This is a concrete reminder that a generated API's CLR type is only as accurate as its inputs and parser. Documenting the limitation is preferable to demonstrating an invocation with incorrect semantics.
Validation and consumption
Tests cover JSON events, resource fields, service output, v5 BuildKit records, channels, profile fallback, bounded buffering, malformed data and nonzero actual exit. Generated events/start commands exercise parser-format selection.
These observations expose progress and resource information. Configuration checks and service readiness require their own evidence. See chapter 14 for the runner and application boundary.
Source snapshot
Source inspection: 2 October 2026. These implementation paths are relative to Net/FrenchExDev in the local ecosystem checkout. The v2 changes are largely uncommitted; these paths identify inspected code, without claiming that public branch links contain it.
DockerCompose/src/FrenchExDev.Net.DockerCompose.Design/Program.csDockerCompose/src/FrenchExDev.Net.DockerCompose.Contracts/FrenchExDev.Net.DockerCompose.Contracts.csprojDockerCompose/src/FrenchExDev.Net.DockerCompose/FrenchExDev.Net.DockerCompose.csprojDockerCompose/doc/TYPED-OUTPUT.mdDockerCompose/test/FrenchExDev.Net.DockerCompose.Tests
Previous: Docker · Implementation index · Series index · Next: Git