Summary
This postmortem analyzes a common confusion engineers face when trying to replicate docker context ls using the Moby/Docker Go SDK. The issue stems from a misunderstanding of what Docker “contexts” actually are and where they live. The result is a failed attempt to retrieve context information through the Docker Engine API, even though the Engine has no awareness of contexts at all.
Root Cause
The Docker Engine API does not manage or store Docker contexts.
Contexts are a client-side feature implemented entirely in the Docker CLI, stored locally in files under the user’s configuration directory.
Because of this:
- The Moby/Docker Engine API has no endpoints for listing contexts.
- The Go SDK (which wraps the Engine API) also has no functions for context management.
docker context lsworks only because the CLI reads local metadata, not because the Engine exposes it.
Why This Happens in Real Systems
This misunderstanding is extremely common because:
- The Docker CLI feels like a thin wrapper over the Engine API, but some features are purely client-side.
- Engineers assume that if the CLI can do it, the API can too.
- Docker’s documentation does not clearly separate client features from engine features.
- The Go SDK is named “moby/moby,” which leads developers to believe it mirrors all CLI capabilities.
Real-World Impact
This leads to:
- Wasted engineering time searching for nonexistent API endpoints.
- Incorrect assumptions about Docker’s architecture.
- Broken tooling when developers try to build context-aware applications.
- Confusion when the Engine behaves differently from the CLI.
Example or Code (if necessary and relevant)
To list contexts in Go, you must read the same files the Docker CLI reads.
A minimal example of loading Docker contexts from the local filesystem:
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/context/store"
)
func main() {
dir := config.Dir()
s := store.New(filepath.Join(dir, "contexts", "meta"))
contexts, err := s.List()
if err != nil {
panic(err)
}
for _, c := range contexts {
fmt.Println(c.Name)
}
}
How Senior Engineers Fix It
Experienced engineers recognize that not all CLI features map to the Engine API. Their approach:
- Inspect the CLI source code to see how a feature is implemented.
- Trace file paths used by the CLI (e.g., Docker config directory).
- Use the Docker CLI libraries instead of the Engine API when dealing with client-side features.
- Avoid reinventing the CLI and instead embed or wrap it when appropriate.
- Document architectural boundaries so future developers avoid the same trap.
Why Juniors Miss It
Junior engineers often fall into this pit because:
- They assume the CLI is a thin API wrapper.
- They expect “context” to be a server-side concept.
- They don’t yet know how to read or navigate large open-source codebases.
- They rely on search terms like “moby context list,” which return nothing useful.
- They haven’t learned to distinguish client-side state from engine-side state.
The key takeaway: Docker contexts are a CLI abstraction, not an Engine API feature, so the Go SDK cannot list them unless you read the same metadata files the CLI uses.