Package: dcrx
dcrxDockerX (dcrx) is a library for creating Docker images via an SQL query-builder like API. It is designed to facilitate programmatic, typesafe, and in-memory image generation. dcrx isnota wrapper around the Docker CLI or API, nor is it an implementation of these things. Rather, it is designed a lightweight, single-dependency means of writing and creating images that can then be provided to the Docker CLI or SDK for consumption.Setup and Installdcrx is available via PyPi as an installable package. We recommend using Python 3.10+ and a virtual environment. To install dcrx, run:python-mvenv~/.dcrx&&\source~/.dcrx/bin/activate&&\pipinstalldcrxGetting Starteddcrx uses a combination of method chaining common to SQL query builder libraries with Pydantic type validation to help you build images with fewer mistakes. As an example, let's create a basic Docker image that echoes "Hello world!" to output upon running the Docker image.First let's create an empty Python file:touch hello_world.pyOpen the file and importImagefromdcrx:fromdcrximportImageThe Image class is the primary and only interface for generating and working with Docker images in dcrx. Let's go ahead and create an instance:fromdcrximportImagehello_world=Image("hello-world")Note that we need to provide the name of the image to our class. We can also provide a tag via thetagkeyword argument if needed (the default islatest):fromdcrximportImagehello_world=Image("hello-world",tag="latest")Next let's call thestage()method on ourhello_worldImage instance, passing both the base image name and image tag from which we want to create our image. We'll usepythonas our base image and3.11-slimas our tag:fromdcrximportImagehello_world=Image("hello-world",tag="latest")hello_world.stage("python","3.11-slim")This call translates directly to:FROM python:3.11-slimunderneath, just as if you were writing it in the image yourself! You can also use the optionalaliasarg to name the stage:fromdcrximportImagehello_world=Image("hello-world",tag="latest")hello_world.stage("python","3.11-slim",alias="build")which translates to:FROM python:3.11-slim as buildThis is particularly useful for multi-stage builds.Next let's chain a call to theentrypoint()method, passing a list consisting of the CLI command (echoin this instance) and positional or keyword arguments/values we want to use:fromdcrximportImagehello_world=Image("hello-world",tag="latest")hello_world.stage("python","3.11-slim").entrypoint(["echo","Hello world!"])the call toentrypoint()translates directly to:ENTRYPOINT ["echo", "Hello world!"]just like you would write in a Dockerfile.Finally, we need to write our dcrx image to an actual Dockerfile! Let's chain a final call toto_file()passingDockerfileas the sole argument.fromdcrximportImagehello_world=Image("hello-world",tag="latest")hello_world.stage("python","3.11-slim").entrypoint(["echo","Hello world!"]).to_file("Dockerfile")Now run the script:pythonhello_world.pyYou'll immediately see ourDockerfileis generated in-directory. Opening it up, we see:FROMpython:3.11-slimENTRYPOINT["echo","Hello world!"]Now build your image as you normally would:dockerbuild-thello-world:latest.and run it:dockerrunhello-world:latestwhich outputs:Helloworld!to console. The image works exactly like we'd expect it to! Congrats on building your first dcrx image!Parsing and Resolving ImagesAs of version0.3.0, dcrx now facilitates image loading and resolution allowing you to ingest, simplify, and search Dockerfiles.Let's start by creating the followingDockerfilein an empty directory of your choice:# DockerfileARGPYTHON_VERSIONARGPYTHON_FILE=test.pyFROMpython:${PYTHON_VERSION}RUNapt-getupdate-y&&apt-getinstall-ypython3-devRUNmkdir/srcCOPY${PYTHON_FILE}/srcEXPOSE8000CMD["python",${PYTHON_FILE}]This image tags two arguments, one for the Python version (with no default), and one specifying a Python script to copy and run.We want generate a series of images for Python versions 3.10, 3.11, and 3.12. To do so, let's create a Python scriptgenerate_python_images.py. As before, we'll start by importing theImageclass:# generate_python_images.pyfromdcrximportImageThe image class has several methods to load images from files:from_file()- Loads the specified Dockerfile into the currentImageinstance.from_string()- Parses a Dockerfile already read as existing string, list of strings, bytes, or list of bytes into the currentImageinstance.generate_from_file()- Loads the specified Dockerfile and generates a newImagefile instance. This is aclassmethodand does not require an existingImageinstance.generate_from_string()- Parses a Dockerfile already read as existing string, list of strings, bytes, or list of bytes and generates a newImagefile instance. This is aclassmethodand does not require an existingImageinstance.When choosing betweengenerateorfrommethods, consider whether you want to generate anewImageor simply want to load a Dockerfile into and existingImage. In this case, since we want to generate a series of new image files, we'll use thegenerate_from_filemethod. Add the following to you Python script:# generate_python_images.pyfromdcrximportImageimage_versions=['3.10-slim','3.11-slim','3.12-slim']forversioninimage_versions:version_stub=version.replace('.','')version_tag=f'python-{version_stub}'image=Image.generate_from_file('Dockerfile.python-template',output_path=f'Dockerfile.{version_tag}')image.name='python'image.tag=version_tagprint(image.path)Go ahead and run the script:pythongenerate_python_images.pywhich should output:Dockerfile.python-310-slim
Dockerfile.python-311-slim
Dockerfile.python-312-slimAwesome! We're able to generate the three distinct image instances. Let's explore our images a bit more. Let's examine our generated images'FROMdirectes via thelayers()method. Modify your script as below:# generate_python_images.pyfromdcrximportImageimage_versions=['3.10-slim','3.11-slim','3.12-slim']forversioninimage_versions:version_stub=version.replace('.','')version_tag=f'python-{version_stub}'image=Image.generate_from_file('Dockerfile.python-template',output_path=f'Dockerfile.{version_tag}')image.name='python'image.tag=version_tagprint(image.path)stage_layers=image.layers(layer_types='stage')forstageinstage_layers:print(stage.base,stage.tag,'\n')run the script again, which should output:Dockerfile.python-310-slim
python${PYTHON_VERSION}Dockerfile.python-311-slim
python${PYTHON_VERSION}Dockerfile.python-312-slim
python${PYTHON_VERSION}while our script ran successfully, the images aren't build ready, still containing the templated arguments. This is where theresolve()method comes in! Theresolve()method flood-fills an Image based upon any defaults supplied toARGorENVdirectes (or an optional dictionary specifyingARG/ENVnames or keys and value defaults).Let's modify our script once again as below:# generate_python_images.pyfromdcrximportImageimage_versions=['3.10-slim','3.11-slim','3.12-slim']forversioninimage_versions:version_stub=version.replace('.','')version_tag=f'python-{version_stub}'image=Image.generate_from_file('Dockerfile.python-template',output_path=f'Dockerfile.{version_tag}')image.name='python'image.tag=version_tag# Resolve our Image here:image=image.resolve(defaults={# Note that since PYTHON_VERSION doesn't have# a default, we should use the "defaults" optionsl# arg to provide one from our array of versions.'PYTHON_VERSION':version})stage_layers=image.layers(layer_types='stage')forstageinstage_layers:print(stage.base,stage.tag,'\n')Run the script again, which should output:python 3.10-slim

