Moby — Architecture#

Architectural style#

Plugin-based Layered Monolith with a Microkernel Transition in Progress

Moby’s core is a large, layered monolith — daemon/ — that owns container lifecycle, image management, networking, volumes, logging, and cluster orchestration in a single process. However, the architecture is undergoing a systematic transition toward a microkernel style: execution has already been delegated to containerd (a separate process), image storage is being migrated from an embedded graphdriver system to containerd’s native snapshotter, and api/ and client/ have been extracted as independent Go modules.

Evidence of layering: cmd/dockerddaemon/command (bootstrap/CLI) → daemon (core logic) → daemon/server (HTTP API) → daemon/libnetwork, daemon/internal/libcontainerd, daemon/images/daemon/containerd (subsystem implementations). Evidence of microkernel transition: the libcontainerd abstraction layer presents a uniform Client interface regardless of whether the backing containerd is in-process or remote gRPC.


Component diagram (textual)#

┌──────────────────────────────────────────────────────────────────────────────┐
│  cmd/dockerd (binary entry point)                                            │
│    reexec.Init() — worker process re-execution for namespace setup           │
│    └─► daemon/command.NewDaemonRunner → r.Run(ctx)                           │
└────────────────────────────┬─────────────────────────────────────────────────┘
                             │ bootstrap sequence
                             ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  daemon/command — Bootstrap & Lifecycle                                      │
│    loadDaemonCliConfig (flags + daemon.json merge)                           │
│    initContainerd (detect/start managed containerd)                          │
│    initMiddlewares → Server{Experimental, Version, AuthZ middleware chain}   │
│    NewDaemon(ctx, cfg, pluginStore, authzMiddleware)                         │
│    createAndStartCluster (Swarm/SwarmKit integration)                        │
│    initBuildkit (BuildKit session manager + BuildKit builder)                │
│    buildRouters (per-resource Router slice)                                  │
│    httpServer.Serve (gorilla/mux, HTTP/1+HTTP/2+h2c)                        │
└────────────────────────────┬─────────────────────────────────────────────────┘
                             │ owns
                             ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  daemon.Daemon (god-struct — ~30 fields)                                     │
│  ┌──────────────────┐  ┌─────────────────────┐  ┌─────────────────────┐    │
│  │  Container Store  │  │   ImageService      │  │  EventsService      │    │
│  │  (in-memory)      │  │   (interface)       │  │  (pubsub channel)   │    │
│  └──────────────────┘  └──────┬──────────────┘  └─────────────────────┘    │
│                               │ two implementations                          │
│                   ┌───────────┴──────────────┐                              │
│                   ▼                          ▼                               │
│         daemon/images                 daemon/containerd                      │
│         (legacy graphdriver)          (containerd snapshotter)               │
│         daemon/internal/layer         daemon/snapshotter                     │
│         daemon/graphdriver/           (modern path)                          │
│         (overlay2, btrfs, zfs…)                                              │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │  daemon/libnetwork (embedded SDN library)                            │   │
│  │    bridge, overlay (VXLAN/Swarm), macvlan, ipvlan, host              │   │
│  │    iptables, IPAM, osl (OS network layer), networkdb (gossip)        │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │  daemon/internal/libcontainerd (Client interface)                    │   │
│  │    local/ — in-process containerd (legacy, deprecated)               │   │
│  │    remote/ — gRPC to external containerd (primary)                   │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
│  ┌──────────────────────┐  ┌──────────────────┐  ┌───────────────────────┐ │
│  │  daemon/cluster      │  │  daemon/volume   │  │  daemon/pkg/plugin    │ │
│  │  (Swarm/SwarmKit)    │  │  service         │  │  (v2 plugin lifecycle)│ │
│  └──────────────────────┘  └──────────────────┘  └───────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
                             │
                             ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  External process: containerd (gRPC / Unix socket)                           │
│    → runc (OCI runtime) via containerd shim                                  │
│    → containerd snapshotters (overlayfs, native, windows)                    │
└──────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────┐
│  Separate Go modules (independent versioning, same repo)                     │
│    api/   — Docker API types + Swagger spec (no daemon dependency)           │
│    client/ — Go client library for Docker Engine API                         │
└──────────────────────────────────────────────────────────────────────────────┘

Core components#

daemon.Daemon — Central God-Struct#

  • Package: github.com/moby/moby/v2/daemon
  • Responsibility: Owns the full container lifecycle and all management operations. Every API handler ultimately calls a method on *daemon.Daemon.
  • Key types:
    • Daemon struct (~30 fields): containers container.Store, imageService ImageService, netController *libnetwork.Controller, volumes *volumesservice.VolumesService, cluster Cluster, containerd libcontainerdtypes.Client, EventsService *events.Events, pluginManager *plugin.Manager, configStore atomic.Pointer[configStore]
  • Dependencies: Nearly all daemon sub-packages. This is the intentional coupling point of the monolith.

