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
- Python 3.7+
- A reachable Docker daemon, local or remote, with the Engine API exposed
- pip installed
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:
client.containers.run(...)→POST /containers/createfollowed byPOST /containers/{id}/startclient.images.list()→GET /images/jsonclient.containers.list()→GET /containers/json
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
- Always use TLS client certificates for remote access — never bind an unauthenticated TCP socket beyond a firewalled lab network.
- Pin the
dockerPython package version inrequirements.txtso API behavior doesn’t shift under you across projects. - Prefer the high-level API (
client.containers,client.images) for readability; drop toAPIClientonly for endpoints not yet wrapped. - Set explicit timeouts (
docker.DockerClient(base_url=..., timeout=30)) so a hung remote daemon doesn’t block your automation indefinitely. - Close clients explicitly in long-running processes:
client.close().
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
ConnectionError: Connection aborted | Daemon not listening on TCP, or firewall blocking it | Confirm daemon.json hosts config and open the port |
docker.errors.TLSParameterError | Certificate paths wrong or mismatched CA | Regenerate certs, verify paths match daemon config |
| Calls work with CLI but not docker-py | DOCKER_HOST env var set but base_url not passed | Pass base_url explicitly or call docker.from_env() |
PermissionError on Unix socket | User not in docker group | sudo 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
- Docker SDK for Python documentation: https://docker-py.readthedocs.io/
- Docker Engine API reference: https://docs.docker.com/engine/api/
- Docker daemon configuration: https://docs.docker.com/engine/reference/commandline/dockerd/
- Docker TLS setup guide: https://docs.docker.com/engine/security/protect-access/
