Skip to content
WSLDeep Dive Published Updated 8 min readViews unavailable

WSL Containers API: Model Windows App Work as a Session Lifecycle

Use Microsoft's WSL Containers API through its documented service, session, container, and process objects with explicit readiness and cleanup checks.

The WSL Containers API gives a Windows application a programmatic way to pull, run, and interact with Linux containers. It is a different integration surface from calling a command-line tool: the application works with service, session, container, and process objects, and can exchange standard input and output without treating terminal text as a protocol.

Microsoft documents the API alongside the wslc.exe CLI and states that the WSL container feature requires WSL 2.9.3 or higher. Microsoft’s WSL 3.0.1 release, published September 29, 2026, marks WSL containers generally available. The Learn page separately identifies the C++/WinRT projection as preview; do not confuse that projection-specific caveat with the overall feature’s GA status. The C# projection and C++/WinRT headers are packaged as Microsoft.WSL.Containers. The API is new and version-sensitive, so build a small integration test against the exact package rather than assuming a CLI example is a stable application contract.

Understand the object model

The documented flow begins with WslcService, the static entry point for checking required WSL components, querying service version, and installing missing dependencies. A Session is a WSL-backed host that manages images and creates containers. A Container is created in a session and can be started, stopped, inspected, deleted, or used to run additional processes. A Process represents a Linux process in a container; the API describes stdin/stdout/stderr, signal delivery, and exit-code events.

This hierarchy gives a Windows program explicit ownership boundaries. A missing platform component is a setup failure, not an image-pull failure. A session failing to start is different from a container failing to create. A container process exiting nonzero is different from a transport operation failing to complete. Keep these errors distinct in application logs and user-facing messages.

The API is not a request to install an arbitrary Linux distro and shell out to a local daemon. Do not assume it shares storage, image caches, network settings, credentials, or lifecycle with Docker Desktop, Podman, or a user’s interactive WSL distro. The documented session settings include a storage location, CPU count, and memory limit; use current API reference details for the exact members available in the installed package.

Install and version the package deliberately

Microsoft’s current setup uses the NuGet package and namespace:

dotnet add package Microsoft.WSL.Containers
using Microsoft.WSL.Containers;

Pin or centrally manage the package version used to build the Windows application. The WSL runtime and NuGet projection can be updated independently, so log both versions when collecting a failure report. The current Microsoft page calls out the C++/WinRT projection as preview and subject to breaking changes; do not generalize that statement to every projection without checking the specific release notes.

At startup, check for missing WSL components before creating a session. The Microsoft C# example starts with WslcService.GetMissingComponents(). Show a clear remediation path to the user or administrator, and do not automatically install platform components from a background application unless the deployment policy authorizes that change.

Make session ownership explicit

A session is a resource boundary, not just a convenience object. It carries the WSL-backed host configuration and manages images and containers. Give each workload a predictable session name and Windows storage path, and record those alongside the WSL package and API library version. Avoid generating a unique session for every tiny command if one bounded session can serve a well-defined batch; also avoid a single unbounded session that accumulates abandoned containers.

The official C# documentation shows setting CPU count and memory in SessionSettings, creating a Session, and calling Start(). Those controls should be treated as request configuration, not as proof of host-wide resource isolation. Check the current API reference for units, default values, and whether each option is available in the package version you target.

Use try/finally or an equivalent structured cleanup strategy around the session lifecycle. If an image pull fails after the session starts, cleanup still needs to run. If the app is canceled or the Windows process exits, ensure that the session is terminated according to the API’s documented lifecycle. A UI window closing is not a reliable cleanup mechanism for a background task.

Treat image operations as asynchronous work

Image pulls may take time, fail due to registry access, or be canceled. The API exposes asynchronous image operations and progress reporting. The following simplified C# pattern reflects the documented pull and progress shape; adapt method signatures to the exact package reference:

var pull = session.PullImageAsync(
    new PullImageOptions("docker.io/library/alpine:latest"));

