FluentDocker

May 11, 2026 · View on GitHub

CI codecov Release License .NET

PackageNuGetDownloads
FluentDockerNuGetDownloads
Testing.XunitNuGetDownloads
Testing.MsTestNuGetDownloads
Testing.NUnitNuGetDownloads

Quick Start

using System.Linq;
using FluentDocker.Builders;
using FluentDocker.Drivers.Podman;
using FluentDocker.Kernel;
using FluentDocker.Model.Drivers;

// Multiple kernels per app are supported.
// This kernel registers both Docker CLI and Podman CLI.
using var kernel = await FluentDockerKernel.Create()
    .WithDockerCli("docker", d => d.AsDefault())
    .WithPodmanCli("podman", d => d.WithAutoStartMachine())
    .BuildAsync();

1) Standard container (Docker CLI)

await using var results = await new Builder()
    .WithinDockerCli("docker", kernel)
    .UseContainer(c => c
        .UseImage("nginx:alpine")
        .ExposePort("80")
        .WaitForPort("80/tcp", 30000))
    .BuildAsync();

var endpoint = results.Containers.First()
    .ToHostExposedEndpoint("80/tcp");
Console.WriteLine($"Docker endpoint: {endpoint.Address}:{endpoint.Port}");

2) Standard container (Podman CLI)

await using var results = await new Builder()
    .WithinPodmanCli("podman", kernel)
    .UseContainer(c => c
        .UseImage("nginx:alpine")
        .ExposePort("80")
        .WaitForPort("80/tcp", 30000))
    .BuildAsync();

var endpoint = results.Containers.First()
    .ToHostExposedEndpoint("80/tcp");
Console.WriteLine($"Podman endpoint: {endpoint.Address}:{endpoint.Port}");

3) Docker Compose (Docker CLI)

await using var results = await new Builder()
    .WithinDockerCli("docker", kernel)
    .UseCompose(c => c
        .WithComposeFile("docker-compose.yml")
        .WithRemoveOrphans()
        .WithWait()
        .WithWaitTimeout(30))
    .BuildAsync();

var compose = results.ComposeServices.First();

4) Podman Kubernetes (kube play / kube down)

var context = new DriverContext("podman");
var kube = kernel.SysCtl<IPodmanKubernetesDriver>("podman");

await kube.PlayAsync(context, new KubePlayConfig
{
    YamlPath = "pod.yaml",
    Replace = true
});

// Teardown
await kube.DownAsync(context, "pod.yaml");

5) Test Support

FluentDocker supports xUnit, MSTest, and NUnit via adapter packages. See Test Support below for examples.

Installation

dotnet add package FluentDocker
dotnet add package FluentDocker.Testing.Xunit   # xUnit adapter
dotnet add package FluentDocker.Testing.MsTest  # MSTest adapter
dotnet add package FluentDocker.Testing.NUnit   # NUnit adapter

v3.0.0 is a major rewrite — multi-driver kernel, async-first API, Podman support, new test packages. See the 3.0.0 release notes and the migration guide for the full feature list and breaking changes.

Features

Container Management

// Create and start
using var results = new Builder()
    .WithinDockerCli("docker", kernel)
    .UseContainer(c => c
        .UseImage("nginx:latest")
        .ExposePort("80"))
    .Build();

var container = results.Containers.First();

// Get configuration
var config = container.GetConfiguration(true);

// Container stats (v3)
var stats = await container.GetStatsAsync();
Console.WriteLine($"CPU: {stats.CpuPercent:F2}%");

Port Mapping

// Explicit: host port 8080 → container port 80
.ExposePort(8080, 80)

// Random: let Docker choose host port
.ExposePort("80")

// Resolve actual endpoint
var endpoint = container.ToHostExposedEndpoint("80/tcp");

Wait Strategies

.WaitForPort("5432/tcp", 30000)           // Wait for port
.WaitForProcess("postgres", 30000)         // Wait for process
.WaitForLogMessage("ready", 30000)         // Wait for log message

Networks with Static IP

using var nwResults = new Builder()
    .WithinDockerCli("docker", kernel)
    .UseNetwork(n => n
        .WithName("my-network")
        .WithSubnet("10.18.0.0/16"))
    .Build();

var network = nwResults.Networks.First();

using var cResults = new Builder()
    .WithinDockerCli("docker", kernel)
    .UseContainer(c => c
        .UseImage("nginx")
        .WithNetwork("my-network")
        .UseIpV4("10.18.0.100"))
    .Build();

Volume Mounts

// Host path mount
.WithVolume("/host/path", "/container/path")

