Size and build optimisation¶
How big the images really are, where that size comes from, and what actually works if you need something smaller or faster to build.
Real sizes¶
Measured from GHCR (latest). arm64 downloads are about 0.05 to 0.1 GB smaller.
| Image | Compressed download | Unpacked on disk (amd64) |
|---|---|---|
| all-devops | ~1.6 GB | ~5.0 GB |
| aws-devops | ~1.55 GB | ~4.6 GB |
| gcp-devops | ~1.5 GB | ~4.6 GB |
The shared base is most of the size: Rocky Linux with the build toolchain (gcc, make, the -devel libraries), Python compiled from source with its pip packages, Node.js with four AI CLIs, and a dozen standalone binaries. The cloud layers on top are comparatively small, so switching from all-devops to a single-cloud image saves only a few hundred MB.
flowchart LR
B["Shared base<br/>most of the size"] --> A["all-devops<br/>~5.0 GB"]
B --> W["aws-devops<br/>~4.6 GB"]
B --> G["gcp-devops<br/>~4.6 GB"]
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 B base
class A all
class W aws
class G gcp
What works for a smaller image¶
Deleting in a child image doesn't shrink it
FROM all-devops followed by RUN dnf remove … or rm -rf … hides the files but keeps them in the lower layers, so the image is just as big (or bigger). To really shrink, either change the Dockerfile or copy what you need into a fresh base.
The most effective option. Delete the install steps you don't need from the base stage, then build your target:
- no AI agents: drop the third
RUN(Node.js, Claude Code, Codex, Copilot CLI, Antigravity CLI) - no databases: drop the MongoDB, PostgreSQL and MySQL client steps
- a single cloud: build
aws-devopsorgcp-devopsinstead ofall-devops
Many of the tools are static binaries in /usr/local/bin, so a multi-stage build can copy just those onto a clean Rocky Linux image:
FROM ghcr.io/jinalshah/devops/images/all-devops:latest AS tools
FROM rockylinux/rockylinux:10
COPY --from=tools /usr/local/bin/kubectl /usr/local/bin/
COPY --from=tools /usr/local/bin/terraform /usr/local/bin/
COPY --from=tools /usr/local/bin/helm /usr/local/bin/
COPY --from=tools /usr/local/bin/tflint /usr/local/bin/
RUN dnf install -y git jq && dnf clean all && rm -rf /var/cache/dnf
WORKDIR /srv
CMD ["/bin/bash"]
This only works for self-contained binaries. Python tools (Ansible, the AWS CLI's dependencies, gcloud) and Node.js tools need their runtimes too, so install those normally.
The easiest option, but the smallest saving: aws-devops and gcp-devops are about 0.4 GB smaller unpacked than all-devops.
Dockerfile habits that keep layers lean¶
-
Clean up in the same
RUN
Files deleted in a later layer still ship. The project Dockerfile removes
/tmp/*and pip caches at the end of eachRUN(and__pycache__in most of them). -
Order by change frequency
Rarely changing steps first and fast-moving ones last, so a version bump only rebuilds the tail. In this Dockerfile the AI CLIs, which change most often, sit in the last base
RUN. -
Use
.dockerignore
Keep
.git,site/,.terraform/and state files out of the build context. That speeds up builds, though it doesn't change the image size. -
Cache mounts for faster rebuilds
BuildKit cache mounts keep package downloads between builds without storing them in the image:
--squash isn't an option
docker build --squash is a legacy-builder feature and isn't supported with BuildKit, which is now the default builder. Since the Dockerfile already cleans up inside each RUN, flattening the layers would save little anyway.
Faster pulls in CI¶
- Pin one tag per pipeline. Using the same
1.0.<sha>(or digest) across jobs lets self-hosted runners reuse layers they've already pulled.latestmoves with every rebuild. - Use GHCR. Docker Hub rate-limits anonymous pulls.
- Hosted runners start empty. GitHub-hosted runners don't keep container layers between jobs, so each job downloads the image again (about 1.5 to 1.6 GB). If that matters, use self-hosted runners or a single job with several steps.
Measure it yourself¶
# Size of local images
docker image ls --format 'table {{.Repository}}:{{.Tag}}\t{{.Size}}' | grep devops
# Layer-by-layer breakdown, biggest first
docker history --no-trunc --format '{{.Size}}\t{{.CreatedBy}}' \
ghcr.io/jinalshah/devops/images/all-devops:latest | sort -h -r | head
# Platforms and digests in the published manifest list (no sizes)
docker buildx imagetools inspect ghcr.io/jinalshah/devops/images/all-devops:latest
To analyse the image interactively, use dive from your host:
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest ghcr.io/jinalshah/devops/images/all-devops:latest
Checklist¶
- Use a single-cloud image if you only need one cloud
- To really shrink, trim the Dockerfile or copy binaries into a fresh base; don't delete in a child image
- Clean package caches (
dnf clean all,rm -rf /var/cache/dnf,pip --no-cache-dir) in the sameRUN - Order steps by how often they change
- Add a
.dockerignore - Use BuildKit cache mounts for faster rebuilds
- Pin one tag or digest per pipeline