Image Building
FluentDocker v3 provides a lambda-based API for building Docker images from Dockerfiles or inline definitions. All builder operations require a kernel and a driver scope.
Step by Step
- Basics: Kernel Setup, Build from Dockerfile, Inline Dockerfile
- Intermediate: Dockerfile Instructions, .NET Application Examples, Build Arguments in Dockerfile
- Advanced: Build with Container, Accessing Build Results, Testing with Custom Images
Kernel Setup
Before using the builder, create a kernel. Multiple kernels per application are supported:
using FluentDocker.Kernel;
using FluentDocker.Builders;
// Create kernel (multiple kernels per app are supported)
var kernel = FluentDockerKernel.Create()
.WithDockerCli("docker", d => d.AsDefault())
.Build();
The kernel manages driver lifecycle. Many apps reuse one kernel across builder calls, but using multiple kernels in the same app is supported.
Build from Dockerfile
Basic Build
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myapp:latest", img => img
.FromFile("/path/to/Dockerfile"))
.Build();
var image = results.All.OfType<IImageService>().First();
Console.WriteLine($"Image: {image.Name}");
Build from Dockerfile String
var dockerfileContent = @"
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD [""node"", ""app.js""]
";
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myapp:latest", img => img
.FromString(dockerfileContent))
.Build();
Inline Dockerfile
Simple Application
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("mynode:latest", img => img
.From("node:18-alpine")
.Run("npm install -g nodemon")
.Add("app.js", "/app/app.js")
.Add("package.json", "/app/package.json")
.UseWorkDir("/app")
.Run("npm install")
.ExposePorts(3000)
.Command("node", "app.js"))
.Build();
Important: The UseImage(name, configure) lambda receives a DockerfileBuilder, not an
IImageBuilder. The DockerfileBuilder provides Dockerfile instructions (.From(), .Run(),
.Copy(), etc.). The IImageBuilder methods listed below are set at a different level – on the
ImageBuilder that wraps the DockerfileBuilder. See the IImageBuilder Methods
section for details.
IImageBuilder Methods
The ImageBuilder class (which implements IImageBuilder) provides build-level configuration
that is separate from the Dockerfile instructions. These methods are not available inside the
UseImage lambda directly. Instead, they can be accessed by calling .ToImage() on the
DockerfileBuilder to return to the ImageBuilder:
| Method | Description |
|---|---|
ReuseIfAlreadyExists() |
Skip the build if an image with the same name/tag already exists |
AsImageName(string name) |
Set or override the image name |
ImageTag(params string[] tags) |
Add additional tags to the built image |
BuildArguments(params string[] args) |
Pass build arguments (format: "KEY=VALUE") |
Label(params string[] labels) |
Add labels to the image metadata (format: "key=value") |
NoCache() |
Disable the build cache |
AlwaysPull() |
Always pull the base image, even if cached locally |
RemoveIntermediate(bool force = false) |
Remove intermediate containers after a successful build |
Platform(string platform) |
Set the target platform (e.g., "linux/amd64") |
Target(string target) |
Set the target build stage in a multi-stage Dockerfile |
With Environment Variables
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myapi:latest", img => img
.From("node:18-alpine")
.Environment("NODE_ENV=production")
.Environment("PORT=8080")
.UseWorkDir("/app")
.Copy("package*.json", "./")
.Run("npm ci --only=production")
.Copy(".", ".")
.ExposePorts(8080)
.Command("node", "server.js"))
.Build();
Multi-Stage Build
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myapp:latest", img => img
// Build stage
.From("node:18-alpine", "builder")
.UseWorkDir("/app")
.Copy("package*.json", "./")
.Run("npm ci")
.Copy(".", ".")
.Run("npm run build")
// Production stage
.From("nginx:alpine")
.Copy("/app/dist", "/usr/share/nginx/html", fromAlias: "builder")
.ExposePorts(80))
.Build();
Note: For multi-stage COPY –from, use the fromAlias parameter on the Copy method.
Dockerfile Instructions
FROM
.From("node:18-alpine")
.From("node:18-alpine", "builder") // Named stage
.From("node:18-alpine", platform: "linux/amd64") // With platform
RUN
.Run("apt-get update && apt-get install -y curl")
.Run("npm install")
COPY and ADD
.Copy("src/", "/app/src/")
.Copy("package.json", "/app/")
.Copy("src/", "/app/src/", chownUserAndGroup: "node:node")
.Copy("/app/dist", "/usr/share/nginx/html", fromAlias: "builder") // COPY --from
.Add("https://example.com/file.tar.gz", "/app/") // ADD can fetch URLs
WORKDIR
.UseWorkDir("/app")
ENV
.Environment("NODE_ENV=production")
.Environment("PORT=8080", "HOST=0.0.0.0")
EXPOSE
.ExposePorts(80)
.ExposePorts(80, 443, 8080)
CMD and ENTRYPOINT
.Command("node", "app.js")
.Entrypoint("docker-entrypoint.sh")
USER
.User("node")
.User("1000", "1000") // UID, GID
VOLUME
.Volume("/data")
.Volume("/data", "/logs", "/config")
LABEL
.Label("version=1.0.0")
.Label("maintainer=dev@example.com")
ARG
.Arguments("VERSION", "1.0.0")
.Arguments("NODE_VERSION")
HEALTHCHECK
.WithHealthCheck("curl -f http://localhost/ || exit 1",
interval: "30s",
timeout: "10s",
retries: 3)
SHELL
.Shell("/bin/bash", "-c")
.NET Application Examples
ASP.NET Core API
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myapi:latest", img => img
// Build stage
.From("mcr.microsoft.com/dotnet/sdk:8.0", "build")
.UseWorkDir("/src")
.Copy("*.csproj", "./")
.Run("dotnet restore")
.Copy(".", ".")
.Run("dotnet publish -c Release -o /app/publish")
// Runtime stage
.From("mcr.microsoft.com/dotnet/aspnet:8.0")
.UseWorkDir("/app")
.Copy("/app/publish", ".", fromAlias: "build")
.Environment("ASPNETCORE_URLS=http://+:8080")
.ExposePorts(8080)
.Entrypoint("dotnet", "MyApi.dll"))
.Build();
.NET Worker Service
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myworker:latest", img => img
.From("mcr.microsoft.com/dotnet/sdk:8.0", "build")
.UseWorkDir("/src")
.Copy(".", ".")
.Run("dotnet publish -c Release -o /app")
.From("mcr.microsoft.com/dotnet/runtime:8.0")
.UseWorkDir("/app")
.Copy("/app", ".", fromAlias: "build")
.Entrypoint("dotnet", "MyWorker.dll"))
.Build();
Python Application
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myflask:latest", img => img
.From("python:3.11-slim")
.UseWorkDir("/app")
.Copy("requirements.txt", ".")
.Run("pip install --no-cache-dir -r requirements.txt")
.Copy(".", ".")
.Environment("FLASK_APP=app.py")
.ExposePorts(5000)
.Command("flask", "run", "--host=0.0.0.0"))
.Build();
Go Application
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("mygo:latest", img => img
// Build stage
.From("golang:1.21-alpine", "builder")
.UseWorkDir("/app")
.Copy("go.mod", "./")
.Copy("go.sum", "./")
.Run("go mod download")
.Copy(".", ".")
.Run("CGO_ENABLED=0 go build -o main .")
// Runtime stage
.From("alpine:latest")
.Run("apk --no-cache add ca-certificates")
.UseWorkDir("/root/")
.Copy("/app/main", ".", fromAlias: "builder")
.ExposePorts(8080)
.Command("./main"))
.Build();
Build with Container
Build an image and immediately run it as a container in the same builder chain:
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myapp:test", img => img
.From("node:18-alpine")
.UseWorkDir("/app")
.Copy(".", ".")
.Run("npm install")
.ExposePorts(3000)
.Command("npm", "start"))
.UseContainer(c => c
.UseImage("myapp:test")
.ExposePort(3000, 3000)
.WaitForPort("3000/tcp", 30000))
.Build();
// Access the running container from results
var container = results.Containers.First();
Build Arguments in Dockerfile
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myapp:latest", img => img
.Arguments("VERSION", "1.0.0")
.Arguments("BUILD_DATE")
.From("node:18-alpine")
.Label("version=${VERSION}")
.Label("build-date=${BUILD_DATE}")
.UseWorkDir("/app")
.Copy(".", "."))
.Build();
Accessing Build Results
The Build() and BuildAsync() methods return a BuildResults object:
var results = new Builder()
.WithinDriver("docker", kernel)
.UseImage("myapp:latest", img => img.From("alpine:latest"))
.UseContainer(c => c
.UseImage("myapp:latest")
.WithName("myapp"))
.Build();
// All services
var allServices = results.All;
// Typed access
var images = results.OfType<IImageService>();
var containers = results.Containers;
// By name (requires .WithName("myapp") on the container builder)
var myContainer = results.GetContainer("myapp");
// Dispose all services when done
results.Dispose();
Note: BuildResults does not have an Images property. To access built images, use the
generic OfType<T>() method:
var images = results.OfType<IImageService>();
// or equivalently:
var images = results.All.OfType<IImageService>().ToList();
The available typed convenience properties on BuildResults are: Containers, Networks,
Volumes, and ComposeServices. For images, always use OfType<IImageService>().
Async Build
For async contexts (ASP.NET, UI applications), use BuildAsync to avoid deadlocks:
var results = await new Builder()
.WithinDriver("docker", kernel)
.UseImage("myapp:latest", img => img
.From("alpine:latest")
.Run("echo 'hello'"))
.BuildAsync();
// Async disposal
await results.DisposeAllAsync();
Testing with Custom Images
public class CustomImageTest : IDisposable
{
private readonly FluentDockerKernel _kernel;
private readonly BuildResults _results;
public CustomImageTest()
{
_kernel = FluentDockerKernel.Create()
.WithDockerCli("docker", d => d.AsDefault())
.Build();
_results = new Builder()
.WithinDriver("docker", _kernel)
.UseImage("test-app:latest", img => img
.From("node:18-alpine")
.UseWorkDir("/app")
.Copy("./test-fixtures/", "/app/")
.Run("npm install")
.ExposePorts(3000)
.Command("npm", "test"))
.UseContainer(c => c
.UseImage("test-app:latest")
.ExposePort(3000, 3000)
.WaitForPort("3000/tcp", 30000))
.Build();
}
[Fact]
public async Task App_ReturnsHealthy()
{
var container = _results.Containers.First();
var endpoint = container.ToHostExposedEndpoint("3000/tcp");
var response = await $"http://localhost:{endpoint.Port}/health".Wget();
Assert.Contains("healthy", response);
}
public void Dispose()
{
_results?.Dispose();
_kernel?.Dispose();
}
}
Next Steps
- Containers - Using built images with containers
- Docker Compose - Multi-container orchestration
- Testing - Test fixtures and base classes