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 migrationTwo 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:
daemon.start.gobuilds the OCI runtime spec viadaemon/oci_linux.go.- It calls
client.NewContainer(ctx, id, spec, runtimeOpts...). - It calls
client.Start(ctx, id, ...)which creates and starts a containerd task. - The monitor goroutine in
daemon/monitor.goconsumes 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
Clientinterface intypes/, implement it in bothlocal/andremote/, expose helpers indaemon/. - Changing how containerd is supervised: see
supervisor/. - Containerd version bumps: update
go.mod(github.com/containerd/containerd/v2) and re-vendor.
See also
- Daemon core for
Daemon.containerdusage. - Image management for the containerd-backed image service that uses the same client.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.