python 3.11-slim

python 3.12-slimLet's modify the script once more, adding  a call toto_file()to output our images to file:# generate_python_images.pyfromdcrximportImageimage_versions=['3.10-slim','3.11-slim','3.12-slim']forversioninimage_versions:version_stub=version.replace('.','')version_tag=f'python-{version_stub}'image=Image.generate_from_file('Dockerfile.python-template',output_path=f'Dockerfile.{version_tag}')image.name='python'image.tag=version_tag# Resolve our Image here:image=image.resolve(defaults={'PYTHON_VERSION':version})image.to_file()and run the script again:pythongenerate_python_images.pyNote that the script now generates three distinct Dockerfiles -Dockerfile.python-310-slim,Dockerfile.python-311-slim, andDockerfile.python-312-slim. Let's examine,Dockerfile.python-312-slim:ARGPYTHON_VERSION="3.12-slim"ARGPYTHON_FILE="test.py"FROMpython:3.12-slimRUNapt-getupdate-y&&apt-getinstall-ypython3-devRUNmkdir/srcCOPY./test.py/srcEXPOSE8000CMD["python","test.py"]Not justPYTHON_VERSION, but any directive referencingPYTHON_FILEhas been fully resolved to its default. While we want to resolve thePYTHON_VERSION, we may still want to specifyPYTHON_FILEat build time. Let's modify the script once more, using theskipoptional arg to ensure thePYTHON_FILEarg is not resolved:# generate_python_images.pyfromdcrximportImageimage_versions=['3.10-slim','3.11-slim','3.12-slim']forversioninimage_versions:version_stub=version.replace('.','')version_tag=f'python-{version_stub}'image=Image.generate_from_file('Dockerfile.python-template',output_path=f'Dockerfile.{version_tag}')image.name='python'image.tag=version_tag# Resolve our Image here:image=image.resolve(defaults={'PYTHON_VERSION':version},skip=['PYTHON_FILE'])image.to_file()and then run the script one final time:pythongenerate_python_image.pyExaminingDockerfile.python-312-slimonce more:ARGPYTHON_VERSION="3.12-slim"ARGPYTHON_FILE="test.py"FROMpython:3.12-slimRUNapt-getupdate-y&&apt-getinstall-ypython3-devRUNmkdir/srcCOPY./${PYTHON_FILE}/srcEXPOSE8000CMD["python","${PYTHON_FILE}"]Perfect! We've now generated the Dockerfiles required!