When the user invokes this skill, perform the following migration steps on their codebase. Work through each step in order. Report what you changed after each step so the user can review incrementally.
CRITICAL: v2.x.x only supported Docker CLI. ALL migrations MUST use
WithDockerCli – the kernel always registers a DockerCli driver.
Search the entire codebase for files that reference FluentDocker:
Grep for: Ductus\.FluentDocker
Grep for: FluentDocker\.Builders\.Builder
Grep for: using Ductus\.FluentDocker
Grep for: Logging\.Enabled\(\)
Grep for: Logging\.Disabled\(\)
The last two patterns find call sites of v2’s static logging toggle, which is removed in v3 (see Step 13). List all files found. These are the files you will modify.
Replace all occurrences of Ductus.FluentDocker with FluentDocker across
every .cs file in the project.
Common replacements:
| v2 Namespace | v3 Namespace |
|---|---|
Ductus.FluentDocker.Builders |
FluentDocker.Builders |
Ductus.FluentDocker.Services |
FluentDocker.Services |
Ductus.FluentDocker.Services.Extensions |
FluentDocker.Services.Extensions |
Ductus.FluentDocker.Model.Common |
FluentDocker.Model.Common |
Ductus.FluentDocker.Model.Containers |
FluentDocker.Model.Containers |
Ductus.FluentDocker.Model.Compose |
FluentDocker.Model.Compose |
Ductus.FluentDocker.Model.Images |
FluentDocker.Model.Images |
Ductus.FluentDocker.Model.Networks |
FluentDocker.Model.Networks |
Ductus.FluentDocker.Model.Volumes |
FluentDocker.Model.Volumes |
Ductus.FluentDocker.Commands |
Removed – delete these imports |
Ductus.FluentDocker.MsTest |
FluentDocker.Testing.MsTest |
Ductus.FluentDocker.XUnit |
FluentDocker.Testing.Xunit |
Add these new imports where needed:
using FluentDocker.Kernel; – wherever kernel is created or referenced.using FluentDocker.Services.Extensions; – wherever extension methods are
used (ToHostExposedEndpoint, GetConfiguration, Wget).In all .csproj files, replace:
<!-- OLD -->
<PackageReference Include="Ductus.FluentDocker" Version="2.*" />
<PackageReference Include="Ductus.FluentDocker.MsTest" Version="2.*" />
<PackageReference Include="Ductus.FluentDocker.XUnit" Version="2.*" />
<!-- NEW -->
<PackageReference Include="FluentDocker" Version="3.*" />
<PackageReference Include="FluentDocker.Testing.MsTest" Version="3.*" />
<PackageReference Include="FluentDocker.Testing.Xunit" Version="3.*" />
Find the entry point, test setup, or fixture initialization. Add kernel creation using the DockerCli driver (the only driver type available in v2):
using var kernel = FluentDockerKernel.Create()
.WithDockerCli("docker", d => d.AsDefault())
.Build();
var kernel = await FluentDockerKernel.Create()
.WithDockerCli("docker", d => d.AsDefault())
.BuildAsync();
InitializeAsync(), store as field.XunitContainerFixture – kernel is managed automatically.MsTestResourceHelpers.CreateContainerAsync() – kernel is returned
in the tuple. Pass a kernelFactory for custom configuration.NUnitResourceHelpers.CreateContainerAsync() – same pattern as MSTest.// OLD
var container = new Builder()
.UseContainer()
.UseImage("nginx:alpine")
.ExposePort(80)
.WaitForPort("80/tcp", 30000)
.Build()
.Start();
// NEW
var results = new Builder()
.WithinDriver("docker", kernel)
.UseContainer(c => c
.UseImage("nginx:alpine")
.ExposePort("80")
.WaitForPort("80/tcp", 30000))
.Build();
var container = results.Containers.First();
// OLD
var network = new Builder()
.UseNetwork("my-net")
.UseSubnet("10.18.0.0/16")
.Build();
// NEW
var results = new Builder()
.WithinDriver("docker", kernel)
.UseNetwork(n => n
.WithName("my-net")
.WithSubnet("10.18.0.0/16"))
.Build();
var network = results.Networks.First();
// OLD
var volume = new Builder()
.UseVolume("my-data")
.Build();
// NEW
var results = new Builder()
.WithinDriver("docker", kernel)
.UseVolume(v => v
.WithName("my-data"))
.Build();
var volume = results.Volumes.First();
// OLD
var img = new Builder()
.DefineImage("myapp:latest")
.From("node:18-alpine")
.Run("npm install")
.ExposePorts(8080)
.Command("node", "app.js")
.Build();
// NEW
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myapp:latest", img => img
.From("node:18-alpine")
.Run("npm install")
.ExposePorts(8080)
.Command("node", "app.js"))
.Build();
// OLD
var svc = new Builder()
.UseContainer()
.UseCompose()
.FromFile("docker-compose.yml")
.RemoveOrphans()
.WaitForHttp("api", "http://localhost:8080/health")
.Build()
.Start();
// NEW
var results = new Builder()
.WithinDriver("docker", kernel)
.UseCompose(c => c
.WithComposeFile("docker-compose.yml")
.WithRemoveOrphans()
.WithWait()
.WithWaitTimeout(60))
.Build();
.Build().Start() becomes just .Build() – auto-starts.Build() return type changes from a service to BuildResults.results.Containers.First(), results.Networks.First(), etc.int to string (e.g., 80 to "80").BuildAsync()) in async contexts.| v2 Method | v3 Method |
|---|---|
container.Resume() |
container.Start() |
container.Pause() |
await container.PauseAsync() |
container.Logs() |
await container.GetLogsAsync() |
container.ExecAsync(...) |
await container.ExecuteAsync(...) |
container.CopyFromAsync(containerPath, hostPath) |
await container.CopyFromToPathAsync(containerPath, hostPath) |
container.CopyToAsync(hostPath, containerPath) |
await container.CopyToAsync(hostPath, containerPath) |
container.GetRunningProcesses() |
await container.ExecuteAsync("ps", "-ef") – no direct equivalent; workaround is ExecuteAsync |
container.Export() |
await container.ExportAsync() |
The container stats model was restructured:
| v2 Property | v3 Property |
|---|---|
stats.CpuPercent |
stats.Cpu.UsagePercent |
stats.MemoryUsage |
stats.Memory.Usage |
stats.MemoryLimit |
stats.Memory.Limit |
stats.MemoryPercent |
stats.Memory.UsagePercent |
stats.NetworkRx |
stats.Network.RxBytes |
stats.NetworkTx |
stats.Network.TxBytes |
stats.BlockRead |
stats.Disk.ReadBytes |
stats.BlockWrite |
stats.Disk.WriteBytes |
The global static sudo configuration was removed. Configure sudo in the kernel builder instead:
// OLD
SudoMechanism.NoPassword.SetSudo();
var container = new Builder()
.UseContainer()
.UseImage("nginx")
.Build()
.Start();
// NEW
var kernel = FluentDockerKernel.Create()
.WithDockerCli("docker", d => d
.WithSudo(SudoMechanism.NoPassword)
.AsDefault())
.Build();
var results = new Builder()
.WithinDriver("docker", kernel)
.UseContainer(c => c.UseImage("nginx"))
.Build();
Important: The enum values are SudoMechanism.None, SudoMechanism.NoPassword,
and SudoMechanism.Password. There is no SudoMechanism.Sudo value.
Remove all Docker Machine code. It was deprecated by Docker and removed in v3.
// DELETE all of these patterns:
var machines = new Hosts().Discover();
var machine = machines.First(x => x.Name == "default");
machine.Start();
machine.Create(1024, 20000, 1);
// Any IMachineDriver references
// Any using Ductus.FluentDocker.Machine imports
Replace with kernel creation:
var kernel = FluentDockerKernel.Create()
.WithDockerCli("docker", d => d.AsDefault())
.Build();
Remove DOCKER_TOOLBOX_INSTALL_PATH checks and DockerToolbox class usage.
The Ductus.FluentDocker.Commands namespace is removed entirely. All direct
command invocations are replaced by the driver layer:
// OLD
using Ductus.FluentDocker.Commands;
host.InspectContainer(containerId);
host.ComposeUp(composeFile);
// NEW -- use driver layer
var driver = kernel.SysCtl<IContainerDriver>("docker");
await driver.InspectAsync(context, containerId);
// Or use the builder pattern (preferred)
var results = new Builder()
.WithinDriver("docker", kernel)
.UseContainer(c => c.UseImage("nginx"))
.Build();
// OLD
host.ComposeUp(composeFile, forceRecreate: true);
host.ComposeDown(removeOrphans: true, removeVolumes: true);
host.ComposeBuild(altProjectName: "myproject", forceRm: true);
// NEW -- via builder (preferred)
var results = new Builder()
.WithinDriver("docker", kernel)
.UseCompose(c => c
.WithComposeFile(composeFile)
.WithForceRecreate())
.Build();
// NEW -- via driver (advanced)
var composeDriver = kernel.SysCtl<IComposeDriver>("docker");
await composeDriver.UpAsync(context, new ComposeUpConfig {
ComposeFiles = new List<string> { composeFile },
ForceRecreate = true
});
await composeDriver.DownAsync(context, new ComposeDownConfig {
RemoveOrphans = true,
RemoveVolumes = true
});
await composeDriver.BuildAsync(context, new ComposeBuildConfig {
ProjectName = "myproject",
ForceRm = true
});
v2 had per-service wait strategies (.WaitForHttp(), .WaitForPort()). v3 uses
Docker Compose V2’s native --wait flag with healthchecks defined in the compose
file:
// OLD
.WaitForHttp("api", "http://localhost:8080/health")
// NEW
.WithWait() // Uses compose --wait flag
.WithWaitTimeout(60) // Timeout in seconds
For post-startup HTTP polling, access containers from results.Containers.
// OLD: sync IDisposable
_container?.Dispose();
// NEW: async IAsyncDisposable -- dispose results BEFORE kernel
if (_results is IAsyncDisposable ad) await ad.DisposeAsync();
if (_kernel is IAsyncDisposable kd) await kd.DisposeAsync();
// OLD: using statement
using var container = new Builder().UseContainer().UseImage("nginx").Build().Start();
// NEW: await using
await using var kernel = FluentDockerKernel.Create()
.WithDockerCli("docker", d => d.AsDefault()).Build();
await using var results = new Builder()
.WithinDriver("docker", kernel).UseContainer(c => c.UseImage("nginx")).Build();
The legacy FluentDockerTestBase class has been removed. Use the new adapters:
MsTestResourceHelpers.CreateContainerAsync(builder => ...) returns
(kernel, resource). Cleanup with MsTestResourceHelpers.DisposeAsync(resource, kernel).XunitContainerFixture and call InitializeAsync(builder => ...)
in the constructor.NUnitResourceHelpers.CreateContainerAsync(builder => ...) – same as MSTest.BuildResults fields, dispose
results before kernel. Use concrete BuildResults class (no IBuildResults interface).// MSTest v3 pattern
(_kernel, _resource) = await MsTestResourceHelpers.CreateContainerAsync(
builder => builder.UseImage("redis:alpine").ExposePort("6379"));
// xUnit v3 pattern
public class MyFixture : XunitContainerFixture
{
public MyFixture()
{
InitializeAsync(builder => builder
.UseImage("redis:alpine").WaitForPort("6379/tcp")
).GetAwaiter().GetResult();
}
}
BREAKING CHANGE. v2 used a static toggle (Logging.Enabled() /
Logging.Disabled()) that wrote to System.Diagnostics.Trace. v3 removes that
class entirely and integrates with Microsoft.Extensions.Logging.Abstractions.
An ILoggerFactory is required at the kernel entry point — the compiler
will refuse to build any code that calls FluentDockerKernel.Create() or
constructs KernelBuilder / FluentDockerKernel / DriverRegistry without
one. There is no library-side default; consumers who want silence must pass
NullLoggerFactory.Instance explicitly.
Search for and delete every occurrence of these statements:
Grep for: Logging\.Enabled\(\)
Grep for: Logging\.Disabled\(\)
Grep for: using FluentDocker\.Common;\s*$ (only if Logger was its sole use)
Grep for: Logger\.Log\( (the static helper is gone)
// OLD (v2)
using Ductus.FluentDocker.Services;
Logging.Enabled(); // toggle on
Logging.Disabled(); // toggle off
// NEW (v3) -- supply an ILoggerFactory to the kernel
using Microsoft.Extensions.Logging;
using FluentDocker.Kernel;
using var factory = LoggerFactory.Create(b => b.AddConsole());
var kernel = await FluentDockerKernel.Create(factory)
.WithDockerCli("docker", d => d.AsDefault())
.BuildAsync();
To preserve v2’s Logging.Disabled() semantics (no log output at all), pass
NullLoggerFactory.Instance:
using Microsoft.Extensions.Logging.Abstractions;
var kernel = await FluentDockerKernel.Create(NullLoggerFactory.Instance)
.WithDockerCli("docker", d => d.AsDefault())
.BuildAsync();
In v2 the appsettings.json Logging section did not actually flow into
FluentDocker – the library only checked the static Enabled bool. In v3 the
library logs through MEL, so appsettings.json filters work as expected. Each
FluentDocker type uses its FQN as the log category, so you can filter at any
granularity:
// OLD (v2) -- this block was a no-op for FluentDocker itself, only the host
{
"Logging": {
"LogLevel": {
"Ductus.FluentDocker": "Debug"
}
}
}
// NEW (v3) -- consumed by FluentDocker via your host's LoggerFactory
{
"Logging": {
"LogLevel": {
"Default": "Information",
"FluentDocker.Drivers": "Warning",
"FluentDocker.Services.Impl.ContainerService": "Debug",
"FluentDocker.Kernel": "Information"
}
}
}
The factory built from appsettings.json is passed to
FluentDockerKernel.Create(factory) – the configuration does not flow into
the library automatically.
Ensure the consumer project has the MEL abstractions package (transitively provided by FluentDocker 3.x, but list it explicitly if you create the factory in this project):
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.3" />
<!-- Plus whichever provider you want, e.g. Microsoft.Extensions.Logging.Console -->
FluentDocker v3 logs at these levels:
Debug – best-effort parse skips in streaming readers (stats, events,
compose service info). Routine.Warning – cleanup/disposal failures. Non-fatal but flagged.Error – operation failures the library catches and returns as a failed
CommandResponse.After all changes:
dotnet build and fix any remaining compilation errors.dotnet test and verify tests pass.Ductus.FluentDocker references..Build().Start() chains.ContainerBuilder Build() overrides.| v2 | v3 |
|---|---|
Ductus.FluentDocker.Builders |
FluentDocker.Builders |
Ductus.FluentDocker.Services |
FluentDocker.Services |
Ductus.FluentDocker.Model.* |
FluentDocker.Model.* |
Ductus.FluentDocker.Commands |
Removed (use driver layer) |
Ductus.FluentDocker.MsTest |
FluentDocker.Testing.MsTest |
Ductus.FluentDocker.XUnit |
FluentDocker.Testing.Xunit |
| (new) | FluentDocker.Kernel |
| (new) | FluentDocker.Services.Extensions |
| v2 | v3 |
|---|---|
new Builder().UseContainer() |
new Builder().WithinDriver("docker", kernel).UseContainer(c => ...) |
.UseImage("x") |
c.UseImage("x") (inside lambda) |
.ExposePort(80) |
c.ExposePort("80") (string) |
.WaitForPort("80/tcp", ms) |
c.WaitForPort("80/tcp", ms) |
.Build().Start() |
.Build() or .BuildAsync() |
.UseCompose().FromFile("x") |
.UseCompose(c => c.WithComposeFile("x")) |
.UseNetwork("name") |
.UseNetwork(n => n.WithName("name")) |
.UseVolume("name") |
.UseVolume(v => v.WithName("name")) |
.DefineImage("tag") |
.UseImage("tag", img => ...) |
| v2 | v3 |
|---|---|
container.Start() |
await container.StartAsync() |
container.Stop() |
await container.StopAsync() |
container.Resume() |
container.Start() (sync) or await container.StartAsync() |
container.Pause() |
await container.PauseAsync() |
container.Remove() |
await container.RemoveAsync() |
container.Logs() |
await container.GetLogsAsync() |
container.ExecAsync(...) |
await container.ExecuteAsync(...) |
container.CopyFromAsync(a, b) |
await container.CopyFromToPathAsync(a, b) |
container.GetRunningProcesses() |
await container.ExecuteAsync("ps", "-ef") – no direct equivalent; workaround is ExecuteAsync |
container.Export() |
await container.ExportAsync() |
| v2 | v3 |
|---|---|
stats.CpuPercent |
stats.Cpu.UsagePercent |
stats.MemoryUsage |
stats.Memory.Usage |
stats.MemoryLimit |
stats.Memory.Limit |
stats.MemoryPercent |
stats.Memory.UsagePercent |
stats.NetworkRx |
stats.Network.RxBytes |
stats.NetworkTx |
stats.Network.TxBytes |
stats.BlockRead |
stats.Disk.ReadBytes |
stats.BlockWrite |
stats.Disk.WriteBytes |
| v2 | v3 Builder |
|---|---|
.FromFile("x") |
.WithComposeFile("x") |
.RemoveOrphans() |
.WithRemoveOrphans() |
.WaitForHttp("svc", "url") |
.WithWait() + compose healthchecks |
.ForceRecreate() |
.WithForceRecreate() |
.Build().Start() |
.Build() (auto-starts) |
host.ComposeUp(...) |
composeDriver.UpAsync(ctx, config) |
host.ComposeDown(...) |
composeDriver.DownAsync(ctx, config) |
host.ComposeBuild(...) |
composeDriver.BuildAsync(ctx, config) |
| v2 API | Reason |
|---|---|
new Hosts().Discover(), IMachineDriver |
Docker Machine removed |
DockerToolbox, DOCKER_TOOLBOX_INSTALL_PATH |
Docker Toolbox removed |
Ductus.FluentDocker.Commands.* |
Replaced by driver layer |
SudoMechanism.SetSudo() |
Configure via kernel .WithSudo() |
docker-compose (V1 binary) |
V3 uses docker compose (V2) |
Legacy test packages have been removed. Use the new packages:
| Legacy | New Package |
|---|---|
Ductus.FluentDocker.XUnit |
FluentDocker.Testing.Xunit |
Ductus.FluentDocker.MsTest |
FluentDocker.Testing.MsTest |
| (none) | FluentDocker.Testing.NUnit |
Key changes:
FluentDockerTestBase → XunitContainerFixture with InitializeAsync lambdaFluentDockerTestBase → MsTestResourceHelpers.CreateContainerAsync static helperPostgresTestBase → configure container directly or use plugin packageFluentDocker.Testing.Core (inside main assembly)FluentDocker.Testing.Core.PluginsSee docs/testing/migration-from-legacy.md for complete side-by-side examples.