pull.Progress = (_, progress) =>
    Console.WriteLine(
        $"{progress.Status} {progress.CurrentBytes}/{progress.TotalBytes}");

await pull;

Do not block a Windows UI thread while awaiting network activity. Surface progress, a bounded deadline, cancellation behavior, and a diagnostic identifier. A registry may return a mutable tag, so use a controlled image reference for reproducible testing. Store credentials with an approved credential mechanism rather than embedding them in a URL, source file, or log entry.

Test network access independently from container startup. A proxy, enterprise certificate authority, registry allow-list, or offline image import path can fail before a container exists. Report pull failures separately from the process’s exit code; otherwise a user may misdiagnose a registry outage as a Linux command failure.

Create containers with explicit process settings

The documented example builds a ProcessSettings object, sets an initial command, chooses event-based output, creates ContainerSettings for an image, names the container, and attaches the process settings. It then creates and starts the container. Keep image, command arguments, working directory, environment, mount, and resource intent explicit where the current API exposes those properties.

Avoid constructing a shell command string from untrusted or user-controlled text. Prefer an argument vector where supported, because shell quoting rules differ from Windows process argument rules and Linux shell rules. Treat container names as an application-managed identifier with collision handling; a retry after a partial failure should inspect or clean up an existing container rather than blindly creating duplicates.

Event-based output provides a natural stream of bytes, not necessarily complete human-readable lines. Buffer partial UTF-8 sequences and split lines carefully. Preserve stdout and stderr distinctions if the API surface exposes them separately. Do not assume every process exits after writing output; define a timeout or cancellation path for long-running commands and a health check for services.

Design shutdown and error handling

Microsoft’s example stops a container with a signal and grace period, deletes it, then terminates the session. Treat that order as a minimum lifecycle pattern. Choose a stop signal and grace interval based on the workload’s documented behavior; a database, compiler, and stateless test command have different shutdown requirements. Capture stop failures and do not silently report success if a process may still be running.

Cleanup should be idempotent. A process can fail before container creation, during image pull, or after the session starts. Track which objects were successfully created, and clean up only objects that exist. If cleanup itself fails, preserve the error and leave an operator-visible recovery record. Avoid automatic deletion of an image or volume that may be shared with another test unless the ownership boundary is explicit.

Map errors into layers:

  • prerequisite/service error: WSL component or package state;
  • session error: WSL-backed host startup or configuration;
  • image error: registry, archive, or image metadata;
  • container error: creation, settings, mounts, or start;
  • process error: stdin/stdout, signal, timeout, or nonzero exit.

This classification makes telemetry actionable and avoids placing every failure under “container exited.”

Qualify behavior on the target platform

The API is Windows-facing and WSL-specific. Build and execute tests on a supported Windows host with the documented WSL package; source inspection on macOS cannot verify runtime behavior. A unit test with mocked interfaces can validate application control flow, but it cannot prove that WSL components are installed, a session starts, a mount works, or output events arrive.

Start with Microsoft’s end-to-end sample and a deterministic echo command. Then test image pull, a nonzero process exit, cancellation during pull, cancellation during execution, a slow shutdown, a missing component, and cleanup after each failure. Include a small mounted-file test and a port test only if your app relies on those features. Record WSL and NuGet versions, Windows build, image digest, and exit status for each test.

The API page documents capabilities such as mounts, networking, GPU access, and stream interaction, but “available in the API” does not prove the current machine supports every GPU, driver, file path, or network topology. Qualify each required capability under the actual host policy and hardware.

Production boundary

If using the preview C++/WinRT projection, do not make it a critical application dependency without an explicit support and update plan. Keep the library version pinned, retest against each WSL servicing update, and give operators a fallback when WSL is absent or below the required version. Make the app report its own readiness only after the service, session, container, and workload-specific health check all pass.

If the task is simply to run a developer container interactively, the CLI may be a better fit. If a Windows application needs structured lifecycle calls, stream events, and programmatic process management, the API is the relevant surface. The key is to select one integration boundary deliberately and own its cleanup and compatibility tests.

Related:

Sources:

Comments