Skip to content

Docker registry images

Whitebox publishes several Docker images to the GitLab container registry. These are managed by packaging/scripts/maintenance/build_registry_images.sh and live under packaging/docker/injection_images/ and packaging/docker/standalone_images/.

Whitebox base image

The whitebox-base image (standalone_images/whitebox-base/) is the foundation for all Whitebox Docker builds and CI jobs. It provides:

  • Ubuntu 24.04
  • Python 3.10 (deadsnakes PPA)
  • Node.js 22 (NodeSource)
  • Poetry (custom Whitebox fork)
  • corepack (yarn/pnpm management)
  • Build tools (gcc, g++, make) and shared system libraries

All backend and frontend images inherit from this base, and CI jobs use it as their default image — so Python, Node, and Poetry are available everywhere without reinstallation.

To rebuild and push the base image:

./packaging/scripts/maintenance/build_registry_images.sh --push whitebox-base

Canonical tag + sync workflow

packaging/docker/standalone_images/whitebox-base/whitebox-base.Dockerfile (ENV REGISTRY_TAG=...) is the canonical source of truth for the whitebox-base registry tag.

When bumping whitebox-base, follow this sequence:

  1. Update ENV REGISTRY_TAG in the whitebox-base Dockerfile.
  2. Sync all consumer references (CI, Compose, Ansible):
python3 packaging/scripts/maintenance/sync_whitebox_base_tag.py sync
  1. Verify there is no drift:
python3 packaging/scripts/maintenance/sync_whitebox_base_tag.py check
  1. Build and publish the new whitebox-base image:
./packaging/scripts/maintenance/build_registry_images.sh --push whitebox-base

CI also runs the check command, so manual edits to consumer tags are caught.

Injection images

Some utilities that Whitebox requires to operate need to be built from source (for example, ffmpeg). As those images seldom update, we can build them once and reuse that build in all future builds.

As an example, take a look at packaging/docker/injection_images/ffmpeg.Dockerfile. It downloads ffmpeg and compiles it with flags that we need, allowing us to reuse this image. For easier access, this image is distributed to Whitebox project's container registry.

Using injection images

To use an injection image, you should find it at the container repository, and you can then copy its files inside the container you are building. For example, with ffmpeg, everything that's compiled resides in /usr/local/, so to use it, you need to COPY all those files into your build:

COPY --from=IMAGE_LOCATION /usr/local/ /usr/local/

Some injection images may need additional steps, such as dynamic linked update, additional dependency installations through package manager, etc. Refer to the originating Dockerfile's comments, in this example - ffmpeg.Dockerfile.

Building and managing injection images

To simplify image management, there is a packaging/scripts/maintenance/build_registry_images.sh script available. You can use it to build (and push) *.Dockerfiles inside packaging/docker/injection_images/ and packaging/docker/standalone_images/.

You can either specify image names, or provide --all argument to specify build targets. Additionally, provide --push to push them to registry.

Tag version, obtained from Dockerfile's ENV REGISTRY_TAG=VERSION_VALUE line, will be used to tag the resulting image and will be pushed with that tag. If the file does not contain such a line, image will not be built.

Multi-architecture builds

By default, the build script produces images for both linux/amd64 and linux/arm64. This requires a dedicated buildx builder with QEMU emulation.

One-time setup:

# Register QEMU handlers for cross-platform emulation
docker run --privileged --rm tonistiigi/binfmt --install all

The build script creates and bootstraps a dedicated multiarch buildx builder on first use, and invokes it explicitly via --builder multiarch. It does not mark that builder as your global default, so docker compose and other docker build invocations continue to use the fast native default builder. If you previously ran docker buildx create ... --use and are seeing long exporting to oci image format / sending tarball steps during compose builds, switch back with docker buildx use default.

Registry login:

docker login registry.gitlab.com

Build and push all images:

./packaging/scripts/maintenance/build_registry_images.sh --all --push

Writing Dockerfiles for injection images

File contents

As mentioned above, these Dockerfiles need to be placed within packaging/docker/injection_images/.

The script needs to define environment variable REGISTRY_TAG that will be used to infer the tag version when pushing the image. For example, if you want your image to be tagged with version 1.3.37a, you would need to place this line in your file:

ENV REGISTRY_TAG=1.3.37a

Optimizing image size

When writing an image to compile a program, the resulting image is usually going to be extremely heavy, as it would contain minimal OS layer, compilers, additionally downloaded dependencies, etc. As these images exist only to hold data to be injected into other images, you can strip the end result image to contain only the data they actually need to provide (e.g. produced libraries and executables). Even if not technically mandatory, you should not skip this step as the gains can be huge (e.g. for ffmpeg, this reduced the resulting image size from ~750MB to ~75MB).

For the sake of the example, let's assume:

  • Your build requires you to start from debian image
  • Your build's output is contained to two dirs, /usr/local/and/opt/helloworld/`

You can start a new image from a blank slate and just add those files to it:

# Name the first build step for easier access
FROM debian AS builder
ENV ENV REGISTRY_TAG=1.3.37a
# Run your build here that produces those files

# Now let's create a new build step, that will be the actual end resulting image
# that gets pushed to the registry
FROM scratch
COPY --from=builder /usr/local/ /usr/local/
COPY --from=builder /opt/helloworld/ /opt/helloworld/

And voilà~, you now have a lightweight image!