// Named volume
.WithVolume("my-vol", "/data")

File Operations

// Copy to container
await container.CopyToAsync("/local/file", "/container/file");
await container.CopyToAsync("/local/dir", "/container/dir");  // v3: directories

// Copy from container
await container.CopyFromAsync("/container/file", "/local/file");

Image Building

// Inline Dockerfile
using var imgResults = new Builder()
    .WithinDockerCli("docker", kernel)
    .UseImage("mynode:latest", img => img
        .From("node:18-alpine")
        .Run("npm install -g nodemon")
        .ExposePorts(8080)
        .Command("node", "app.js"))
    .Build();

Drivers

FluentDocker ships with three drivers:

  • Docker CLI
  • Docker API
  • Podman CLI

All drivers share a common core (IContainerDriver, IImageDriver, INetworkDriver, IVolumeDriver, ISystemDriver, IAuthDriver, IStreamDriver) and add driver-specific capabilities on top.

Quick Driver Examples (Start Here)

Use these imports in the snippets below:

using System.Collections.Generic;
using System.Linq;
using FluentDocker.Builders;
using FluentDocker.Drivers;
using FluentDocker.Drivers.Podman;
using FluentDocker.Kernel;
using FluentDocker.Model.Drivers;

Example: create a kernel with both Docker CLI and Podman CLI (multiple kernels per app are also supported):

using var kernel = await FluentDockerKernel.Create()
    .WithDockerCli("docker", d => d.AsDefault())
    .WithPodmanCli("podman", d => d
        .WithAutoStartMachine() // macOS/Windows: ensure Podman VM is running
        .AsDefault())
    .BuildAsync();

1) Standard container (Docker CLI)

await using var dockerResults = await new Builder()
    .WithinDockerCli("docker", kernel)
    .UseContainer(c => c
        .UseImage("nginx:alpine")
        .ExposePort("80")
        .WaitForPort("80/tcp", 30000))
    .BuildAsync();

var dockerEndpoint = dockerResults.Containers.First()
    .ToHostExposedEndpoint("80/tcp");

2) Standard container (Podman CLI)

await using var podmanResults = await new Builder()
    .WithinPodmanCli("podman", kernel)
    .UseContainer(c => c
        .UseImage("nginx:alpine")
        .ExposePort("80")
        .WaitForPort("80/tcp", 30000))
    .BuildAsync();

var podmanEndpoint = podmanResults.Containers.First()
    .ToHostExposedEndpoint("80/tcp");

3) Docker Compose (Docker CLI)

await using var composeResults = await new Builder()
    .WithinDockerCli("docker", kernel)
    .UseCompose(c => c
        .WithComposeFile("docker-compose.yml")
        .WithRemoveOrphans()
        .WithWait())
    .BuildAsync();

var compose = composeResults.ComposeServices.First();

4) Kubernetes play/down (Podman CLI)

var podmanContext = new DriverContext("podman");
var kube = kernel.SysCtl<IPodmanKubernetesDriver>("podman");

var play = await kube.PlayAsync(podmanContext,
    new KubePlayConfig
    {
        YamlPath = "pod.yaml",
        Replace = true
    });

// Teardown when done
await kube.DownAsync(podmanContext, "pod.yaml");

5) Swarm stack deploy/remove (Docker CLI)

// Requires Docker Swarm mode: docker swarm init
var dockerContext = new DriverContext("docker");
var stacks = kernel.SysCtl<IStackDriver>("docker");

var deploy = await stacks.DeployAsync(dockerContext,
    new StackDeployConfig
    {
        StackName = "web",
        ComposeFiles = new List<string> { "docker-stack.yml" }
    });

var services = await stacks.GetServicesAsync(dockerContext, "web");

// Teardown when done
await stacks.RemoveAsync(dockerContext, new[] { "web" });

Capability per Driver

CapabilityDocker CLIDocker APIPodman CLI
Container / Image / Network / Volumeyesyesyes
System / Auth / Streamingyesyesyes
Composeyes--
Stack (Swarm)yes--
Service (Swarm)yesyes-
Pods--yes
Kubernetes play/generate--yes
Machine management--yes
Multi-arch manifests--yes

Kernel Setup

Register one or more drivers in the kernel builder:

using var kernel = await FluentDockerKernel.Create()
    .WithDockerCli("docker", d => d.AsDefault())
    .WithDockerApi("docker-api", d => d
        .WithConnectionTimeout(TimeSpan.FromSeconds(30)))
    .WithPodmanCli("podman", d => d
        .WithAutoStartMachine()
        .AsDefault())
    .BuildAsync();

