Open-Source Wikis

/

Moby

/

Systems

/

Container runtime

moby/moby

Container runtime

Purpose

Moby does not run containers directly. It delegates to a separate containerd daemon for low-level lifecycle (create-task, start, stop, signal, wait, exec). The integration code lives in daemon/internal/libcontainerd/ — a thin client wrapper that the rest of the daemon talks to via the Client interface in daemon/internal/libcontainerd/types/.

Layout

daemon/internal/libcontainerd/
├── libcontainerd_linux.go    # Linux platform glue
├── libcontainerd_windows.go  # Windows (HCS) platform glue
├── local/                    # Local in-process backend (Windows HCS path)
├── remote/                   # gRPC client to a separate containerd daemon (Linux default)
├── supervisor/               # Owns the containerd subprocess on Linux
├── shimopts/                 # Helpers for OCI runtime shim options
├── types/                    # Public Client + EventInfo interfaces
├── queue/                    # Per-container event queue
└── replace.go                # Hot-swap helpers used during migration

Two backends, one interface

graph TD
  Daemon -->|libcontainerdtypes.Client| Switch{Platform / mode}
  Switch -->|Linux| Remote[remote/<br/>gRPC to containerd]
  Switch -->|Windows| Local[local/<br/>direct HCS]
  Remote --> Containerd[containerd daemon]
  Local --> HCS[Host Compute Service]

On Linux, the daemon spawns its own containerd instance via daemon/internal/libcontainerd/supervisor/ (or attaches to an existing one). On Windows, the runtime path goes directly through HCS in daemon/internal/libcontainerd/local/.

Supervisor

supervisor/ wraps the containerd binary as a child process: it writes a config file, sets the right --root and --state paths under the Moby data root, captures stderr into the Moby log, and restarts containerd if it dies. Configuration shows up under daemon.json as the containerd and containerd-namespace keys.

Event queue

Containerd publishes lifecycle events on a single stream. queue/ buffers and dispatches them per container so handlers in daemon/monitor.go and daemon/start.go see them in order.

Replace

replace.go supports hot-swapping the underlying client during the legacy-to-containerd image-store migration. The Daemon.containerd field is updated atomically while existing operations finish on the old client.

Talking to it

The rest of the daemon uses methods like client.NewContainer, client.Start, client.Status, client.Exec, client.Wait, etc. These are declared on the interface in types/types.go.

A typical container start sequence:

  1. daemon.start.go builds the OCI runtime spec via daemon/oci_linux.go.
  2. It calls client.NewContainer(ctx, id, spec, runtimeOpts...).
  3. It calls client.Start(ctx, id, ...) which creates and starts a containerd task.
  4. The monitor goroutine in daemon/monitor.go consumes lifecycle events and applies the container's restart policy.

Plugin executor

When the daemon launches a managed plugin, it uses the same containerd client through daemon/internal/plugin/executor/containerd/ so plugins are first-class containerd tasks.

Entry points for modification

  • Adding a new runtime call: extend the Client interface in types/, implement it in both local/ and remote/, expose helpers in daemon/.
  • Changing how containerd is supervised: see supervisor/.
  • Containerd version bumps: update go.mod (github.com/containerd/containerd/v2) and re-vendor.

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Container runtime – Moby wiki | Factory