Use docker/podman, compose local and remote via a FluentAPI
FluentDocker
| Package | NuGet | Downloads | |---------|:-----:|:---------:| | FluentDocker | |
| | Testing.Xunit |
|
| | Testing.MsTest |
|
| | Testing.NUnit |
|
|
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
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
| Capability | Docker CLI | Docker API | Podman CLI | |---|:---:|:---:|:---:| | Container / Image / Network / Volume | yes | yes | yes | | System / Auth / Streaming | yes | yes | yes | | Compose | yes | - | - | | Stack (Swarm) | yes | - | - | | Service (Swarm) | yes | yes | - | | 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
- Documentation Site - Full documentation on GitHub Pages
- Migration Guide - Upgrading from v2.x.x
- Architecture Docs - v3 architecture details
- NuGet Package
License
Apache 2.0 - See LICENSE for details.