Container runtimes

edit

The library does not run containers itself: it drives a container runtime installed on the machine.

RuntimeContainerRuntimeTalks to
Docker Engine APIContainerRuntime.DockerApiThe Docker daemon, over its socket or named pipe, without the docker CLI
DockerContainerRuntime.DockerThe docker CLI
PodmanContainerRuntime.PodmanThe podman CLI
Apple containerContainerRuntime.AppleContainerApple's container CLI (macOS)
WSL containersContainerRuntime.WslcThe wslc CLI (Windows)

Automatic detection

A definition uses ContainerRuntime.Auto unless you set another runtime. It uses the first runtime of this list that is available:

  1. wslc, on Windows.
  2. Apple's container, on macOS.
  3. The Docker Engine API.
  4. The docker CLI.
  5. The podman CLI.

A runtime is available when its command line is found on the PATH (the Docker Engine API needs none) and its daemon answers. A daemon that is installed but stopped does not count: start Docker Desktop, the Podman machine, or Apple's container service (container system start) before the tests. The first runtime that answers is kept for the rest of the process; when none answers, the next attempt probes them again, so a daemon started later is still found.

When no runtime is available, StartAsync throws an InvalidOperationException that lists the runtimes it looked for.

On a machine that has several runtimes, the order matters: a Mac with both Apple's container and Docker Desktop uses Apple's container as long as its service runs, and a Windows machine with wslc uses it. Choose the runtime explicitly to use another one.

Choose a runtime

Set Runtime on the definition:

var definition = ContainerDefinition.CreateRedis();
definition.Runtime = ContainerRuntime.Podman; // ContainerRuntime.Auto by default

To let each developer and build agent choose, read the runtime from the environment and check that it answers before the tests:

var runtime = Environment.GetEnvironmentVariable("TEST_CONTAINER_RUNTIME") switch
{
    "docker" => ContainerRuntime.Docker,
    "podman" => ContainerRuntime.Podman,
    _ => ContainerRuntime.Auto,
};

// Probes the runtime: runs a command or connects to the daemon
if (!await runtime.IsSupportedAsync())
    throw new InvalidOperationException($"The container runtime '{runtime}' is not available.");

IsSupportedAsync runs a command or opens a connection to the daemon. It caches a success, not a failure. VolumeDefinition.Runtime chooses the runtime of a volume the same way.

Connect to a Docker daemon

The docker and podman runtimes connect wherever their command line is configured to (contexts, DOCKER_HOST). The Docker Engine API runtime finds the daemon the way the docker CLI does, and uses the first source that is set:

  1. DOCKER_HOST: unix://, npipe://, tcp://, http:// or https://.

  2. The docker context named by DOCKER_CONTEXT.

  3. The current context of config.json (currentContext, set by docker context use). The configuration directory is DOCKER_CONFIG, ~/.docker by default. A context reached over TCP uses the TLS certificates stored with it, and honors skip-tls-verify.

  4. Otherwise, or with the default context, the sockets of the usual distributions, in this order, and the docker_engine named pipe on Windows:

    SocketDaemon
    /var/run/docker.sockDocker Engine, Docker Desktop
    ~/.docker/run/docker.sock, ~/.docker/desktop/docker.sock (Linux)Docker Desktop without the default socket
    ~/.colima/default/docker.sock, ~/.colima/docker.sockColima
    ~/.orbstack/run/docker.sockOrbStack
    ~/.rd/docker.sockRancher Desktop
    $XDG_RUNTIME_DIR/docker.sock (/run/user/$UID on Linux)Rootless Docker
    $XDG_RUNTIME_DIR/podman/podman.sockPodman

A DOCKER_HOST or a context that cannot be used (a missing context, an ssh:// address) is not replaced by another daemon: the runtime is unavailable, as the docker CLI would fail.

For DOCKER_HOST with tcp:// or https://, TLS follows the environment variables of the docker CLI:

  • DOCKER_TLS_VERIFY turns on TLS and verifies the daemon certificate against the authorities of ca.pem. The file can hold several certificates, such as an intermediate authority and its root.
  • DOCKER_TLS alone turns on TLS without verifying the daemon certificate.
  • The ca.pem, cert.pem and key.pem files are read from DOCKER_CERT_PATH, the configuration directory by default. cert.pem and key.pem are the client certificate, when the daemon requires one.

When no daemon answers, the "not available" exception lists each endpoint the runtime tried and why it failed. The Logger of a container logs, at the Debug level, the endpoint the Docker Engine API uses, or why the Auto runtime did not use it.

A daemon on another machine publishes the ports of the containers on that machine: see Connect to a remote daemon for the address to connect to.

Runtime differences

Docker Engine API, DockerPodmanApple containerwslc
Volumes (TemporaryVolume)YesYesYes, no driverNo
Networks (TemporaryNetwork), several networks per containerYesYesYes, no driverNo
Pause, restartYesYesNo (a restart is a stop and a start)Pause only (a restart is a stop and a start)
HostnameYesYesNoYes
Privileged mode, extra hosts, healthcheck, stop signal and timeout, exposed ports, tmpfs options (Advanced options)YesYesNoNot verified
HostIp of a portYes, except Windows containersYesYes, IPv4 onlyNo, loopback forwarding of WSL
PullPolicy.NeverYesYesYesNo
Platform of a containerYesYesYesNo
Built images removed and cleaned upYesYesYesNo, they are not labeled
Anonymous volumes removed with the containerYesYesYesNo
ReaperYesYesNoNo
Log timestamps, ContainerLogsOptions.Since and UntilYesYesNoYes, clock of the WSL virtual machine

The Docker Engine API cannot send ExecOptions.StandardInput to a daemon reached over a named pipe, the default on Windows: use the docker runtime there.

Every runtime reports a failed operation with a ContainerRuntimeException. See Troubleshooting.