Containerized Tooling

In day-to-day work we constantly use various tools: protoc, golangci-lint, allure, and many others.

To avoid situations where one developer’s tool version or configuration differs from another’s — or from what’s set up in CI — our tools are “baked” into containers.

The tooling containers are built in CI and are available both on local development and debugging machines and in CI pipelines. That gives us a unified configuration and end-to-end versioning for every tool across every environment.

We took a systematic approach to containerizing our tooling:

  • Set up a base repository template (boilerplate) for tools;
  • Carved out a dedicated project group called tooling;
  • Inside the tooling group, created a separate repository per tool.

All repositories share a single common pipeline for building and publishing to the internal image registry.

A project group might look like this:

tree -a -C -L 2 tooling
tooling
├── golangci-lint
│   ├── .env
│   ├── .git
│   ├── Dockerfile
│   ├── Makefile
│   ├── README.md
│   ├── entrypoint.sh
│   └── golangci.yaml
├── protoc
│   ├── .env
│   ├── .git
│   ├── Dockerfile
│   ├── Makefile
│   ├── README.md
...

The .env file is responsible for configuration; it usually sets the image name:

IMAGE_NAME=internal-registry.dmz/dx/tooling/cowsay

Every repository ships with a templated Makefile that may differ slightly from the others when extra steps are needed. The .env file is included in the very first line of the Makefile:

include .env

.DEFAULT_GOAL := build

.PHONY: build
build:
	docker build --tag ${IMAGE_NAME} .

The Dockerfile doesn’t really need a separate description; here’s the simplest possible example:

# syntax=docker/dockerfile:1

FROM ubuntu

RUN <<EOF
apt-get update
apt-get upgrade -y

apt-get install -y cowsay
EOF

WORKDIR /workdir

ENTRYPOINT ["/usr/games/cowsay"]

Two things are worth pointing out that may not be obvious:

  • The comment on the first line, # syntax=docker/dockerfile:1, enables HEREDOC support — used inside the RUN directive;
  • ENTRYPOINT is used instead of CMD, which lets you pass arguments to the command when running the container.

Thanks to this approach to containerizing tools, my team gets:

  • Transparent, easy “installation” and execution of tools locally;
  • Consistent tool versions across local environments and CI;
  • A common, unified configuration of the toolchain across all projects.

References