Common API - Works with Any Driver

The fluent builder API is shared across drivers. Switch driver scope and keep the same container definition style.

await using var results = await new Builder()
    .WithinDriver(driverId, kernel) // driverId: "docker", "docker-api", "podman"
    .UseContainer(c => c
        .UseImage("postgres:15-alpine")
        .ExposePort("5432")
        .WithEnvironment("POSTGRES_PASSWORD=secret")
        .WaitForPort("5432/tcp", 30000))
    .BuildAsync();

Docker API — Direct Engine Communication

The Docker API driver talks directly to the Docker Engine REST API over Unix socket, named pipe, or TCP+TLS. No Docker CLI binary is required.

using var kernel = await FluentDockerKernel.Create()
    .WithDockerApi("api", d => d
        .AtHost("unix:///var/run/docker.sock")    // optional, auto-detected
        .WithCertificates("/path/to/certs")       // optional, for TLS
        .WithConnectionTimeout(TimeSpan.FromSeconds(15))
        .WithRequestTimeout(TimeSpan.FromMinutes(10))
        .AsDefault())
    .BuildAsync();

await using var results = await new Builder()
    .WithinDockerApi("api", kernel)
    .UseContainer(c => c
        .UseImage("redis:7-alpine")
        .ExposePort("6379"))
    .BuildAsync();

var stream = kernel.SysCtl<IStreamDriver>("api");
var context = new DriverContext("api");
await foreach (var ev in stream.StreamEventsAsync(context))
    Console.WriteLine($"Event: {ev.Action} on {ev.Type}");

Podman-Specific Features via Driver Layer

Access Podman-only capabilities through SysCtl<T> or TrySysCtl<T>:

var context = new DriverContext("podman");

// Pods
var pods = kernel.SysCtl<IPodmanPodDriver>("podman");
await pods.CreatePodAsync(context, new PodCreateConfig { Name = "my-pod" });

// Machine management
var machines = kernel.SysCtl<IPodmanMachineDriver>("podman");
var list = await machines.ListAsync(context);

// Multi-arch manifests
var manifest = kernel.SysCtl<IPodmanManifestDriver>("podman");
await manifest.CreateAsync(context,
    new ManifestCreateConfig
    {
        Name = "myapp:latest",
        Images = new List<string> { "myapp:amd64", "myapp:arm64" }
    });

Writing Driver-Portable Code

Use TrySysCtl<T> when you want optional driver-specific behavior:

if (kernel.TrySysCtl<IPodmanPodDriver>(driverId, out var podDriver))
{
    await podDriver.CreatePodAsync(context,
        new PodCreateConfig { Name = "my-pod" });
}

Test Support

FluentDocker v3 includes FluentDocker.Testing.Core in the main assembly. Framework-specific adapters are available as separate packages.

xUnit (Testing.Core)

// Option A — Abstract base (recommended):
public class MyRedisFixture : XunitContainerFixtureBase
{
  protected override void ConfigureContainer(IContainerBuilder builder)
    => builder
        .UseImage("redis:alpine")
        .ExposePort(6379)
        .WaitForPort("6379/tcp");
}

// Option B — Configure + IAsyncLifetime:
public class MyRedisFixture : XunitContainerFixture
{
  public MyRedisFixture()
  {
    Configure(builder => builder
        .UseImage("redis:alpine")
        .ExposePort(6379)
        .WaitForPort("6379/tcp"));
  }
}

MSTest (Testing.Core)

using FluentDocker.Testing.MsTest;

[TestClass]
public class MyTests
{
    private static FluentDockerKernel _kernel;
    private static ContainerResource _resource;

    [ClassInitialize]
    public static async Task ClassInit(TestContext context)
    {
        (_kernel, _resource) = await MsTestResourceHelpers.CreateContainerAsync(
            builder => builder
                .UseImage("redis:alpine")
                .WaitForPort("6379/tcp"));
    }

    [ClassCleanup]
    public static async Task ClassCleanup()
    {
        await MsTestResourceHelpers.DisposeAsync(_resource, _kernel);
    }
}

See the full testing docs for NUnit, Compose, Topology, Swarm Stack, and Podman Kubernetes resource types.


Linux Users

Docker requires sudo by default. Configure per-driver: WithSudo(SudoMechanism.NoPassword) or add user to docker group: sudo usermod -aG docker $USER


Contributing

Contributions welcome! Please adhere to .editorconfig for code style.


Resources


License

Apache 2.0 - See LICENSE for details.