daemon/command — Bootstrap & Lifecycle#

  • Package: github.com/moby/moby/v2/daemon/command
  • Responsibility: cobra CLI wiring, configuration loading, daemon startup/shutdown sequence, systemd notification, signal handling, HTTP server lifecycle.
  • Key types: daemonCLI (holds *daemon.Daemon, *authorization.Middleware, TLS config, shutdown channels), NewDaemonRunner() (public entrypoint from cmd/dockerd/main.go)
  • Dependencies: All top-level daemon subsystems; wires them together manually.

daemon/server — HTTP API Server#

  • Package: github.com/moby/moby/v2/daemon/server
  • Responsibility: HTTP server creation, middleware chain management, route registration via the Router/Route interface pair, error serialization.
  • Key types:
    • Server struct (holds []middleware.Middleware)
    • Router interface (Routes() []Route)
    • Route interface (Handler() APIFunc, Method() string, Path() string)
  • Dependencies: gorilla/mux, per-resource router packages, middleware packages
  • Router implementations: container, image, network, volume, build, system, swarm, plugin, distribution, checkpoint, session, debug — each in daemon/server/router/<resource>/

daemon/server/middleware — Middleware Chain#

  • Package: github.com/moby/moby/v2/daemon/server/middleware
  • Responsibility: Cross-cutting HTTP concerns injected before route handlers.
  • Key middleware:
    • ExperimentalMiddleware — adds Docker-Experimental header
    • VersionMiddleware — validates API version range, injects version into context
    • authorization.Middleware — delegates to external authz plugins (loaded from pkg/authorization/)
  • Pattern: Functional wrapping — each middleware implements WrapHandler(APIFunc) APIFunc

daemon/internal/libcontainerd — Container Runtime Abstraction#

  • Package: github.com/moby/moby/v2/daemon/internal/libcontainerd
  • Responsibility: Abstracts the containerd gRPC API behind Go interfaces, enabling both local (in-process) and remote execution modes.
  • Key interfaces:
    • Client: LoadContainer, NewContainer, Subscribe (event stream)
    • Container: NewTask, Task, Delete, Config, AttachTask
    • Task: Start, Pause, Resume, Kill, Delete, Exec, Stats, Resize
    • Process: Pid, Kill, Resize, Delete
    • Backend: ProcessEvent (callback to daemon for container lifecycle events)
  • Implementations: remote/ (gRPC to external containerd — primary), local/ (deprecated in-process mode)

daemon/images and daemon/containerd — Dual Image Service#

  • Packages: daemon/images (legacy), daemon/containerd (modern)
  • Responsibility: Both implement the daemon.ImageService interface — the unified abstraction for image operations (pull, push, delete, list, history, commit, export, layers).
  • Legacy path (daemon/images + daemon/internal/layer + daemon/graphdriver): Uses overlay2/btrfs/zfs chain-id layer management, separate metadata DB (bbolt), reference store (repositories.json).
  • Modern path (daemon/containerd + daemon/snapshotter): Delegates all image/layer operations to containerd’s content store and snapshotter API. Selected at startup via determineImageStoreChoice().

daemon/libnetwork — Embedded SDN Library#

  • Package: github.com/moby/moby/v2/daemon/libnetwork
  • Responsibility: Full software-defined networking stack: network CRUD, endpoint management, IP address management (IPAM), driver dispatch (bridge, overlay, macvlan, ipvlan, host, null), iptables rules, OS network layer abstraction.
  • Key types: Controller (network lifecycle manager), Network, Endpoint
  • Sub-packages: driverapi/ (driver interface), ipamapi/ (IPAM interface), iptables/, osl/ (OS Layer), networkdb/ (gossip-based network state DB for Swarm overlay), drivers/ (bridge, overlay, macvlan, ipvlan, remote, null, host, windows)
  • Dependencies: hashicorp/go-memdb, hashicorp/go-immutable-radix, hashicorp/memberlist (Swarm gossip), moby/swarmkit

