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
toolinggroup, 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, enablesHEREDOCsupport — used inside theRUNdirective; ENTRYPOINTis used instead ofCMD, 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.