How to Use Docker-Py to Access the Docker Daemon Remotely: Python SDK and API Integration

How to Use docker-py to Access the Docker Daemon Remotely

How to Use docker-py to Access the Docker Daemon Remotely

If you’ve ever wanted to control Docker from a Python script instead of typing CLI commands, docker-py is the library you want. It’s the official Python SDK for the Docker Engine API, and once you understand how it talks to the daemon, you can build automation, dashboards, CI tooling, or full orchestration logic entirely in Python — including against daemons running on remote machines.

What Docker-Py Actually Does

Under the hood, the Docker CLI itself is just a client that sends HTTP requests to the Docker daemon’s REST API, whether that daemon is local (over a Unix socket) or remote (over TCP). docker-py wraps that same REST API in a clean, Pythonic interface, so instead of shelling out to docker run ... you call client.containers.run(...).

This matters for remote access because the daemon doesn’t care whether the request came from the docker binary or from docker-py — it only cares about the HTTP request and how it’s authenticated. That means anything you can do with the CLI against a remote host, you can do with docker-py, often more flexibly.

Prerequisites

Install the SDK:

pip install docker

Verify the installed version:

python3 -c "import docker; print(docker.__version__)"

Connecting to a Local Daemon First

Before jumping to remote, confirm the basics work locally:

import docker

client = docker.from_env()
print(client.version())

Expected output (truncated):

{'Platform': {'Name': ''}, 'Version': '27.3.1', 'ApiVersion': '1.47', ...}

docker.from_env() reads the same environment variables the CLI does (DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH), which is exactly what makes switching to remote access almost trivial.

Exposing the Remote Daemon (Server Side)

On the remote host, the daemon needs to listen on a TCP socket in addition to (or instead of) the default Unix socket. Edit /etc/docker/daemon.json:

{
  "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"]
}

If you’re using systemd (most modern distros), you also need to override the systemd unit, since it usually specifies -H flags directly:

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/override.conf <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
EOF
sudo systemctl daemon-reexec
sudo systemctl restart docker

Restart and confirm the daemon is listening:

sudo systemctl restart docker
sudo ss -tlnp | grep 2376
LISTEN  0  4096  0.0.0.0:2376  0.0.0.0:*  users:(("dockerd",pid=1123,fd=15))

Important: binding 0.0.0.0:2376 without TLS exposes root-equivalent control of that host to anyone on the network. Treat this as a lab-only step; see the security section below for the production-safe approach.

Connecting Docker-Py to the Remote Daemon

From your Python client machine:

import docker

client = docker.DockerClient(base_url="tcp://192.168.1.20:2376")
print(client.info())

If successful, you’ll get the full daemon info dictionary, including containers, images, and system details for the remote host — not your local machine.

Running Containers Remotely

container = client.containers.run(
    "nginx:latest",
    detach=True,
    name="remote-nginx",
    ports={"80/tcp": 8081}
)
print(container.id, container.status)

Expected output:

a1b2c3d4e5f6... running

List running containers on the remote host:

for c in client.containers.list():
    print(c.name, c.status, c.image.tags)
remote-nginx running ['nginx:latest']

Stop and remove it when done:

container.stop()
container.remove()

Building Images Remotely

You can build an image on the remote daemon by sending the build context over the wire:

image, build_logs = client.images.build(path="./my-app", tag="my-app:1.0")
for line in build_logs:
    if "stream" in line:
        print(line["stream"], end="")

The Docker context (your Dockerfile and surrounding files) is tarred up locally and streamed to the daemon, which does the actual build — this is identical to how docker build behaves against a remote DOCKER_HOST.

Working with the Low-Level API

For anything not covered by the high-level object model (containers, images, networks, volumes), drop down to APIClient for raw Engine API access:

from docker import APIClient

api = APIClient(base_url="tcp://192.168.1.20:2376")
for event in api.events(decode=True):
    print(event["Action"], event.get("Type"))

This streams live daemon events (container starts, stops, image pulls, network changes) — extremely useful for building monitoring dashboards or audit logs.

Managing Networks and Volumes Remotely

network = client.networks.create("app-net", driver="bridge")
volume = client.volumes.create(name="app-data")

container = client.containers.run(
    "postgres:16",
    detach=True,
    name="remote-db",
    network="app-net",
    volumes={"app-data": {"bind": "/var/lib/postgresql/data", "mode": "rw"}},
    environment={"POSTGRES_PASSWORD": "changeme"}
)

Securing the Connection with TLS

Plaintext TCP is unacceptable outside an isolated lab network. Generate certificates (see the companion guide on securing the Docker daemon for the full CA setup), then connect like this:

import docker

tls_config = docker.tls.TLSConfig(
    client_cert=("/path/to/cert.pem", "/path/to/key.pem"),
    ca_cert="/path/to/ca.pem",
    verify=True
)

client = docker.DockerClient(base_url="tcp://192.168.1.20:2376", tls=tls_config)
print(client.version())

On the daemon side, this requires dockerd to be started with --tlsverify, --tlscacert, --tlscert, and --tlskey flags — covered in detail in the TLS security guide.

Internal Working: What Happens on the Wire

Every docker-py call ultimately resolves to an HTTP request against a versioned REST endpoint, for example:

You can watch this directly by enabling debug logging:

import logging
logging.basicConfig(level=logging.DEBUG)

This is useful for troubleshooting since it shows you the exact endpoint, headers, and payload docker-py sent, which you can then reproduce with curl for isolation testing:

curl --unix-socket /var/run/docker.sock http://localhost/containers/json

Error Handling

Docker-py raises specific exceptions you should catch in production code:

from docker.errors import APIError, NotFound, ImageNotFound

try:
    client.containers.get("nonexistent")
except NotFound:
    print("Container does not exist")
except APIError as e:
    print(f"Daemon returned an error: {e}")

Real-World Use Case: A Simple Fleet Health Checker

import docker

hosts = ["tcp://10.0.0.11:2376", "tcp://10.0.0.12:2376", "tcp://10.0.0.13:2376"]

for host in hosts:
    client = docker.DockerClient(base_url=host, tls=tls_config)
    unhealthy = [
        c.name for c in client.containers.list(all=True)
        if c.status != "running"
    ]
    print(f"{host}: {len(unhealthy)} unhealthy containers -> {unhealthy}")

This kind of script is the backbone of many lightweight internal fleet-monitoring tools before teams graduate to full observability stacks.

Best Practices

Troubleshooting

SymptomCauseFix
ConnectionError: Connection abortedDaemon not listening on TCP, or firewall blocking itConfirm daemon.json hosts config and open the port
docker.errors.TLSParameterErrorCertificate paths wrong or mismatched CARegenerate certs, verify paths match daemon config
Calls work with CLI but not docker-pyDOCKER_HOST env var set but base_url not passedPass base_url explicitly or call docker.from_env()
PermissionError on Unix socketUser not in docker groupsudo usermod -aG docker $USER and re-login

Summary

Docker-py gives you full programmatic control over any Docker daemon — local or remote — using the same REST API that powers the CLI. Once you’ve exposed the daemon safely (ideally behind TLS or an SSH tunnel) and installed the SDK, you can create containers, manage images, stream events, and build entire automation pipelines in Python with a few lines of code.

References

Exit mobile version