daemon/cluster — Swarm Orchestration#

  • Package: github.com/moby/moby/v2/daemon/cluster
  • Responsibility: Docker Swarm mode — wraps moby/swarmkit to provide cluster join/leave/init, service management, task scheduling, secret/config management, and node lifecycle.
  • Key types: Cluster struct (always present, even in non-Swarm mode), NodeRunner (manages SwarmKit node lifecycle with backoff restart), NodeState (current swarm node gRPC client state)
  • Concurrency: Two-mutex pattern: controlMutex (long-running reconfigurations) + mu (state reads during reconfiguration)
  • Dependencies: moby/swarmkit/v2, libnetwork, daemon image/volume/plugin backends

daemon/internal/builder-next — BuildKit Integration#

  • Package: github.com/moby/moby/v2/daemon/internal/builder-next
  • Responsibility: Bridges BuildKit (the modern build engine) onto Moby’s daemon APIs. Provides snapshotter backends, registry access, and network configuration to BuildKit workers.
  • Dependencies: moby/buildkit, wrapped around daemon.Daemon method surfaces via buildkit.Opt config struct

Data flow#

Container creation (POST /containers/create + POST /containers/{id}/start)#

HTTP request
  → gorilla/mux router
    → VersionMiddleware (validate API version, inject into context)
    → ExperimentalMiddleware (set response header)
    → AuthZ Middleware (optional — delegates to authz plugin over Unix socket)
    → container.Router.postContainersCreate handler
      → daemon.ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, name)
        → runconfig.Merge (normalize config, apply defaults)
        → imageService.GetImage (resolve image reference → image.Image)
        → imageService.CreateLayer (allocate RW layer via graphdriver OR snapshotter)
        → container.NewBaseContainer → container.Container{} assembled
        → container.Store.Add (register in memory)
        → daemon.setHostConfig (apply HostConfig, security opts, cgroups)
        → network.connectToNetwork (libnetwork endpoint creation)
        → container.ToDisk (persist to daemonRepo/<id>/config.v2.json)
      ← returns container ID

  → POST /containers/{id}/start
    → daemon.ContainerStart(ctx, name, hostConfig)
      → container.Store.Get
      → daemon.containerStart(ctx, container, checkpoint, checkpointDir)
        → imageService.GetLayerByID (mount RW layer)
        → libcontainerdClient.NewContainer (create containerd container record)
        → daemon.createSpec(ctx, c) → OCI runtime spec (namespaces, capabilities, mounts, seccomp)
        → libcontainerdClient.Container.NewTask → containerd creates runc process via shim
        → Task.Start() → container becomes Running
        → events.LogContainerEvent(container, "start")
        → statsCollector.Collect(container)

Image pull (POST /images/create?fromImage=...)#

HTTP request
  → distribution.Router.postImagesCreate handler
    → imageService.PullImage(ctx, ref, options)
      [legacy path — daemon/images]:
        → distribution.Pull(ctx, ref, ...) 
          → registry.Service.LookupPullEndpoints (resolve registry URL)
          → distribution.puller.Pull (chunked layer download via HTTP to content store)
          → image.Store.Create (assemble image manifest → image.ID)
          → layer.Store.Register (create layer chain)
          → refstore.AddTag (update repositories.json)
      [modern path — daemon/containerd]:
        → containerd.Client.Pull (delegates entirely to containerd)
          → containerd fetches OCI manifest + layers → content store
          → containerd unpacks → snapshotter creates snapshot chain
        → refstore updated in Moby's identity cache (bbolt)

Initialization / Bootstrap#

The startup sequence is sequential and manually wired (no DI framework):

  1. reexec.Init() — if this is a re-executed worker process (e.g., for network namespace setup), handle it and exit. This is Moby’s mechanism for running privileged operations in a fresh goroutine without fork(2).

  2. command.NewDaemonRunner() — wraps cobra CLI construction. On Windows, additionally wraps in a Windows Service runner.

  3. daemonCLI.start(ctx) — the full bootstrap: a. daemon.CheckSystem() — platform-specific requirements check b. daemon.CreateDaemonRoot() — create data directory with correct permissions c. pidfile.Write() — prevent double-start d. loadListeners() — create Unix/TCP/TLS/named-pipe net.Listener instances e. cli.initContainerd(ctx) — detect if system containerd is running; if not, start a managed containerd subprocess via supervisor.Start() f. otelutil.NewTracerProvider() — initialize OpenTelemetry tracing g. plugin.NewStore() + CDI registration h. initMiddlewares() — construct Server{} with Experimental, Version, and AuthZ middleware i. daemon.NewDaemon() — construct the Daemon struct (~400 lines):

    • Registry service, volume service, plugin manager, event bus
    • containerd gRPC client connection
    • Image service selection (graphdriver OR snapshotter) with optional live migration
    • libcontainerd.NewClient() — container runtime client
    • Load + restore containers from disk j. createAndStartCluster() — initialize SwarmKit node (even if Swarm is not enabled) k. initBuildkit() — BuildKit session manager + builder wiring l. buildRouters() — construct all per-resource Router instances m. httpServer.Handler = newHTTPHandler(gs, apiServer.CreateMux(routers...)) — finalize mux n. httpServer.Serve(ls) for each listener in goroutines o. notifyReady() — systemd sd_notify

