Skip to content

About

Managed Docker Compose SDK for .NET with Compose file loading and Docker Engine operations.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

ComposeSharp

CI Engine on NuGet License

简体中文

ComposeSharp is for .NET applications that need to own a Docker Compose project instead of starting docker compose as a child process. Give it a working directory and a Compose file; it reads the file, turns services into Docker Engine requests, and keeps the resources grouped with familiar com.docker.compose.* labels.

It is deliberately an SDK, not a command-line wrapper and not a promise of Docker Compose CLI parity. That distinction matters: the project is useful today for embedding a small Compose-shaped environment in an application, test harness, developer tool, or local control plane, while its compatibility work remains visible rather than hidden behind CLI-looking method names.

The shape of the project

compose.yml + .env + env_file
            │
            ▼
   ComposeSharp.Loader
   typed services, ports, volumes, networks, deploy hints
            │
            ▼
   ComposeSharp.Engine
   Docker Engine containers, networks, volumes, labels
            │
            ▼
 IComposeService in your application

Project ownership is label-based. Containers created for inventory are named and queried as part of that project, so PsAsync, DownAsync, ScaleAsync, LogsAsync, and volume/network cleanup operate within a project boundary instead of scanning arbitrary Docker resources.

Start here

Install the engine package when you want to load and run a project:

dotnet add package ComposeSharp.Engine
using ComposeSharp.Api;
using ComposeSharp.Engine;

var compose = new ComposeService();
var project = new ComposeProjectContext
{
    ProjectName = "inventory-dev",
    WorkingDirectory = @"C:\src\inventory"
};

// Resolves docker-compose.yml, compose.yml, compose.yaml, or docker-compose.yaml.
var config = compose.LoadProject(project);
Console.WriteLine(string.Join(", ", config.Services));

await compose.UpAsync(project, new ComposeUpOptions
{
    Pull = "always",
    Scale = new Dictionary<string, int> { ["api"] = 2 },
    LogConsumer = new ConsoleStatus()
});

var containers = await compose.PsAsync(project);
foreach (var container in containers)
    Console.WriteLine($"{container.Service}: {container.State}");

sealed class ConsoleStatus : ILogConsumer
{
    public void OnStatus(string serviceName, string message) =>
        Console.WriteLine($"[{serviceName}] {message}");
    public void OnLog(string serviceName, string message, bool isStdErr) =>
        Console.WriteLine($"[{serviceName}] {message}");
    public void OnLogComplete(string serviceName) { }
}

For an application that already uses IServiceCollection:

dotnet add package ComposeSharp.DependencyInjection
builder.Services.AddComposeSharp();

Resolve IComposeService from the container. ComposeProjectContext stays explicit at the call site, which keeps project names, working directories, registry credentials, and Docker endpoints visible in multi-project applications.

What the loader understands

The loader is intentionally useful even when you do not start containers. It reads .env and per-service env_file files, expands variables in YAML, and returns strongly typed services. It recognizes common service configuration including images, build definitions, commands, entrypoints, environment, ports, volumes, networks, health checks, restart policy, profiles, labels, logging, Linux capabilities, resource hints, secrets, configs, and selected deploy fields.

Variable interpolation

When expanding YAML, ComposeSharp uses the process environment first and then the project .env file. A service's env_file contributes only to that container's environment; it is not an interpolation source. Unset variables expand to an empty value unless a -/:- default or ?/:? required-value form is used. $$ produces a literal $. Required-variable errors name both the variable and the Compose file.

ComposeFileMerger.MergeWithEnv applies its explicit overrides ahead of the process environment and .env values for that merge only. It does not change process-wide environment variables, so concurrent merges remain isolated and callers do not need to restore environment state.

Loader diagnostics

Invalid input throws ComposeValidationException. Its message and structured properties identify the resolved source file and stable YAML property path, plus the service name for service-scoped failures. YAML parser failures also expose one-based line and column values when available and retain the original exception as InnerException. For example: Invalid Compose input in 'C:\app\compose.yaml' at 'services.api.environment', service 'api': expected a YAML list or mapping, but found scalar 'invalid'. When files are loaded with LoadMerged, diagnostics use the overlay source for invalid nodes introduced by an overlay.

