Compare commits
2 Commits
32615bf615
...
90f5451ff8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90f5451ff8 | ||
|
|
2e229ceadd |
@@ -0,0 +1,360 @@
|
|||||||
|
# Docker SDK for Python – Working with Images
|
||||||
|
|
||||||
|
This document explains how to **manage Docker images** using the Docker SDK for Python. Instead of treating the SDK as a set of function calls, we’ll approach images the same way Docker itself does: as immutable artifacts that are pulled, built, tagged, pushed, inspected, and eventually cleaned up.
|
||||||
|
|
||||||
|
All examples assume:
|
||||||
|
|
||||||
|
* Docker is installed and running
|
||||||
|
* The Python process has access to the Docker socket
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Creating the Docker Client
|
||||||
|
|
||||||
|
```python
|
||||||
|
import docker
|
||||||
|
|
||||||
|
client = docker.DockerClient(base_url='unix://var/run/docker.sock')
|
||||||
|
ping_docker = client.ping()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* `DockerClient` establishes a connection to the Docker daemon.
|
||||||
|
* `ping()` verifies that Docker is reachable before doing any real work.
|
||||||
|
|
||||||
|
This is a common pattern in automation:
|
||||||
|
|
||||||
|
* Fail fast if Docker is unavailable
|
||||||
|
* Avoid partial execution later in the script
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Pulling Images from a Registry
|
||||||
|
|
||||||
|
```python
|
||||||
|
def pull_image(name_image, tag_image):
|
||||||
|
image = client.images.pull(name_image, tag=tag_image)
|
||||||
|
print(image)
|
||||||
|
```
|
||||||
|
|
||||||
|
### What this does
|
||||||
|
|
||||||
|
* Downloads an image from a registry (Docker Hub or private registry).
|
||||||
|
* If the image already exists locally, Docker may reuse layers.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
* `name_image`: repository name (e.g. `alpine`, `nginx`, `myrepo/app`)
|
||||||
|
* `tag_image`: specific version or variant (e.g. `3.20`, `latest`)
|
||||||
|
|
||||||
|
Returned value:
|
||||||
|
|
||||||
|
* An `Image` object representing the pulled image
|
||||||
|
|
||||||
|
Why this matters:
|
||||||
|
|
||||||
|
* Pulling explicitly avoids relying on implicit image downloads
|
||||||
|
* Makes automation predictable and repeatable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Building Images from a Dockerfile
|
||||||
|
|
||||||
|
### Basic build
|
||||||
|
|
||||||
|
```python
|
||||||
|
def build_image():
|
||||||
|
image, logs = client.images.build(
|
||||||
|
path=".",
|
||||||
|
tag="myapp:1.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
for log in logs:
|
||||||
|
if "stream" in log:
|
||||||
|
print(log["stream"].strip())
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* `path="."` tells Docker to use the current directory as the build context.
|
||||||
|
* Docker automatically looks for a file named `Dockerfile`.
|
||||||
|
* `tag="myapp:1.0"` assigns a name and version to the resulting image.
|
||||||
|
|
||||||
|
The build process returns:
|
||||||
|
|
||||||
|
* `image`: the final built image object
|
||||||
|
* `logs`: a stream of build output messages
|
||||||
|
|
||||||
|
Printing build logs is important because:
|
||||||
|
|
||||||
|
* Docker build failures are only visible in logs
|
||||||
|
* CI pipelines rely on this output for debugging
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Advanced Build with Custom Dockerfile and Build Arguments
|
||||||
|
|
||||||
|
```python
|
||||||
|
def build_image_2():
|
||||||
|
image, logs = client.images.build(
|
||||||
|
path=".",
|
||||||
|
dockerfile="Dockerfile.prod",
|
||||||
|
tag="myapp:prod",
|
||||||
|
buildargs={
|
||||||
|
"APP_ENV": "production",
|
||||||
|
"VERSION": "1.0.0"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
This version adds more control:
|
||||||
|
|
||||||
|
* `dockerfile="Dockerfile.prod"`
|
||||||
|
|
||||||
|
* Allows multiple Dockerfiles per project
|
||||||
|
* Common for dev vs prod builds
|
||||||
|
|
||||||
|
* `buildargs`
|
||||||
|
|
||||||
|
* Passed to `ARG` instructions inside the Dockerfile
|
||||||
|
* Enables parameterized builds without editing the Dockerfile
|
||||||
|
|
||||||
|
Typical DevOps use cases:
|
||||||
|
|
||||||
|
* Environment-specific builds
|
||||||
|
* Injecting version numbers
|
||||||
|
* Feature flags during build time
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Tagging Images
|
||||||
|
|
||||||
|
```python
|
||||||
|
def tag_image():
|
||||||
|
image = client.images.get("myapp:1.0")
|
||||||
|
image.tag("myrepo/myapp", tag="latest")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* Docker images are immutable, but tags are not.
|
||||||
|
* This creates an additional reference to the same image ID.
|
||||||
|
|
||||||
|
Why tagging is important:
|
||||||
|
|
||||||
|
* One image can have multiple tags
|
||||||
|
* Tags represent lifecycle stages (`1.0`, `prod`, `latest`)
|
||||||
|
|
||||||
|
This is how promotion pipelines work:
|
||||||
|
|
||||||
|
* Build once
|
||||||
|
* Tag many times
|
||||||
|
* Push selectively
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Removing Images
|
||||||
|
|
||||||
|
```python
|
||||||
|
def remove_image():
|
||||||
|
client.images.remove("myapp:1.0", force=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* Removes the image reference from the local Docker host.
|
||||||
|
* `force=True` removes the image even if it is in use by stopped containers.
|
||||||
|
|
||||||
|
Use with care:
|
||||||
|
|
||||||
|
* Running containers still prevent deletion
|
||||||
|
* Forced removal is destructive
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Pushing Images to a Registry
|
||||||
|
|
||||||
|
```python
|
||||||
|
def push_image():
|
||||||
|
client.images.push(
|
||||||
|
repository="myrepo/myapp",
|
||||||
|
tag="latest"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* Uploads the image layers to a registry.
|
||||||
|
* Requires prior authentication (`client.login`).
|
||||||
|
|
||||||
|
Important notes:
|
||||||
|
|
||||||
|
* Only new or changed layers are pushed
|
||||||
|
* Tags determine what remote users pull
|
||||||
|
|
||||||
|
This step is usually automated in:
|
||||||
|
|
||||||
|
* CI/CD pipelines
|
||||||
|
* Release workflows
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Cleaning Up Unused Images
|
||||||
|
|
||||||
|
```python
|
||||||
|
def prune_images():
|
||||||
|
result = client.images.prune()
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* Removes dangling images (untagged and unused).
|
||||||
|
* Helps reclaim disk space on build servers.
|
||||||
|
|
||||||
|
The result includes:
|
||||||
|
|
||||||
|
* Number of images removed
|
||||||
|
* Amount of disk space freed
|
||||||
|
|
||||||
|
This is essential for:
|
||||||
|
|
||||||
|
* CI runners
|
||||||
|
* Long-lived build machines
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Inspecting Image Metadata
|
||||||
|
|
||||||
|
```python
|
||||||
|
def inspect_image():
|
||||||
|
alpine_image = client.images.get("alpine:3.20")
|
||||||
|
|
||||||
|
print(alpine_image.attrs)
|
||||||
|
print(alpine_image.attrs["Id"])
|
||||||
|
print(alpine_image.attrs["Size"])
|
||||||
|
print(alpine_image.attrs["Config"]["Env"])
|
||||||
|
print(alpine_image.attrs["Config"]["Cmd"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* `attrs` exposes the raw Docker image inspection data.
|
||||||
|
* This is equivalent to `docker image inspect`.
|
||||||
|
|
||||||
|
Useful fields:
|
||||||
|
|
||||||
|
* `Id`: content-addressable image hash
|
||||||
|
* `Size`: image size in bytes
|
||||||
|
* `Config.Env`: environment variables baked into the image
|
||||||
|
* `Config.Cmd`: default command
|
||||||
|
|
||||||
|
This information is often used for:
|
||||||
|
|
||||||
|
* Debugging unexpected behavior
|
||||||
|
* Auditing images
|
||||||
|
* Validating build output
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Listing Local Images
|
||||||
|
|
||||||
|
```python
|
||||||
|
def image_list():
|
||||||
|
images = client.images.list()
|
||||||
|
for item in images:
|
||||||
|
print(item.id, item.tags)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* Lists all images stored locally.
|
||||||
|
* Each image may have multiple tags or none.
|
||||||
|
|
||||||
|
This mirrors:
|
||||||
|
|
||||||
|
* `docker images`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Ensuring an Image Exists
|
||||||
|
|
||||||
|
```python
|
||||||
|
def ensure_image(name):
|
||||||
|
try:
|
||||||
|
client.images.get(name)
|
||||||
|
print(f"{name} exists")
|
||||||
|
except docker.errors.ImageNotFound:
|
||||||
|
print(f"Pulling {name}")
|
||||||
|
client.images.pull(name)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
This is a very common DevOps pattern:
|
||||||
|
|
||||||
|
* Check if the image exists locally
|
||||||
|
* Pull it only if necessary
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Avoids unnecessary network calls
|
||||||
|
* Makes scripts idempotent
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Important Exceptions to Know
|
||||||
|
|
||||||
|
When working with images, you must handle failures explicitly.
|
||||||
|
|
||||||
|
### `docker.errors.ImageNotFound`
|
||||||
|
|
||||||
|
* Raised when an image does not exist locally
|
||||||
|
* Common when calling `get()`
|
||||||
|
|
||||||
|
### `docker.errors.BuildError`
|
||||||
|
|
||||||
|
* Raised when an image build fails
|
||||||
|
* Usually due to Dockerfile errors or missing files
|
||||||
|
|
||||||
|
### `docker.errors.APIError`
|
||||||
|
|
||||||
|
* Raised for general Docker API failures
|
||||||
|
* Includes permission issues, daemon errors, and network problems
|
||||||
|
|
||||||
|
Catching these exceptions is critical for:
|
||||||
|
|
||||||
|
* Reliable automation
|
||||||
|
* Meaningful error reporting
|
||||||
|
* CI/CD stability
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Summary
|
||||||
|
|
||||||
|
In this section, you learned how to:
|
||||||
|
|
||||||
|
* Pull images from registries
|
||||||
|
* Build images using Dockerfiles
|
||||||
|
* Use build arguments and multiple Dockerfiles
|
||||||
|
* Tag and push images
|
||||||
|
* Inspect and list images
|
||||||
|
* Clean up unused images
|
||||||
|
* Handle common Docker image errors
|
||||||
|
|
||||||
|
This image workflow is the backbone of:
|
||||||
|
|
||||||
|
* CI pipelines
|
||||||
|
* Release automation
|
||||||
|
* Platform engineering systems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
Official Docker SDK for Python documentation:
|
||||||
|
|
||||||
|
* [https://docker-py.readthedocs.io/](https://docker-py.readthedocs.io/)
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
# Docker SDK for Python – Working with Containers
|
||||||
|
|
||||||
|
This document explains how to **manage Docker containers** using the Docker SDK for Python. Containers are the runtime unit of Docker, so this section focuses on lifecycle management: listing, creating, running, starting, stopping, restarting, pausing, and inspecting logs.
|
||||||
|
|
||||||
|
The goal is to understand **how container state changes**, how the Python SDK maps to Docker CLI behavior, and how these operations are typically used in real DevOps automation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Creating the Docker Client
|
||||||
|
|
||||||
|
```python
|
||||||
|
import docker
|
||||||
|
import time
|
||||||
|
|
||||||
|
client = docker.DockerClient(base_url='unix://var/run/docker.sock')
|
||||||
|
ping_docker = client.ping()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* Establishes a connection to the Docker daemon via the Unix socket.
|
||||||
|
* `ping()` is used as a sanity check before executing container operations.
|
||||||
|
|
||||||
|
In production-grade scripts, this check prevents:
|
||||||
|
|
||||||
|
* Silent failures
|
||||||
|
* Partial execution when Docker is down
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Listing Running Containers
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_containers_list():
|
||||||
|
containers = client.containers.list()
|
||||||
|
|
||||||
|
for c in containers:
|
||||||
|
print(c.id, c.name, c.status)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* `containers.list()` returns **only running containers** by default.
|
||||||
|
* Each returned object represents a live container instance.
|
||||||
|
|
||||||
|
Common attributes:
|
||||||
|
|
||||||
|
* `id`: unique container identifier
|
||||||
|
* `name`: human-readable container name
|
||||||
|
* `status`: runtime state (running)
|
||||||
|
|
||||||
|
This is equivalent to:
|
||||||
|
|
||||||
|
* `docker ps`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Listing All Containers (Including Stopped)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_all_containers_list():
|
||||||
|
containers = client.containers.list(all=True)
|
||||||
|
|
||||||
|
for c in containers:
|
||||||
|
print(c.id, c.name, c.status)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* `all=True` includes stopped, exited, and created containers.
|
||||||
|
* Useful for cleanup, auditing, and lifecycle reconciliation.
|
||||||
|
|
||||||
|
Equivalent CLI command:
|
||||||
|
|
||||||
|
* `docker ps -a`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Creating a Container (Without Starting It)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def create_container():
|
||||||
|
container = client.containers.create(
|
||||||
|
image="nginx:latest",
|
||||||
|
name="my_nginx",
|
||||||
|
ports={"80/tcp": 8080},
|
||||||
|
detach=True
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
This creates a container in the **created** state.
|
||||||
|
|
||||||
|
Key parameters:
|
||||||
|
|
||||||
|
* `image`: base image for the container
|
||||||
|
* `name`: explicit container name
|
||||||
|
* `ports`: port mapping (container → host)
|
||||||
|
* `detach=True`: container runs in background when started
|
||||||
|
|
||||||
|
Important distinction:
|
||||||
|
|
||||||
|
* `create()` does **not** start the container
|
||||||
|
* This mirrors `docker create`
|
||||||
|
|
||||||
|
Use cases:
|
||||||
|
|
||||||
|
* Deferred startup
|
||||||
|
* Multi-step initialization
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Running a Container (Create + Start)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def run_container():
|
||||||
|
container = client.containers.run(
|
||||||
|
"nginx:latest",
|
||||||
|
name="nginx_run",
|
||||||
|
ports={"80/tcp": 8081},
|
||||||
|
detach=True
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* `run()` is a convenience method.
|
||||||
|
* Internally performs `create()` followed by `start()`.
|
||||||
|
|
||||||
|
Equivalent CLI command:
|
||||||
|
|
||||||
|
* `docker run -d -p 8081:80 nginx:latest`
|
||||||
|
|
||||||
|
This is the most common approach for:
|
||||||
|
|
||||||
|
* Short-lived workloads
|
||||||
|
* Simple services
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Managing Container Lifecycle
|
||||||
|
|
||||||
|
```python
|
||||||
|
def start_container():
|
||||||
|
container = client.containers.get("my_nginx")
|
||||||
|
|
||||||
|
container.start()
|
||||||
|
time.sleep(1000)
|
||||||
|
|
||||||
|
container.restart()
|
||||||
|
time.sleep(1000)
|
||||||
|
|
||||||
|
container.pause()
|
||||||
|
container.unpause()
|
||||||
|
time.sleep(1000)
|
||||||
|
|
||||||
|
container.stop(timeout=10)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
This function demonstrates multiple lifecycle transitions.
|
||||||
|
|
||||||
|
#### `containers.get(name)`
|
||||||
|
|
||||||
|
* Retrieves an existing container by name or ID.
|
||||||
|
* Raises an exception if the container does not exist.
|
||||||
|
|
||||||
|
#### `start()`
|
||||||
|
|
||||||
|
* Transitions container from `created` or `stopped` → `running`.
|
||||||
|
|
||||||
|
#### `restart()`
|
||||||
|
|
||||||
|
* Stops and immediately starts the container.
|
||||||
|
* Often used after configuration changes.
|
||||||
|
|
||||||
|
#### `pause()` / `unpause()`
|
||||||
|
|
||||||
|
* Freezes container processes using cgroups.
|
||||||
|
* Network and filesystem state remain intact.
|
||||||
|
|
||||||
|
#### `stop(timeout=10)`
|
||||||
|
|
||||||
|
* Sends SIGTERM, then SIGKILL after timeout.
|
||||||
|
* Allows graceful shutdown.
|
||||||
|
|
||||||
|
The `sleep()` calls simulate:
|
||||||
|
|
||||||
|
* Long-running services
|
||||||
|
* Observing container behavior between states
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Reading Container Logs
|
||||||
|
|
||||||
|
```python
|
||||||
|
def log_container():
|
||||||
|
container = client.containers.get("my_nginx")
|
||||||
|
logs = container.logs(tail=20)
|
||||||
|
print(logs.decode())
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* Retrieves logs from the container’s stdout/stderr.
|
||||||
|
* `tail=20` limits output to the last 20 lines.
|
||||||
|
|
||||||
|
Important details:
|
||||||
|
|
||||||
|
* Logs are returned as bytes
|
||||||
|
* Decoding is required for readable output
|
||||||
|
|
||||||
|
Equivalent CLI command:
|
||||||
|
|
||||||
|
* `docker logs --tail 20 my_nginx`
|
||||||
|
|
||||||
|
This is critical for:
|
||||||
|
|
||||||
|
* Debugging runtime issues
|
||||||
|
* Health checks
|
||||||
|
* Observability tooling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Conditional Execution Based on Docker Availability
|
||||||
|
|
||||||
|
```python
|
||||||
|
if ping_docker:
|
||||||
|
get_containers_list()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
* Ensures container operations only run if Docker is reachable.
|
||||||
|
* Prevents unhandled API errors.
|
||||||
|
|
||||||
|
This pattern is common in:
|
||||||
|
|
||||||
|
* Entry-point scripts
|
||||||
|
* Health probes
|
||||||
|
* Automation jobs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Container States (Conceptual Model)
|
||||||
|
|
||||||
|
Understanding container states is essential:
|
||||||
|
|
||||||
|
* `created`: container exists but is not running
|
||||||
|
* `running`: container processes are active
|
||||||
|
* `paused`: execution is suspended
|
||||||
|
* `exited`: container stopped normally
|
||||||
|
* `dead`: container failed unexpectedly
|
||||||
|
|
||||||
|
Every method in this document transitions the container between these states.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Summary
|
||||||
|
|
||||||
|
In this section, you learned how to:
|
||||||
|
|
||||||
|
* List running and stopped containers
|
||||||
|
* Create containers separately from starting them
|
||||||
|
* Run containers in a single step
|
||||||
|
* Control container lifecycle states
|
||||||
|
* Read container logs
|
||||||
|
* Safely execute operations based on Docker availability
|
||||||
|
|
||||||
|
This container-level control is the foundation for:
|
||||||
|
|
||||||
|
* Service orchestration
|
||||||
|
* CI/CD job execution
|
||||||
|
* Debugging and recovery automation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
Official Docker SDK for Python documentation:
|
||||||
|
|
||||||
|
* [https://docker-py.readthedocs.io/](https://docker-py.readthedocs.io/)
|
||||||
|
|||||||
Reference in New Issue
Block a user