Moby — API Surface#
API types#
- REST/HTTP — primary API (Docker Engine API, ~100 endpoints)
- gRPC over HTTP/2 — BuildKit build and session APIs (multiplexed over same listener)
- Library —
github.com/moby/moby/clientas an independent Go module - Plugin (extension) — v2 plugin HTTP protocol over Unix socket; v1 legacy plugin protocol
- Events stream — SSE-style JSON/NDJSON stream (
GET /events) - Hijacked TCP — container attach/exec (protocol upgrade from HTTP to raw TCP)
REST/HTTP API#
Router#
- Router:
gorilla/mux(versioned path prefix/v{version}/) - Route registration: per-resource Router structs, each implementing
Routerinterface (Routes() []Route). Routes are registered by callingServer.CreateMux(routers...)at startup, which iterates over all routers and registers each route with the shared mux.
Middleware chain#
Applied in order (outermost → innermost) for every request:
ExperimentalMiddleware— setsDocker-Experimental: true/falseresponse headerVersionMiddleware— validates API version in URL prefix against min/max bounds; injects version intocontext.Context; rejects requests outside configured range with a structured errorAuthZ Middleware— if authz plugins configured: serializes full request (method, URI, headers, body up to 4 MiB) as JSON and forwards to each authz plugin over Unix socket; blocks if any plugin returnsAllow: false; also inspects response and can block/modify it
Each middleware implements:
type Middleware interface {
WrapHandler(func(ctx, w, r, vars) error) func(ctx, w, r, vars) error
}API versioning#
- Current max version:
1.54(set indaemon/config/config.go) - Absolute minimum:
1.24(cannot be raised above this by config) - Configurable default minimum:
1.40(operator can lower to1.24viamin-api-versionindaemon.json) - Per-route version gates:
router.WithMinimumAPIVersion("1.25")options function wraps a handler; requests below the required version get a structured version-mismatch error - Experimental routes:
router.Experimentaloption; returns501 Not Implementedif daemon not in experimental mode - Version negotiation: client library auto-negotiates by calling
GET /versionand downgrading to min(server, client)
Authentication#
- Transport security: TLS mutual auth (cert+key, CA verification) for TCP listeners
- Request auth: delegated entirely to external authz plugins; the daemon has no built-in auth (no Bearer tokens, no API keys). The typical deployment places the daemon behind a TLS socket accessible only to trusted processes.
Transport#
Multiple listeners can run simultaneously:
unix:///var/run/docker.sock(default, no TLS)tcp://host:port(optionally with TLS)- Windows named pipe (
npipe://) - HTTP/1.1, HTTP/2, and h2c (HTTP/2 cleartext) all supported on all listeners via
golang.org/x/net/http2
Key endpoints (by router)#
System (system.Router)#
| Method | Path | Description |
|---|---|---|
| GET | /_ping | Health check; returns API version headers |
| GET | /version | Engine version, API version, OS/arch |
| GET | /info | Daemon configuration, stats (single-flight protected) |
| GET | /events | Server-sent event stream (JSON lines / NDJSON) |
| GET | /system/df | Disk usage breakdown |
| POST | /auth | Registry login credential validation |
Containers (container.Router)#
| Method | Path | Description |
|---|---|---|
| GET | /containers/json | List containers |
| GET | /containers/{name}/json | Inspect container |
| POST | /containers/create | Create container |
| POST | /containers/{name}/start | Start container |
| POST | /containers/{name}/stop | Stop container |
| POST | /containers/{name}/restart | Restart container |
| POST | /containers/{name}/kill | Send signal |
| POST | /containers/{name}/pause | Pause (SIGSTOP) |
| POST | /containers/{name}/unpause | Unpause |
| POST | /containers/{name}/exec | Create exec instance |
| POST | /exec/{name}/start | Start exec (hijacks connection) |
| POST | /containers/{name}/attach | Attach stdin/stdout/stderr (hijacks connection) |
| GET | /containers/{name}/attach/ws | Attach via WebSocket |
| GET | /containers/{name}/logs | Stream or tail logs |
| GET | /containers/{name}/stats | Stream resource stats |
| GET | /containers/{name}/top | Process list (ps) |
| POST | /containers/{name}/update | Update resource limits |
| POST | /containers/{name}/rename | Rename container |
| POST | /containers/{name}/wait | Wait for state change |
| DELETE | /containers/{name} | Remove container |
| POST | /containers/prune | Remove stopped containers (API 1.25+) |
| GET/PUT/HEAD | /containers/{name}/archive | Copy files in/out (tar) |
| POST | /commit | Create image from container |
Images (image.Router)#
| Method | Path | Description |
|---|---|---|
| GET | /images/json | List images |
| GET | /images/{name}/json | Inspect image |
| GET | /images/{name}/history | Layer history |
| POST | /images/create | Pull image (fromImage) or import (fromSrc) |
| POST | /images/{name}/push | Push image to registry |
| POST | /images/{name}/tag | Tag image |
| GET | /images/search | Search Docker Hub |
| GET | /images/{name}/get | Export image as tar |
| GET | /images/get | Export multiple images |
| POST | /images/load | Import image from tar |
| DELETE | /images/{name} | Remove image |
| POST | /images/prune | Remove unused images (API 1.25+) |
Build (build.Router)#
| Method | Path | Description |
|---|---|---|
| POST | /build | Build image (v1 classic builder or BuildKit) |
| POST | /build/prune | Prune build cache (API 1.31+) |
| POST | /build/cancel | Cancel active build |
Networks (network.Router)#
| Method | Path | Description |
|---|---|---|
| GET | /networks | List networks |
| GET | /networks/{id} | Inspect network |
| POST | /networks/create | Create network |
| POST | /networks/{id}/connect | Connect container to network |
| POST | /networks/{id}/disconnect | Disconnect container |
| DELETE | /networks/{id} | Remove network |
| POST | /networks/prune | Remove unused networks |
Volumes (volume.Router)#
| Method | Path | Description |
|---|---|---|
| GET | /volumes | List volumes |
| GET | /volumes/{name} | Inspect volume |
| POST | /volumes/create | Create volume |
| PUT | /volumes/{name} | Update volume (API 1.42+, Swarm cluster volumes) |
| DELETE | /volumes/{name} | Remove volume |
| POST | /volumes/prune | Remove unused volumes |
Swarm (swarm.Router)#
| Method | Path | Description |
|---|---|---|
| POST | /swarm/init | Initialize Swarm |
| POST | /swarm/join | Join Swarm |
| POST | /swarm/leave | Leave Swarm |
| GET | /swarm | Inspect Swarm |
| POST | /swarm/update | Update Swarm config |
| POST | /swarm/unlock / GET /swarm/unlockkey | Manager unlock |
| CRUD | /services/{id} | Service management |
| CRUD | /nodes/{id} | Node management |
| GET | /tasks/{id} | Task inspect/list/logs |
| CRUD | /secrets/{id} | Secret management (API 1.25+) |
| CRUD | /configs/{id} | Config management (API 1.30+) |
Plugins (plugin.Router)#
| Method | Path | Description |
|---|---|---|
| GET | /plugins | List plugins |
| GET | /plugins/{name}/json | Inspect plugin |
| POST | /plugins/pull | Install plugin from registry |
| POST | /plugins/create | Create plugin from tar |
| POST | /plugins/{name}/enable | Enable plugin |
| POST | /plugins/{name}/disable | Disable plugin |
| POST | /plugins/{name}/push | Push plugin to registry |
| POST | /plugins/{name}/upgrade | Upgrade plugin (API 1.26+) |
| DELETE | /plugins/{name} | Remove plugin |
Checkpoints (Experimental, checkpoint.Router)#
| Method | Path | Description |
|---|---|---|
| GET | /containers/{name}/checkpoints | List checkpoints |
| POST | /containers/{name}/checkpoints | Create checkpoint (CRIU) |
| DELETE | /containers/{name}/checkpoints/{checkpoint} | Delete checkpoint |
Debug (debug.Router)#
| Path | Description |
|---|---|
/debug/vars | expvar — Go runtime metrics as JSON |
/debug/pprof/ | pprof index |
/debug/pprof/profile | CPU profile |
/debug/pprof/heap, /goroutine, etc. | Standard pprof endpoints |
Session / Distribution#
POST /session— BuildKit session multiplexing (upgrades to h2c; gRPC streams for cache, secrets, auth, SSH forwarding)GET /distribution/{name}/json— OCI manifest/platform info from registry (API 1.30+)
gRPC API#
BuildKit (via HTTP/2 upgrade)#
Moby does not expose a standalone gRPC port. Instead, gRPC is multiplexed over the existing HTTP/2 listeners using two mechanisms:
POST /grpc(deprecatedgrpc.Router) — receives an HTTP/1.1 request, upgrades to HTTP/2 in-process, then dispatches to the gRPC server. Deprecated because it is no longer necessary.- Native h2c — since HTTP/2 is now enabled on all listeners, gRPC clients can connect directly using standard HTTP/2 (h2c for Unix socket, TLS for TCP).
gRPC services registered:
moby.buildkit.v1.Control— BuildKit build control (solve, status, prune, build history)opentelemetry.proto.collector.trace.v1.TraceService— client-side traces forwarded to daemon for inclusion in build history
Proto files: None in moby/moby itself; proto definitions live in the moby/buildkit dependency. Moby delegates to buildkitd.NewController() for service implementation.
gRPC interceptors:
grpcerrors.UnaryServerInterceptor/StreamServerInterceptor— normalize gRPC errorsotelgrpc.StatsHandler— OpenTelemetry tracing- Custom
unaryInterceptor— logs errors atDEBUGlevel, skips tracing for OTLP export calls
Plugin / Extension System#
Moby has two distinct plugin systems with very different designs:
v2 Managed Plugins (primary)#
How it works:
- Plugin is an OCI image distributed via any Docker registry
docker plugin installpulls the image, unpacks it to a content-addressed rootfs- Plugin config (
config.json) declares: plugin type (volume driver, network driver, authz, log driver), required capabilities, settings - On enable,
daemon/pkg/plugin/Managercreates an OCI spec (plugin.InitSpec) and runs the plugin as an isolated container viacontainerd - Plugin process binds to a Unix socket at
/run/docker/plugins/<id>.sock - Daemon communicates with the plugin over this socket using
moby.plugins.http/v1protocol
Plugin protocol (moby.plugins.http/v1):
- HTTP over Unix domain socket
- Handshake:
POST /Plugin.Activate→ plugin returns{"Implements": ["VolumeDriver"]}(orNetworkDriver,authz,LoggingDriver, etc.) - Each subsystem has its own RPC endpoints (e.g.,
VolumeDriver.Create,VolumeDriver.Mount,NetworkDriver.CreateNetwork, etc.) - No OpenAPI/gRPC — plain JSON-over-HTTP
Extension points:
- Volume drivers:
VolumeDriver.{Create,Remove,Mount,Unmount,Path,List,Get} - Network drivers:
NetworkDriver.{CreateNetwork,DeleteNetwork,CreateEndpoint,Join,Leave} - IPAM drivers:
IpamDriver.{GetCapabilities,GetDefaultAddressSpaces,RequestPool,ReleasePool,RequestAddress,ReleaseAddress} - Authorization:
authz— receives fullRequest/ResponseJSON; returnsAllow bool, Msg string - Log drivers:
LogDriver.{StartLogging,StopLogging,Capabilities,ReadLogs} - Image/build secrets (via BuildKit session)
v1 Legacy Plugins (deprecated)#
- Discovered from
/run/docker/plugins/*.sockor spec files in/etc/docker/plugins/,/usr/lib/docker/plugins/ - Not containerized — run as external processes the user starts manually
- Same HTTP protocol as v2, but lifecycle is unmanaged
- Still supported via
pkg/pluginspackage
AuthZ Plugin System#
- Loaded from v1/v2 plugin store
pkg/authorization.Middlewarewraps every HTTP handler- Each request: daemon serializes
{User, UserAuthNMethod, RequestMethod, RequestURI, RequestBody}→ JSON → POST to each authz plugin - Each response: daemon serializes
{RequestBody, ResponseBody, ResponseStatusCode}→ POST to each authz plugin - Combined result: all plugins must allow (AND logic)
- Body capped at 4 MiB to prevent memory exhaustion
- Supports response body modification (plugins can filter privileged content)
Library API (github.com/moby/moby/client)#
A separately versioned Go module providing a complete typed client for the Docker Engine API.
API style#
- Method-per-operation — one function per API call (e.g.,
ContainerStart,ImagePull,NetworkCreate) - Options structs — each method takes an options struct (e.g.,
ContainerStartOptions,ImagePullOptions) with explicit field names, not variadic options - Functional options for client construction —
client.New(client.FromEnv, client.WithAPIVersionFromEnv()) - Result structs — methods return typed result structs (e.g.,
ContainerCreateResult,ImagePullResponse) - Streaming results — streaming endpoints return
io.ReadCloseror channel-based types (e.g.,EventsResult{Messages <-chan, Err <-chan})
Interface decomposition#
APIClient is composed from 16 sub-interfaces:
APIClient
├── stableAPIClient
│ ├── ContainerAPIClient — 22 methods (CRUD + lifecycle + I/O)
│ ├── ExecAPIClient — 5 methods
│ ├── ImageAPIClient — 11 methods
│ ├── ImageBuildAPIClient — 3 methods
│ ├── NetworkAPIClient — 7 methods
│ ├── VolumeAPIClient — 6 methods
│ ├── PluginAPIClient — 10 methods
│ ├── SystemAPIClient — 5 methods (events, info, ping, df, auth)
│ ├── NodeAPIClient — 4 methods (Swarm)
│ ├── ServiceAPIClient — 6 methods (Swarm)
│ ├── TaskAPIClient — 3 methods (Swarm)
│ ├── SwarmAPIClient — 7 methods
│ ├── SecretAPIClient — 5 methods
│ ├── ConfigAPIClient — 5 methods
│ ├── DistributionAPIClient — 1 method
│ └── RegistrySearchClient — 1 method
└── CheckpointAPIClient — 3 methods (experimental)Client configuration#
// Functional options:
client.New(
client.FromEnv, // DOCKER_HOST, DOCKER_API_VERSION, DOCKER_CERT_PATH
client.WithHost("unix:///var/run/docker.sock"),
client.WithAPIVersion("1.41"), // disable negotiation
client.WithHTTPClient(customHTTP),
client.WithTLSClientConfig(cacert, cert, key),
client.WithResponseHook(hook), // intercept raw responses
)API version negotiation#
- Default: client calls
GET /version, compares server version to client max version, uses min of both - Override:
WithAPIVersionorDOCKER_API_VERSIONenv var disables negotiation - Per-request: client injects
X-Docker-API-Versionin URL path, not header
Backward compatibility#
- The client module has its own
go.modatrepositories/moby/client/go.mod - It depends on
github.com/moby/moby/api(types module) but not on the daemon - No explicit stability/semver policy visible in code, but the interface is stable across Docker CLI releases
Connection hijacking#
Attach, exec-start, and DialHijack endpoints upgrade the HTTP connection to a raw bidirectional TCP stream. The HijackDialer interface exposes this:
DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error)This is used for container attach, exec streams, and BuildKit session streams.
API types / serialization#
- Body format: JSON (standard
encoding/json) - Streaming: NDJSON (newline-delimited JSON lines) for events and build output
- Error format:
{"message": "error text"}with HTTP status codes mapped to semantic error types viaerrdefspackage (IsNotFound,IsConflict, etc.) - Types module:
github.com/moby/moby/api— a standalone Go module containing all request/response structs, no daemon dependencies. This is the contract shared between daemon and all clients. - Swagger spec:
api/swagger.yaml— OpenAPI 2.0 spec with version-tagged variants inapi/docs/v1.{26,27,32,44,50,52,53}.yaml. Used for documentation generation and validation. - Content negotiation:
GET /eventssupportsapplication/json,application/x-ndjson,application/json-seqviaAcceptheader.
Notable API design decisions#
1. Router-per-Resource + Backend Interface separation#
Each resource type (container, image, network, etc.) has:
- Its own
Routerstruct with route registration - Its own
Backendinterface (defined within the router package) - Handlers receive only the
Backendinterface, not*daemon.Daemon
This enforces a soft API boundary even within the monolith. New endpoints can only call methods on the resource-specific Backend, not arbitrary daemon internals. The result is a codebase where adding a new API endpoint requires choosing which Backend interface to extend, making the contract explicit.
2. Per-route version gating via option functions#
Routes are annotated with version constraints using Go option functions at declaration time:
router.NewPostRoute("/containers/prune", c.postContainersPrune, router.WithMinimumAPIVersion("1.25"))
router.NewGetRoute("/containers/{name}/checkpoints", cr.getContainerCheckpoints, router.Experimental)This is cleaner than runtime version checks scattered in handler bodies and makes the API version matrix visible in initRoutes().
3. BuildKit session multiplexing#
BuildKit’s session API (used for cache mounts, SSH agent forwarding, secrets, OIDC tokens) is multiplexed via a single POST /session endpoint that upgrades to HTTP/2 and then carries multiple gRPC streams. This allows a single daemon HTTP listener to serve both Docker API REST and BuildKit gRPC without separate ports or processes.
4. Plugin protocol designed for filesystem discovery#
v1/v2 plugins are discovered via filesystem conventions (/run/docker/plugins/*.sock, /etc/docker/plugins/*.spec) and activated lazily. The GET /Plugin.Activate handshake returns a capability list that determines which subsystem the plugin implements. This means adding a new plugin type doesn’t require daemon changes — only a new spec file and a process that speaks the HTTP protocol.
5. AuthZ as response-intercepting middleware#
Unlike most authorization systems that only check requests, Moby’s authz middleware also passes responses through all authz plugins. This allows plugins to strip sensitive data from responses (e.g., redact environment variables containing secrets from GET /containers/{id}/json) — a capability unique to Moby’s design.
6. Separate api/ module for type contracts#
The api/types/ module (github.com/moby/moby/api) has its own go.mod and contains only type definitions — no HTTP code, no daemon code. This means tools generating clients from OpenAPI specs, or projects that need type compatibility, can depend on just the types module without pulling in the entire daemon. This is an intentional decoupling for ecosystem health.