Skip to content

Introduction

dib is a tool designed to help build multiple Docker images defined within a directory, possibly having dependencies with one another, in a single command.

Warning

dib is still at an early stage, development is still ongoing and new minor releases may bring some breaking changes. This may occur until we release the v1.

Purpose

As containers have become the standard software packaging technology, we have to deal with an ever-increasing number of image definitions. In DevOps teams especially, we need to manage dozens of Dockerfiles, and the monorepo is often the solution of choice to store and version them.

We use CI/CD pipelines to help by automatically building and pushing the images to a registry, but it's often inefficient as all the images are rebuilt at every commit/pull request. There are possible solutions to optimize this, like changesets detection or build cache persistence to increase efficiency, but it's not an easy task.

Also, being able to test and validate the produced images was also something that we were looking forward to.

dib was created to solve these issues, and manage a large number of images in the most efficient way as possible.

Concepts

Before using dib, there are important basic concepts to know about, to understand how it works internally.

Build Directory

dib needs a path to a root directory containing all the images it should manage. The structure of this directory is not important, dib will auto-discover all the Dockerfiles within it recursively.

Example with a simple directory structure:

images/
├── alpine
|   └── Dockerfile
└── debian
    ├── bookworm
    |   └── Dockerfile
    └── bullseye
        └── Dockerfile

In order to be discovered, the Dockerfile must contain the name label:

LABEL name="alpine"

If the name label is missing, the image will be ignored and dib won't manage it.

Dependency Graph

Because some images may depend on other images (when a FROM statement references an image also defined within the build directory), dib internally builds a graph of dependencies (DAG). During the build process, dib waits until all parent images finish to build before building the children.

Example dependency graph:

graph LR
  A[alpine] --> B[nodejs];
  B --> C[foo];
  D[debian] --> E[bar];
  B --> E;

In this example, dib will wait for the alpine image to be built before proceeding to nodejs, and then both alpine and bullseye can be built in parallel (see the --rate-limit build option).

Once debian is completed, the build of bar begins, and as soon as nodejs is completed, foo follows.

Image Version Tag

dib only builds an image when something has changed in its build context since the last build. To track the changes, dib computes a checksum of all the files in the context, and generates a human-readable tag out of it. If any file changes in the build context (or in the build context of any parent image), the computed human-readable tag changes as well.

dib knows it needs to rebuild an image if the target tag is not present in the registry.

Placeholder Tag

When updating images having children, dib needs to update the tags in FROM statements in all child images before running the build, to match the newly computed tag.

Example:

Given a parent image named "parent":

LABEL name="parent"

And a child image referencing the parent:

FROM registry.example.com/parent:REPLACE_ME
LABEL name="child"

When we build using the same placeholder tag:

dib build \
  --registry-url=registry.example.com \
  --placeholder-tag=REPLACE_ME

Then any change to the parent image will be inherited by the child. By default, the placeholder tag is latest.

In some cases, we want to be able to freeze the version of the parent image to a specific tag. To do so, just change the tag in the FROM statement to be anything else than the placeholder tag:

FROM registry.example.com/parent:some-specific-tag
LABEL name="child"

Then any change to the parent image will not be inherited by the child.

Tag promotion

dib always tries to build and push images when it detects some changes, by it doesn't move the reference tag (latest by default) to the latest version. This allows dib to run on feature branches without interfering with one another. Once the changes are satisfying, just re-run dib with the --release flag to promote the current version with the reference tag.

Example workflow

Let's assume we have a simple GitFlow setup, with CI/CD pipelines running on each commit to build docker images with dib.

When one creates a branch from the main branch, and commits some changes to an image. dib builds and pushes the cat-south tag, but latest still references the same tag (beacon-two):

gitGraph
       commit id: "autumn-golf"
       commit id: "beacon-two" tag: "latest"
       branch feature
       commit id: "cat-south"

Once the feature branch gets merged, the cat-south tag is promoted to latest:

gitGraph
       commit id: "autumn-golf"
       commit id: "beacon-two"
       branch feature
       commit id: "cat-south"
       checkout main
       merge feature
       commit id: "cat-south " tag: "latest"

License

dib is licensed under the CeCILL V2.1 License