The library underneath

docker-api-go

Every call Docker Swarm MX makes to the Docker Engine API goes through one library: a typed client, written in Go for this app and published on its own. Use it wherever you talk to Docker from Go.

Go 1.26+ · MIT · no Docker CLI · no moby modules

Why a library of its own

Docker Client MX is written in TypeScript and talks to Docker through docker-api-mx. Docker Swarm MX is written in Go, so it got the same library in Go: the same coverage and the same behaviour, written the way Go code is expected to look.

It brings its own transport for Unix sockets, Windows named pipes, TCP, TLS and SSH, so it needs neither the Docker CLI nor the moby modules. And every field it does not type stays in Extra, so the app can change a service without losing what a stack put in its spec.

A context on every call

Every method takes a context.Context. Cancellation and deadlines work everywhere, also halfway through a stream: end the context and a followed log or a pull stops.

Small, predictable handles

Container, Image, Network, Volume, Exec, Service, Node, Task, Secret, Config and Plugin. Getting a handle makes no request, and the Create methods return one too.

Streams and iterators

Pull, push and build return a ProgressStream you read as lines, as events or by waiting. Events and stats are iter.Seq2 iterators you leave with break, and Demux splits logs, attach and exec output into stdout and stderr.

Its own transport, ssh included

Unix sockets, Windows named pipes, TCP and TLS, and DOCKER_HOST as the CLI reads it. Over ssh it runs docker system dial-stdio on the other machine and checks the host key against known_hosts — nothing to open on the server.

Swarm, with its versions

Services, nodes, tasks, secrets and configs. An update names the current Version.Index: pass it to fail on a concurrent change, or leave it out and the library reads it right before the update. A field the library does not type stays in Extra, also when the spec goes back.

Errors you can branch on

A refused request is a *DockerError with its StatusCode, and IsNotFound and IsConflict say what kind. A pull, push or build that fails halfway is a *DockerStreamError with the daemon’s own message.

Quick start

One client, local or on a manager.

Handles are cheap and make no request until you call a method on them. Options are structs, and the JSON follows the Engine API, so the Docker documentation is the documentation.

  • Containers: create, run, start, stop, exec, attach, logs, stats, archives, commit, export
  • Images: pull, push, build (also BuildKit, with registry credentials), save, load, import, search
  • Networks and volumes, including cluster volumes
  • Events, disk usage and prune
  • Swarm: services, nodes, tasks, secrets and configs
  • Plugins and checkpoints
import dockerapi "codeberg.org/MartijnSchimmel/docker-api-go"

ctx := context.Background()

client, err := dockerapi.New(dockerapi.Options{})
if err != nil {
	log.Fatal(err)
}
defer client.Close()

// The nodes of the swarm, as docker node ls shows them.
nodes, _ := client.ListNodes(ctx, dockerapi.NodeListOptions{})
for _, node := range nodes {
	fmt.Println(node.Description.Hostname, node.Spec.Role, node.Status.State)
}

// Scale a service to five tasks.
service := client.Service("web")
info, _ := service.Inspect(ctx, dockerapi.ServiceInspectOptions{})
spec := info.Spec
spec.Mode = dockerapi.Replicated(5)
if _, err := service.Update(ctx, spec, dockerapi.ServiceUpdateOptions{}); err != nil {
	log.Fatal(err)
}

// The same code against a manager, over ssh.
env := map[string]string{"DOCKER_HOST": "ssh://deploy@manager.example.com"}
manager, err := dockerapi.NewFromEnvironment(func(name string) string { return env[name] })
Free and open source.
The README covers the whole API, with examples.
$ go get codeberg.org/MartijnSchimmel/docker-api-go
pkg.go.devCodeberg