FluentDocker v2.x.x to v3.0.0 Migration Skill

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.


Step 1: Find all FluentDocker usage

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.


Step 2: Update namespaces

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:


Step 3: Update NuGet package references

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.*" />

Step 4: Add kernel creation

Find the entry point, test setup, or fixture initialization. Add kernel creation using the DockerCli driver (the only driver type available in v2):

Synchronous context

using var kernel = FluentDockerKernel.Create()
    .WithDockerCli("docker", d => d.AsDefault())
    .Build();

Async context (test fixtures, ASP.NET, etc.)

var kernel = await FluentDockerKernel.Create()
    .WithDockerCli("docker", d => d.AsDefault())
    .BuildAsync();

Placement rules


Step 5: Transform builder patterns

Container builder

// 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();

Network builder

// 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();

Volume builder

// 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();

Image builder

// 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();

Compose builder

// 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();

Key transformation rules


Step 6: Fix renamed methods

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()

Step 7: Fix stats model

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

Step 8: Fix SudoMechanism

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.


Step 9: Remove deleted APIs

Docker Machine

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();

Docker Toolbox

Remove DOCKER_TOOLBOX_INSTALL_PATH checks and DockerToolbox class usage.

Commands namespace

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();

Step 10: Fix compose patterns

Direct compose commands

// 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
});

Compose wait strategies

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.


Step 11: Fix dispose patterns

// 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();

Step 12: Migrate test adapters

The legacy FluentDockerTestBase class has been removed. Use the new adapters:

// 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();
    }
}

Step 13: Switch logging to Microsoft.Extensions.Logging

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.

Find and remove the old toggle

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)

Rewrite kernel construction

// 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();

Update appsettings.json category names

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.

Package reference

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 -->

Severity policy (so the AI knows what to expect)

FluentDocker v3 logs at these levels:


Step 14: Verify the migration

After all changes:

  1. Run dotnet build and fix any remaining compilation errors.
  2. Run dotnet test and verify tests pass.
  3. Check for any remaining Ductus.FluentDocker references.
  4. Check for any remaining .Build().Start() chains.
  5. Check for any remaining ContainerBuilder Build() overrides.

Complete API Mapping Reference

Namespace mapping

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

Builder method mapping

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 => ...)

Service method mapping

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()

Stats model mapping

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

Compose method mapping

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)

Removed APIs (no v3 equivalent)

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)

Step 15: Migrate test support packages

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:

See docs/testing/migration-from-legacy.md for complete side-by-side examples.