Building the images¶
Most people should just pull the published images. Build them yourself when you want to change tool versions, add or remove tools, or produce images for your own registry.
-
Pull if…
- the standard toolset is enough
- you want multi-arch images that are rebuilt automatically
- you don't want a long compile on every machine
-
Build if…
- you need different tool or Python versions
- you want to add internal tools or remove some
- you publish to your own registry
How the Dockerfile is organised¶
One Dockerfile with a shared base stage and three targets. Each target is a single stage on top of base; there's no separate builder stage.
flowchart TB
R["rockylinux/rockylinux:10"] --> S1["base: RUN 1<br/>dnf packages, gh, DB clients, Trivy,<br/>Python compiled from source, pip tools, Oh My Zsh"]
S1 --> S2["base: RUN 2<br/>kubectl, Terraform (tfswitch), Terragrunt,<br/>TFLint, Packer, Helm, ghorg, k9s, Task"]
S2 --> S3["base: RUN 3<br/>Node.js LTS, Claude Code, Codex,<br/>Copilot CLI, Antigravity CLI"]
S3 --> A["all-devops<br/>+ AWS + gcloud"]
S3 --> W["aws-devops<br/>+ AWS"]
S3 --> G["gcp-devops<br/>+ gcloud"]
classDef neutral fill:#334155,stroke:#1e293b,color:#fff
classDef base fill:#0891b2,stroke:#0e7490,color:#fff
classDef all fill:#059669,stroke:#047857,color:#fff
classDef aws fill:#ea7a0c,stroke:#c2410c,color:#fff
classDef gcp fill:#2563eb,stroke:#1d4ed8,color:#fff
class R neutral
class S1,S2,S3 base
class A all
class W aws
class G gcp
Because the three targets share base, building a second target after the first reuses the cached base layers and only runs the small cloud layer.
Prerequisites¶
- Docker with BuildKit, the default builder since Docker Engine 23 and in Docker Desktop.
docker buildx versionconfirms it. - Plenty of disk space: the finished image is about 5 GB unpacked, and the build cache adds more on top.
- A clone of the repository:
git clone https://github.com/jinalshah/devops-images && cd devops-images
Cold builds are slow
Python is compiled from source with --enable-optimizations, which dominates a cold build. Expect a cold build to take tens of minutes; later builds are much faster once the base layers are cached.
Build a target¶
To build every target in turn (the base is built once and reused):
for target in all-devops aws-devops gcp-devops; do
docker build --target "$target" -t "$target:local" .
done
Docker builds for your machine's architecture by default. On Apple Silicon or an ARM host you get a native linux/arm64 image. For both architectures, see Multi-platform images.
Build arguments¶
These are exactly the ARGs declared at the top of the Dockerfile. CI overrides the first eight from repository variables, which a daily workflow bumps to the latest releases.
| Arg | Default | Used for |
|---|---|---|
GCLOUD_VERSION |
501.0.0 |
Google Cloud SDK tarball (all-devops, gcp-devops) |
PACKER_VERSION |
1.11.2 |
Packer |
TERRAGRUNT_VERSION |
0.68.14 |
Terragrunt |
TFLINT_VERSION |
0.50.3 |
TFLint |
GHORG_VERSION |
1.9.10 |
ghorg |
K9S_VERSION |
0.32.7 |
k9s |
PYTHON_VERSION |
3.14.7 |
Full Python version compiled from source |
PYTHON_VERSION_TO_USE |
python3.14 |
Interpreter registered as the default python3; must match PYTHON_VERSION |
MONGODB_VERSION |
8.0 |
MongoDB repository series for mongosh |
MONGODB_REPO_PATH |
/etc/yum.repos.d/mongodb-org-${MONGODB_VERSION}.repo |
Where the MongoDB repo file is written |
MYSQL_RELEASE_RPM_URL |
https://repo.mysql.com/mysql84-community-release-el10-3.noarch.rpm |
MySQL 8.4 community repository for the mysql client |
MYSQL_GPG_KEY_URL |
https://repo.mysql.com/RPM-GPG-KEY-mysql-2025 |
Current MySQL signing key |
Terraform, kubectl, Helm, Task, Node.js and the AI CLIs have no build arg; they always install the latest release at build time.
You need both the full version (for the source download) and the matching binary name. PYTHON_VERSION=3.13 on its own breaks the build.
Validate the result¶
#!/usr/bin/env bash
set -euo pipefail
IMAGE="$1"
docker run --rm "$IMAGE" bash -c '
set -e
terraform version
terragrunt --version
kubectl version --client
helm version --short
ansible --version | head -1
trivy --version | head -1
python3 --version
node --version
command -v claude codex copilot agy
'
# Cloud CLIs depend on the target
docker run --rm "$IMAGE" aws --version 2>/dev/null && echo "AWS CLI: present"
docker run --rm "$IMAGE" gcloud --version 2>/dev/null | head -1 || true
The repository also has test scripts for the network tools. Each takes the image as its first argument:
./test_network_tools.sh all-devops:local
./test_dns_tools.sh all-devops:local
./test_ncat_tool.sh all-devops:local
Speed up rebuilds¶
Build targets one after another on the same builder. BuildKit reuses the cached base layers, so only the cloud layer runs for the second and third targets.
With a docker-container buildx builder you can export the cache and reuse it later, or on another machine:
The published images can't seed your cache
The published images are built with the GitHub Actions cache backend and carry no inline cache metadata, so --cache-from ghcr.io/jinalshah/devops/images/all-devops:latest won't speed up your build.
To shrink an image, see the Optimisation guide.
Troubleshooting¶
No space left on device
Free up space with docker builder prune (build cache) and docker image prune -a. On Docker Desktop, raise the disk limit under Settings → Resources.
The Python step fails
Check that PYTHON_VERSION is a full release that exists on python.org (for example 3.13.7, not 3.13), and that PYTHON_VERSION_TO_USE names the same minor version (python3.13).
A download returns 404
A pinned version probably doesn't exist, or doesn't publish a build for your architecture. Check the release page for the tool, then pass a different --build-arg, for example --build-arg GCLOUD_VERSION=<a version from the SDK release notes>.
Repository mirrors are slow or unreachable
Rocky Linux 10's repo definitions are /etc/yum.repos.d/rocky*.repo. To use an internal mirror in a fork of the Dockerfile:
Building amd64 on Apple Silicon is very slow
Cross-architecture builds run under emulation. Build linux/arm64 locally, or build each architecture on a native machine as CI does. See Multi-platform images.
Next steps¶
-
Where the size comes from and how to trim it.
-
Extend the published images with your own tools.
-
Build and publish amd64 + arm64.
-
Per-image notes