Images

edit

The image of a container is an ImageSource, passed to the ContainerDefinition constructor or set with definition.Image:

var fromRegistry = new ContainerDefinition(ImageSource.FromRegistry("nginx:1.29")); // a name with a tag or a digest
var fromString = new ContainerDefinition("nginx:1.29"); // a string converts to a registry image
var fromDockerfile = new ContainerDefinition(ImageSource.FromDockerfile("src/MyApp/Dockerfile", contextDirectory: "src"));
var fromArchive = new ContainerDefinition(ImageSource.FromArchive("artifacts/my-app.tar")); // produced by 'docker save'
var fromExisting = new ContainerDefinition(ImageSource.FromExisting("my-app:dev")); // an image already on the machine
Factory methodThe image comes from
ImageSource.FromRegistry(name)A registry, by name with an optional tag or digest. A string converts to it implicitly.
ImageSource.FromDockerfile(dockerfilePath, contextDirectory)A Dockerfile, built when the container is created.
ImageSource.FromArchive(archivePath)An archive written by docker save, loaded when the container is created. The loaded image stays on the machine.
ImageSource.FromExisting(imageId)An image already on the machine, by id or name.

Pin a tag (postgres:17) or a digest rather than latest: a test that ran yesterday then runs against the same server today.

Build an image from a Dockerfile

Use a Dockerfile to test your own application, or an image with test data baked in:

var definition = new ContainerDefinition(ImageSource.FromDockerfile("./Dockerfile", "."));
definition.Ports.Add(new ContainerPort(8080));
definition.WaitStrategies.Add(Wait.ForLogMessage("SERVER READY"));

await using var container = definition.CreateContainer();
await container.StartAsync();
  • Relative paths are resolved against the current directory when you call FromDockerfile. The context directory defaults to the directory of the Dockerfile.
  • The image is built when the container is created. The files of the context keep their permissions, so a script stays executable in the image.
  • The image is built for the container and removed with it, unless the container has a ReuseId. wslc does not label the images it builds, so they are left behind there.
  • The .dockerignore file of the context is applied by every runtime, the Docker Engine API runtime included: the files it excludes are not sent to the builder. A <Dockerfile>.dockerignore file next to the Dockerfile (for instance Dockerfile.dockerignore) takes precedence over it. The Dockerfile and the ignore file are always sent.
  • The output of the build is written to definition.Logging.Logger at the Debug level.

Build options

Pass a DockerfileBuildOptions to set the build arguments, the stage to build, or the platform:

var options = new DockerfileBuildOptions
{
    Target = "runtime",              // the stage of a multi-stage Dockerfile to build
    Platform = "linux/amd64",        // build for another platform than the machine
    Pull = true,                     // pull newer base images
    NoCache = false,                 // keep using the build cache
};
options.BuildArguments.Add("DOTNET_VERSION", "10.0"); // the value of 'ARG DOTNET_VERSION'
options.Labels.Add("org.opencontainers.image.title", "my-app");

var definition = new ContainerDefinition(ImageSource.FromDockerfile("src/MyApp/Dockerfile", "src", options));
OptionDocker CLI flagEffect
BuildArguments--build-argThe values of the ARG instructions. They are hidden in the error messages, so they can carry a token.
Target--targetThe stage of a multi-stage Dockerfile to build. The last stage when not set.
Platform--platformThe platform to build for. ContainerDefinition.Platform when not set.
Pull--pullPull newer versions of the base images, even when they are on the machine.
NoCache--no-cacheBuild every step again, without the build cache.
Labels--labelLabels added to the image.
Tag-tThe name of the image. See below.

The options are copied when you call FromDockerfile: changing them afterwards does not change the image source.

Keep the image between runs

By default, every run builds an image with a random name and removes it with the container. Give the image a name of your own to keep it:

var options = new DockerfileBuildOptions { Tag = "my-app:tests" };
var definition = new ContainerDefinition(ImageSource.FromDockerfile("./Dockerfile", ".", options));

An image with a tag of your choosing belongs to you: it is neither removed with the container nor by the cleanup. Every run still builds it, but the build cache of the runtime makes the build of unchanged sources fast, and the new build replaces the image instead of leaving a new one behind.

Choose when the image is pulled

PullPolicy decides when a registry image is pulled:

ValueBehavior
PullPolicy.IfMissing (default)Pull the image only when it is not on the machine.
PullPolicy.AlwaysPull the image on every run, even when a local copy exists.
PullPolicy.NeverNever pull; fail when the image is not on the machine.
definition.PullPolicy = PullPolicy.Always; // pull a moving tag such as 'latest' on every run

wslc cannot honor PullPolicy.Never.

Choose the platform

Some images have no variant for the architecture of the machine: mcr.microsoft.com/mssql/server is only published for linux/amd64, so it fails on Apple Silicon with "no matching manifest". Set Platform to pull and run the variant of another platform:

definition.Platform = "linux/amd64"; // run the amd64 variant, for instance on Apple Silicon
  • The platform is passed to the pull, to the creation of the container, and to the build of a Dockerfile (unless DockerfileBuildOptions.Platform sets another one).
  • Running an image of another architecture needs an emulator: Rosetta on Apple Silicon (Docker Desktop and Apple's container), or QEMU on Linux.
  • wslc cannot choose the platform of a container: it throws a NotSupportedException when the property is set.
  • A container adopted through ReuseId must have been created for the same platform.

Private registries

The runtimes that drive a command line (docker, podman, container, wslc) use the credentials that command line already has: log in with it once. The Docker Engine API runtime reads the credentials of the docker CLI, including its credential helpers, from the config.json file of the directory in the DOCKER_CONFIG environment variable, or of ~/.docker when it is not set. When it builds a Dockerfile, it sends the credentials of the registries of the FROM and COPY --from images, so a base image can come from a private registry.