ComposeFileLoader.LoadMerged accepts several files and applies an incremental, field-level merge: later scalars replace earlier values, mappings merge recursively, and lists append with focused replacement rules for service resources, command, and entrypoint. It is not Docker Compose's complete merge algorithm; see the merge semantics for the exact supported rules and unsupported YAML tags.

ComposeProjectContext.Profiles selects services consistently for project loading and service-scoped lifecycle, inspection, image, log, event, and watch operations. Services without profiles are always selected; a profiled service is selected when any of its profiles is active. An operation that explicitly names a service can select it even when its profile is not active. An empty selection is a no-op rather than a request to operate on every project container.

The loader can retain fields that the engine does not yet apply. Consult the Compose field support matrix before relying on a Compose property at runtime.

What the engine does today

Area Current behavior
Project lifecycle Creates project networks, creates/starts/removes labeled containers, lists project containers, removes project networks and optional volumes, and builds selected services through Docker Engine.
Service operations Supports start, stop, restart, pause, unpause, kill, remove, run, exec, attach, pull, push, scale, wait, and port lookup.
Inspection Provides project list with aggregate container status (projects excluded by Docker's default container listing require All), containers, images, volumes, logs, process listings via TopAsync, and a DOT graph via VizAsync.
Streaming LogsAsync streams Docker logs. EventsAsync subscribes to the Docker event stream filtered by the project label.
File change signal WatchAsync continuously monitors selected build contexts together and yields rebuild notifications with absolute changed paths. Shared contexts notify every associated service. It does not start services, rebuild, or synchronize files.

Important implementation boundaries

These boundaries are part of the public contract today:

  • BuildAsync sends a tarred local build context to the Docker Engine API. It applies .dockerignore or the selected Dockerfile's .dockerignore, preserves symbolic links and Unix file modes, skips ignored directory trees unless a negated rule can restore a descendant, stages an external Dockerfile safely, observes cancellation while creating the archive, and spools that archive to a temporary file rather than process memory. Valueless build arguments inherit their process-environment value when set. It supports a Dockerfile, tags, target, build arguments, labels, cache_from, network mode, extra hosts, shared-memory and memory limits, one platform, pull, and no-cache. ComposeBuildOptions.LogConsumer receives Docker build status messages, and build-stream errors fail the operation. BuildKit-specific cache_to, multiple platforms, privileged, builder, and progress-mode selection are not applied.
  • CopyAsync uses Docker archive endpoints and ExportAsync streams a service container's filesystem tar through Docker Engine; both can target running, stopped, or created service containers. For copy operations, specify the container side as service:/absolute/path and the local side as a path; the container path is treated as a directory. Uploads target every matching service container unless ComposeCopyOptions.Index selects a 1-based replica; downloads select one replica. ComposeCopyOptions.All includes run-created one-off containers. CopyResult.BytesCopied reports aggregate file payload bytes across upload targets. Local symbolic links and symbolic links in downloaded archives are rejected; downloaded Unix file modes are preserved and hard-linked files are materialized as regular files.
  • CommitAsync uses the Docker Engine commit endpoint and returns the image reference recorded on the new image. It honors author, message, Dockerfile-style Changes, Pause, and ComposeCommitOptions.Index replica selection.
  • TopAsync uses the Docker Engine process-list API and returns one process listing per running service container. Non-running containers are omitted.
  • WaitAsync returns an exit code for every awaited container. Its aggregate Code is the first non-zero exit code by ordinal container ID, independent of which wait completes first.
  • GenerateAsync returns a project summary plus ComposeProjectConfig.RenderedYaml, a normalized snapshot of the loader's retained fields. ComposeGenerateOptions.Services selects service names (including inactive profiles); an empty list selects none, and unknown names fail. ProjectName changes the output name only. Nonempty legacy Containers selections throw because generation does not inspect Docker containers. LoadProject still returns a summary with no rendered YAML.
  • Generated YAML includes resolved service environment values, with env_file folded into environment; it may contain secrets. It retains paths relative to the source Compose directory. Unmodeled fields, long-form dependency conditions, and resource definitions beyond their names are unavailable. Loader-specific deploy shapes are retained, so this is a model snapshot rather than full Compose CLI configuration output. No files are written. See the generation and watch contract for exact semantics.
  • WatchAsync reports create, write, delete, and rename notifications continuously until cancellation or disposal. OS notifications may repeat; ordering across contexts is filesystem-dependent, while shared-context service notifications use ordinal service-name order. Services without build definitions are ignored; no selected build services completes immediately. Unknown selections, missing contexts, watcher failures, and buffer overflow are surfaced as errors. Cancellation throws without emitting a synthetic change. NoUp and Quiet are compatibility no-ops; Prune = true throws. It does not interpret develop.watch or .dockerignore.
  • PublishAsync tags every selected service image as repository:serviceName and pushes each tagged image using ComposeProjectContext.RegistryAuth. It returns a PublishResult per selected service covering tag and push outcomes instead of aborting on the first failure. ComposePublishOptions.ResolveImageDigests, WithEnvironment, OcIVersion, and InsecureRegistry are not applied.
  • depends_on includes transitive dependencies of selected services and starts them in stable order. Dependencies without an effective Docker health check must be running; dependencies with one, including a check inherited from the image, must be healthy. Unhealthy or stopped dependencies fail with diagnostics; callers can set DependencyWaitTimeoutSeconds or cancel the operation. Missing services and cycles fail with diagnostics. Long-form conditions remain unsupported.
  • The default Docker endpoints are npipe://./pipe/docker_engine on Windows and unix:///var/run/docker.sock on Unix. A custom SocketPath can use an npipe:// or unix:// URI.

If you need exact CLI behavior, BuildKit breadth, Compose watch synchronization, or a full Compose Specification merge/interpolation implementation, use Docker Compose CLI directly for now. Issues that demonstrate a minimal compatibility gap are especially valuable: open one.

Migration from 2.0.0

IComposeService.PublishAsync now returns per-service PublishResult values instead of Task. Source call sites that await the method, assign its result to Task, or pass it where a Task-returning method is accepted remain compatible. Rebuild binaries compiled against the earlier signature. Callers that need publish outcomes can inspect the returned results.

Packages

Package Use it when you need…
ComposeSharp.Api Contracts, options, results, callbacks, and ComposeProjectContext.
ComposeSharp.Loader Typed Compose-file loading without Docker access.
ComposeSharp.Engine The Docker Engine implementation of IComposeService.
ComposeSharp.DependencyInjection AddComposeSharp() for Microsoft dependency injection.

All packages target .NET 8, .NET 9, and .NET 10. The engine needs permission to reach Docker Engine; ComposeSharp does not manage Docker Desktop, daemon permissions, registry login state, or remote TLS configuration for you.

Roadmap

The roadmap is organized around implementation honesty rather than pretending every CLI-shaped API is complete:

  1. 2.1 — Compose model correctness. Test-backed YAML interpolation and merge semantics, profile selection, service configuration mapping, and clear validation errors.
  2. 2.2 — Docker Engine coverage. Add real top, Docker event streaming, a meaningful watch action model, and project generation; complete and document the remaining publish options.
  3. Reliable orchestration. Dependency ordering and readiness, safer reconciliation, richer diagnostics, and integration coverage across Linux and Windows Docker environments.

Details, acceptance criteria, and non-goals live in docs/roadmap.md. The Compose field support matrix records parsed versus applied behavior. Work is tracked in GitHub milestones.

Build the repository

dotnet restore ComposeSharp.sln
dotnet build ComposeSharp.sln --configuration Release --no-restore
dotnet test ComposeSharp.sln --configuration Release --no-build --no-restore

Integration tests exercise Docker only when docker info succeeds. Package artifacts can be produced with:

dotnet pack ComposeSharp.sln --configuration Release --no-build --output artifacts/packages

Participate

ComposeSharp is available under the MIT License.

About

Managed Docker Compose SDK for .NET with Compose file loading and Docker Engine operations.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages