| |

Docker 76 🐳 Automating Docker Management using Python (docker-py) and Go SDK

⏱️ Estimated read: 15 min | |

Docker’s official SDKs exist because the Docker CLI is not the only way to talk to the daemon. The Docker Engine API is a RESTful HTTP interface, and the SDKs are language-specific clients that wrap it. The Python SDK (docker-py, installed as docker) and the Go SDK (github.com/docker/docker/client) are the two official options, and they differ in a way that matters for automation: the Python SDK is high-level and ergonomic, while the Go SDK is low-level and closer to the raw API . The Go SDK does not have a Run() method; the Python SDK does. The Go SDK requires you to construct the container configuration, the host configuration, and the networking configuration as separate structs, then call ContainerCreate and ContainerStart explicitly. The Python SDK’s containers.run() wraps all of that into a single call .

The choice between them is not about which language is “better.” It is about which layer of abstraction the automation needs. A Python script that spins up a test container, runs a command, reads the logs, and tears it down is three lines with docker-py and dozens with the Go SDK . A Go program that needs fine-grained control over the container’s resource limits, networking mode, and restart policy can express those directly with the Go SDK’s typed structs, without fighting an abstraction that assumes the defaults are good enough. The Go SDK is the same library that the Docker CLI itself uses, which means it exposes every option the CLI exposes, but it also means the caller is responsible for assembling the options correctly .

Key point: The Python SDK is high-level and concise—client.containers.run() handles create, start, and attach in one call. The Go SDK is low-level and explicit—ContainerCreate and ContainerStart are separate calls with typed configuration structs. Both connect to the same Docker Engine API over the Unix socket or TCP, and both can be configured with the same DOCKER_HOST environment variable.


Why two official SDKs exist

The abstraction problem. The Python SDK was designed for scripting and automation. Its API mirrors the docker CLI: client.containers.run() is the equivalent of docker run, client.containers.list() is docker ps, and client.images.pull() is docker pull . The Go SDK was designed as the foundation for the CLI itself. It exposes the API at a lower level: ContainerList returns a struct with every field the API returns, ContainerCreate takes a container.Config and a container.HostConfig, and ContainerStart is a separate call . The Python SDK is a convenience layer; the Go SDK is the engine.

The verbosity problem. The difference is measurable. The Docker SDK documentation’s own examples show that running a container is three lines in Python and fifty-six lines in Go . The Go code is not doing anything extra—it is constructing the same configuration the Python SDK constructs internally, but the Python SDK hides the construction behind defaults. A Hacker News discussion with a major contributor to the Python SDK confirms the diagnosis: the Go SDK “is very low level, and a lot of the high level stuff happens inside the client code in a way that is hard to extract” . A higher-level Go SDK was prototyped but never prioritized.

The typed configuration problem. The Go SDK’s explicitness is an advantage when the automation needs non-default configuration. A container that needs a memory limit, a CPU quota, a restart policy, and a port binding is expressed as a container.HostConfig with the Resources field set to a container.Resources struct with Memory and NanoCPUs fields, and the PortBindings field set to a nat.PortMap . The Python SDK can express the same configuration, but the parameters are keyword arguments on containers.run(), and the type checking happens at runtime, not at compile time.

The ecosystem problem. The Go SDK is the library the Docker CLI uses, which means it is the canonical implementation. When the CLI gains a new feature, the Go SDK gains it in the same release. The Python SDK is maintained separately, and while it is official, it lags behind the CLI in some areas and has a smaller contributor base. The Docker documentation states that both SDKs are official and can coexist, but the Go SDK is the one that the Docker project itself depends on .


a. The Python SDK: high-level automation

The Python SDK is installed with pip install docker and connects with docker.from_env(), which reads the DOCKER_HOST environment variable or falls back to the default Unix socket . The client exposes resource collections: client.containers, client.images, client.networks, and client.volumes.

# Import the Docker SDK
import docker

# Create a client from the environment
# This reads DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH
# If none are set, it uses the default Unix socket
client = docker.from_env()

# Run a container and capture its output
# The first argument is the image, the second is the command
# run() creates, starts, waits, and removes the container in one call
output = client.containers.run("ubuntu", "echo hello world")
# output is b'hello world\n'
print(output.decode())

# Run a container in the background
# detach=True returns a Container object immediately
container = client.containers.run("nginx:alpine", detach=True, ports={'80/tcp': 8080})

# List running containers
# The list() method returns Container objects
for c in client.containers.list():
    # c.name is the container name, c.image is the image
    print(c.name, c.image, c.status)

# Get a specific container by ID or name
# The get() method raises NotFound if the container does not exist
container = client.containers.get("my-container")

# Read the container's logs
# logs() returns bytes by default; stream=True returns a generator
logs = container.logs()
print(logs.decode())

# Stop the container
# stop() sends SIGTERM and waits for the timeout
container.stop()

The containers.run() method is the highest-level operation. It accepts the image, the command, and keyword arguments for ports, volumes, environment variables, and resource limits. The method creates the container, starts it, and if detach is False, waits for it to finish and returns the output . The detach=True form returns a Container object that can be inspected, stopped, and removed later.

The SDK also exposes a low-level API through client.api, which provides the same methods as the Engine API directly: create_container, start, logs, stop, and so on . This is useful when the high-level API does not expose a specific option, but it requires more code and more careful handling of the API’s types.


b. The Go SDK: low-level control

The Go SDK is installed with go get github.com/docker/docker/client and creates a client with client.NewClientWithOpts(client.FromEnv), which reads the same environment variables as the Python SDK . The client’s methods take a context.Context and a typed options struct, and return typed results.

// Import the Docker client and the types it uses
import (
    "context"
    "fmt"
    "io"
    "os"

    "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/api/types/network"
    "github.com/docker/docker/client"
    "github.com/docker/go-connections/nat"
)

func main() {
    // Create the client from environment variables
    // This reads DOCKER_HOST, DOCKER_API_VERSION, and TLS settings
    cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
    if err != nil {
        panic(err)
    }

    ctx := context.Background()

    // Pull the image first
    // The reader must be consumed and closed
    reader, err := cli.ImagePull(ctx, "nginx:alpine", container.ImagePullOptions{})
    if err != nil {
        panic(err)
    }
    io.Copy(os.Stdout, reader)
    reader.Close()

    // Build the container configuration
    // This is the equivalent of the image and command in docker run
    containerConfig := &container.Config{
        Image: "nginx:alpine",
        Env:   []string{"NGINX_HOST=localhost"},
    }

    // Build the host configuration
    // This is where ports, memory limits, and restart policies live
    hostConfig := &container.HostConfig{
        PortBindings: nat.PortMap{
            "80/tcp": []nat.PortBinding{
                {HostIP: "0.0.0.0", HostPort: "8080"},
            },
        },
        Resources: container.Resources{
            Memory:   256 * 1024 * 1024, // 256 MB
            NanoCPUs: 500000000,          // 0.5 CPU
        },
        RestartPolicy: container.RestartPolicy{
            Name: "on-failure",
        },
    }

    // Create the container
    // The response contains the container ID
    resp, err := cli.ContainerCreate(ctx, containerConfig, hostConfig, &network.NetworkingConfig{}, nil, "web_server")
    if err != nil {
        panic(err)
    }

    // Start the container
    // This is a separate call from create
    if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
        panic(err)
    }

    fmt.Println("Container started:", resp.ID[:12])
}

The Go SDK’s ContainerCreate takes five arguments: the container configuration, the host configuration, the networking configuration, the platform, and the container name . The container configuration defines what runs inside the container; the host configuration defines how the container is isolated and what resources it gets. The separation is explicit, and it is the reason the Go code is longer than the Python code.

The ContainerStart call is separate from ContainerCreate because the API separates them. The Python SDK’s run() method calls both internally, but the Go SDK does not hide that. This separation is useful when the automation needs to create several containers first and start them together, or when it needs to inspect a container between creation and start.


c. Choosing the SDK for the automation

The decision framework is determined by the automation’s complexity and the language of the surrounding system.

Choose the Python SDK when the automation is a script. A test harness that spins up a database, waits for it to be ready, runs a migration, and tears it down is a few dozen lines with docker-py. The high-level methods handle the create-start-wait-remove cycle, and the low-level API is available when a specific option is needed. The Python SDK is also the right choice when the surrounding system is already Python—a Django or Flask application that needs to spawn worker containers, or a data pipeline that needs to run a processing container for each batch.

Choose the Go SDK when the automation is a service. A Go program that manages containers as part of a larger control plane needs the typed configuration and the explicit lifecycle. The Go SDK’s structs are compile-time checked, the context.Context propagates cancellation and deadlines, and the client is safe for concurrent use. The Go SDK is also the right choice when the automation needs to be distributed as a single binary, which is the Go deployment model.

The DOCKER_HOST problem applies to both. Docker Desktop for Linux uses a per-user socket instead of the system-wide /var/run/docker.sock. Both SDKs respect the DOCKER_HOST environment variable, so setting export DOCKER_HOST=unix://$HOME/.docker/desktop/docker.sock is the fix when the SDK cannot connect . The environment variable is the SDK-agnostic configuration mechanism, and it is the first thing to check when the client reports a connection error.


Complete Example Session

The following session builds a Python script that runs a container and a Go program that does the same, demonstrating the abstraction difference.

# ============================================
# PART 1: THE PYTHON SDK SCRIPT
# ============================================
import docker

# Connect to the daemon
client = docker.from_env()

# Run a container and wait for it to finish
# run() creates, starts, waits, and removes by default
result = client.containers.run(
    "alpine:latest",              # the image
    "echo 'hello from python'",   # the command
    remove=True,                  # remove the container after it exits
)
# result is the container's stdout as bytes
print(result.decode())

# Run a detached container with a port binding
container = client.containers.run(
    "nginx:alpine",               # the image
    detach=True,                  # return immediately
    ports={'80/tcp': 8080},       # bind host 8080 to container 80
    name="python-nginx",          # the container name
)
print(container.id[:12])

# Stop and remove the container
container.stop()
container.remove()
// ============================================
// PART 2: THE GO SDK PROGRAM
// ============================================
package main

import (
    "context"
    "fmt"
    "io"
    "os"

    "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/api/types/network"
    "github.com/docker/docker/client"
    "github.com/docker/go-connections/nat"
)

func main() {
    // Create the client from environment variables
    cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
    if err != nil {
        panic(err)
    }

    ctx := context.Background()

    // Pull the image
    reader, err := cli.ImagePull(ctx, "alpine:latest", container.ImagePullOptions{})
    if err != nil {
        panic(err)
    }
    io.Copy(os.Stdout, reader)
    reader.Close()

    // Build the container configuration
    config := &container.Config{
        Image: "alpine:latest",
        Cmd:   []string{"echo", "hello from go"},
    }

    // Create the container
    resp, err := cli.ContainerCreate(ctx, config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
    if err != nil {
        panic(err)
    }

    // Start the container
    if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
        panic(err)
    }

    // Wait for the container to finish
    statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
    select {
    case err := <-errCh:
        if err != nil {
            panic(err)
        }
    case <-statusCh:
    }

    // Read the logs
    out, err := cli.ContainerLogs(ctx, resp.ID, container.LogsOptions{ShowStdout: true})
    if err != nil {
        panic(err)
    }
    defer out.Close()
    io.Copy(os.Stdout, out)

    // Remove the container
    cli.ContainerRemove(ctx, resp.ID, container.RemoveOptions{})
}

The two parts cover the Python script and the Go program. The Python script is shorter because run() handles the create, start, wait, and remove. The Go program is longer because each step is explicit: create, start, wait, read logs, remove. The Go program’s length is the cost of control.


Quick Reference

SDK Installation

SDKCommand
Pythonpip install docker
Gogo get github.com/docker/docker/client

Connection

SDKMethod
Pythondocker.from_env()
Goclient.NewClientWithOpts(client.FromEnv)
BothDOCKER_HOST environment variable

Common Operations

OperationPythonGo
List containersclient.containers.list()cli.ContainerList(ctx, opts)
Run containerclient.containers.run(img, cmd)ContainerCreate + ContainerStart
Get containerclient.containers.get(id)cli.ContainerInspect(ctx, id, opts)
Logscontainer.logs()cli.ContainerLogs(ctx, id, opts)
Stopcontainer.stop()cli.ContainerStop(ctx, id, opts)
Removecontainer.remove()cli.ContainerRemove(ctx, id, opts)

Configuration

AspectPythonGo
ImageFirst arg to run()container.Config.Image
CommandSecond arg to run()container.Config.Cmd
Portsports={...}container.HostConfig.PortBindings
Memorymem_limit="256m"container.Resources.Memory
Restartrestart_policy={...}container.HostConfig.RestartPolicy

Best Practices

✅ Do This:

# Python: use from_env for portable configuration
client = docker.from_env()

# Python: use detach for background containers
container = client.containers.run("nginx", detach=True)
// Go: use API version negotiation
cli, _ := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())

// Go: use context for cancellation
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

❌ Don’t Do This:

# Python: hardcode the socket path
client = docker.DockerClient(base_url="unix:///var/run/docker.sock") # not portable
// Go: ignore the error from ContainerCreate
resp, _ := cli.ContainerCreate(...) // panics later

Common Pitfalls

PitfallWhy It HappensFix
Connection refusedWrong socket (Docker Desktop for Linux)Set DOCKER_HOST
Image not foundImage not pulledPull before create
Port already in useHost port conflictChoose a different port
Go: nil pointerIgnored errorCheck every error
Python: container not removedremove not setremove=True or explicit remove()

Real-World Examples

1. Python: run and wait

result = client.containers.run("alpine", "echo hi")

2. Python: detached

container = client.containers.run("nginx", detach=True, ports={'80/tcp': 8080})

3. Python: list

for c in client.containers.list(): print(c.name)

4. Python: logs

print(container.logs().decode())

5. Go: create

resp, _ := cli.ContainerCreate(ctx, config, hostConfig, &network.NetworkingConfig{}, nil, "")

6. Go: start

cli.ContainerStart(ctx, resp.ID, container.StartOptions{})

7. Go: wait

statusCh, _ := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)

8. Go: logs

out, _ := cli.ContainerLogs(ctx, resp.ID, container.LogsOptions{ShowStdout: true})

9. Go: remove

cli.ContainerRemove(ctx, resp.ID, container.RemoveOptions{})

10. Both: environment

export DOCKER_HOST=unix://$HOME/.docker/desktop/docker.sock

Visual

The Abstraction Layers

┌──────────────────────────────────────────────┐
│  Python SDK (docker-py)                      │
│  high-level: run(), list(), stop()           │
│         │                                    │
│         ▼                                    │
│  Go SDK (docker/client)                      │
│  low-level: ContainerCreate, Start           │
│         │                                    │
│         ▼                                    │
│  Docker Engine API                           │
│  REST over Unix socket or TCP                │
└──────────────────────────────────────────────┘

Python Run Flow

┌──────────────────────────────────────────────┐
│  client.containers.run(img, cmd)             │
│         │                                    │
│         ▼                                    │
│  create + start + wait + remove              │
│         │                                    │
│         ▼                                    │
│  return output                               │
└──────────────────────────────────────────────┘

Go Run Flow

┌──────────────────────────────────────────────┐
│  ImagePull → ImagePullOptions                │
│         │                                    │
│         ▼                                    │
│  ContainerCreate → Config + HostConfig       │
│         │                                    │
│         ▼                                    │
│  ContainerStart → StartOptions               │
│         │                                    │
│         ▼                                    │
│  ContainerWait → WaitCondition               │
│         │                                    │
│         ▼                                    │
│  ContainerLogs → LogsOptions                 │
│         │                                    │
│         ▼                                    │
│  ContainerRemove → RemoveOptions             │
└──────────────────────────────────────────────┘

Connection Configuration

┌──────────────────────────────────────────────┐
│  DOCKER_HOST                                 │
│  ├─ unix:///var/run/docker.sock (default)   │
│  ├─ unix://$HOME/.docker/desktop/... (DD)   │
│  └─ tcp://host:2376 (remote TLS)            │
└──────────────────────────────────────────────┘

Summary

ItemValue
Python SDKdocker (pip)
Python installpip install docker
Python clientdocker.from_env()
Go SDKgithub.com/docker/docker/client
Go installgo get github.com/docker/docker/client
Go clientclient.NewClientWithOpts(client.FromEnv)
ConnectionDOCKER_HOST environment variable
Python abstractionHigh-level, concise
Go abstractionLow-level, explicit

Key takeaways:

  • Both SDKs are official and talk to the same API. The Python SDK is high-level and designed for scripting; the Go SDK is low-level and is the library the Docker CLI itself uses .
  • The Python SDK’s run() is a compound operation. It creates, starts, waits, and optionally removes the container in one call. The Go SDK requires ContainerCreate, ContainerStart, and ContainerWait as separate calls .
  • The Go SDK’s verbosity is the cost of control. The container configuration and the host configuration are separate structs, which makes it possible to set resource limits, port bindings, and restart policies with compile-time type checking .
  • DOCKER_HOST is the SDK-agnostic configuration. Both SDKs read it, and it is the fix for the Docker Desktop for Linux socket difference .
  • The Python SDK has a low-level escape hatch. client.api provides the Engine API methods directly when the high-level API does not expose a specific option .

Remember: The two official Docker SDKs are not competing implementations of the same abstraction. The Python SDK is the high-level scripting interface; the Go SDK is the low-level engine client. The choice is determined by the automation’s complexity and the language of the surrounding system. A Python script that needs to run a container and read its logs should use docker-py because the high-level methods handle the lifecycle. A Go service that needs to manage containers as part of a control plane should use the Go SDK because the typed configuration and the explicit lifecycle are what a control plane needs. Both connect the same way, both respect DOCKER_HOST, and both expose the same underlying API. The difference is how much of the API they hide.


Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!