Docker with Python
Soooo, I have decided to write more about some of the things that I am doing right now.
Today I decided to port my janky subprocess.run(cmd) approach to managing docker containers to docker-py sdk.
Client
To interact with the local docker service, we first have to create a client.
The client can be created as:
import docker
client = docker.from_env()
There are other options available to for various configurations like providing the DOCKER_HOST argument for remote hosts but I’ll leave it to you for reading up on it from docker client ref.
Image
Once we have the client we can do everything that docker cli can do via python such as creating an image. To create an image we can do:
docker_file_path = "/tmp/cool_docker_project/Dockerfile"
tag = "my_docker_image"
(built_image, output) = client.images.build(
path=docker_file_path, ## this can even just be /tmp/cool_docker_project/
tag=tag
)
image_id = built_image.id
## other attributes are available via attrs like
built_image.attrs
Checkout Image obj ref (the built_image variable is this obj) and images ref for a detailed view.
Container
The containers have the same API as images. Honestly, the docker-py sdk is dead simple to work with. Everything is just an attribute away from the client such as client.networks or client.containers or client.nodes. For the containers, we follow a similar creation logic as the container:
container = client.containers.create(
image=my_image,
name=some_container_name,
detach=True,
auto_remove=True,
platform="linux/amd64",
network=my_network
)
container.start()
Do note that the container might not get started until you explicitly start it. I have not tried it but the docker docs do that so I won’t be the one to break tradition. Again, container references at.
Network/s
Now we have arrived at the main reason that I decided to switch to docker-py from my subprocess jank. The networks are probably the most easily managed with the sdk.
Creating network is as easy as containers but now we can also configure network pools and such.
from docker.types import IPAMConfig, IPAMPool
## lets define an IPAM pool with a subnet
## IPAM = IP Address Management btw
subnet = "192.168.0.0/24"
ipam_pool = IPAMPool(subnet=subnet)
ipam_config = IPAMConfig(pool_configs=[ipam_pools])
## then creating a network is like
networks = []
network = client.networks.create(
name=network_name,
driver="bridge", # or any other driver. and i also found a new driver type that I didn't know existed!
ipam=ipam_config,
enable_ipv6=False,
check_duplicate=True
)
networks.append(network)
The magic is when I want to create - let’s say 100 networks. For that, I can define a hell lot of IPAM pools then create networks at scale and tear them down just as easily.
for network in networks:
network.remove()
As easy as that. Also you can do:
my_networks = client.networks.list(names="testnetwork")
which will match any network with the prefix testnetwork. This is very powerful since I can now manage a fleet of networks with a common basename with ease.
None Network
Before going, I didn’t know that there was a network driver called None. This is what my docker network ls gave me:
NETWORK ID NAME DRIVER SCOPE
my_random_sha256 none null local
which broke some part of my code since it doesn’t provide any IPAM config or address pools and since I was checking whether I had already used an address pool, it was breaking my code (cue pdb). Apparently, if you don’t want any network access to the docker container, you can assign it the none network.
That’s it for today. Tia!