Dependency injection: Entirely manual. Subsystems are constructed in sequence and passed as parameters or set as fields on Daemon. There is no wire/dig/fx. The Daemon struct serves as the ambient context for all subsystems.

Config hot-reload: SIGHUP triggers config.Reload(), which re-reads daemon.json, calls daemon.Reload(cfg), and atomically swaps configStore via atomic.Pointer[configStore]. Hot-reloadable fields include log level, logging driver, cluster advertisement settings, and authz plugins.


Configuration#

  • Sources (merged in priority order): CLI flags (pflag) > daemon.json (default: /etc/docker/daemon.json) > compiled-in defaults
  • Mechanism: config.MergeDaemonConfigurations() uses dario.cat/mergo to merge the JSON file onto the flag-initialized Config struct, respecting “value-set” tracking to distinguish explicit zeros from absent fields.
  • Config struct: config.Config — flat struct with ~70 fields covering network, storage, runtime, TLS, logging, Swarm, experimental features, resource limits.
  • Feature flags: Config.Features map[string]bool — runtime feature toggles (e.g., "containerd-snapshotter", "buildkit", "cdi") configurable in daemon.json.
  • Build-time flags: DOCKER_BUILDTAGS injects compile-time features (seccomp, journald, btrfs exclusion, etc.).
  • Version injection: dockerversion.Version, dockerversion.GitCommit set via -ldflags "-X ..." at build time.
  • Live reload: A subset of configuration options can be reloaded on SIGHUP without daemon restart. The configStore is wrapped in atomic.Pointer to allow safe concurrent reads during reload.

Key design decisions#

1. The daemon.Daemon God-Struct#

The Daemon struct aggregates all subsystem references (30+ fields) and exposes hundreds of methods. This is an intentional design for a single-process container engine — all operations need access to multiple subsystems simultaneously (e.g., starting a container requires images, networking, storage, event bus, and the containerd client). The trade-off is high coupling, difficult unit testing, and a 1,900-line initialization function. The ongoing extraction of api/ and client/ as independent modules hints at an eventual decomposition, but the core daemon remains monolithic.

2. Dual Storage Path Coexistence (graphdriver ↔ containerd snapshotter)#

The ImageService interface is the key abstraction that allows the legacy daemon/images + daemon/internal/layer + daemon/graphdriver stack to coexist with the modern daemon/containerd + daemon/snapshotter stack. At startup, determineImageStoreChoice() selects which implementation to instantiate. Both paths implement the same interface. An optional live migration path (migration.NewLayerMigrator) can copy images from the graphdriver to containerd’s content store when no containers are running.

3. libcontainerd Abstraction for Runtime Decoupling#

The libcontainerdtypes.Client interface (with Container, Task, Process sub-interfaces) isolates the daemon from the containerd wire protocol. The remote/ implementation speaks gRPC to an external containerd process; the deprecated local/ implementation ran containerd in-process. This decoupling was essential for the architectural shift from embedded execution to the OCI shim model.

4. Middleware-Based Authorization Plugin System#

Rather than embedding authorization logic in the daemon, the pkg/authorization package implements a middleware that forwards each API request/response to external plugin processes over Unix sockets using a simple JSON protocol. This allows third-party access control engines (e.g., OPA, custom RBAC) without modifying the daemon. The plugins are dynamically loaded and can be reloaded on SIGHUP.

5. Router-per-Resource Pattern#

The HTTP API avoids a monolithic route file by using a Router interface that each resource type implements. Each resource package (container/, image/, network/, etc.) owns its route definitions, HTTP handlers, and backend interfaces. Handlers receive a Backend interface specific to their resource type, defined in daemon/server/backend/ — preventing handlers from directly accessing *daemon.Daemon and enforcing a thin service boundary. This pattern scales the API surface without scaling complexity in a single file.

6. Swarm Always-Present, Content-Conditional#

The Cluster struct is instantiated unconditionally at startup, even if Swarm mode is not enabled. This avoids nil-pointer guards throughout the codebase. When Swarm is not active, the Cluster methods return appropriate “swarm not active” errors. The NodeRunner lifecycle manager handles the swarmkit node with exponential backoff restart — reflecting real-world operational reliability requirements.