← Back to list

EcoInfra 3/8: Build-time energy, the invisible kilowatts in your CI

CI is the invisible second runtime of your code, and almost nobody measures it.

Thierno Diallo in Just-Tech-IT · 2026-06-04 09:30 · 0 claps · 5.1 min read
#docker #build-time #ci-cd-pipeline #sustainability #oci
Open on Medium ↗
Wiki topics: MM · Multimodal & Generative Media ESG · ESG & Sustainability ☁️ · DevOps & Cloud

EcoInfra 3/8: Build-time energy, the invisible kilowatts in your CI

CI is the invisible second runtime of your code, and almost nobody measures it. A pull request, merged 50 times a day, rebuilt 8 times by branch protection, with a cold cache and a :latest tag base image, can burn more energy than the application it ships will ever consume in a week of production.

This article will spot some rules, and map those rules onto eco-design axis 2, build-time energy.

1. The big picture

Runner energy ≈ wall-time × runner TDP, per Cloud Carbon Footprint methodology. Same green build, 25× less electricity.

2. Why build-time energy is important

GitHub publishes billions of Actions-minutes per month.

The CNCF Sustainability TAG flagged CI runners in 2023 as the largest unmeasured energy line in the cloud-native stack (CNCF Sustainability Whitepaper). Three independent drivers compound:

  • **Cache misses: **30 to 70 % of CI wall-time is dependency download when caches are missing or invalidated (GitHub Docs — caching).
  • **Redundant runs: **pushing 4 commits in 10 minutes triggers 4 full pipelines unless you opt into concurrency control.
  • **Mutable tags: **every silent base-image republish (ubuntu:latest, node:lts, tj-actions/changed-files@v35) invalidates the local layer cache and forces a cold build (Sysdig 2024).

And the supply-chain angle reinforces the eco-design angle: the March 2025 tj-actions/changed-files attack (GitHub Security Lab post-mortem, StepSecurity report) injected a credential-stealing payload into thousands of pipelines for 24 hours. Every unpinned action is both a security risk and a cache-miss waiting to happen.

3. The levers

Lever A: Order Dockerfile instructions for cache hits

BuildKit invalidates every layer downstream of a changed input. Putting COPY . . before RUN pip install means each code edit re-downloads the full dependency tree. Move volatile inputs last.

Expected gain: cache hit-rate goes from ~40 % to ~85 % on cached PRs, build wall-time −30 to −70 % (Docker BuildKit cache).

Lever B: Pin the base image by digest / forbid :latest

Pinned digests mean the same input, the same cache, every build (OCI Image Spec). The official Docker best-practices guide is explicit about it.

Lever C: Cache your dependencies

Here some hints for the common builders, actions/cache, setup-node cache key, Maven's ~/.m2, Gradle's ~/.gradle/caches, pip's ~/.cache/pip, npm/pnpm's store. Caching is the cheapest CI optimisation on the planet:

GitHub’s own benchmark cites 30 to 60 % build-time reduction on hot-path repos (GitHub Docs).

Lever D: Skip redundant jobs

Add paths-filter so doc-only PRs don't rebuild the JVM, skip drafts unless ready-for-review. CI-minutes typically drop −20 to −50 % with zero functional cost.

Lever E: Pin the runner & prefer ARM

ubuntu-latest quietly migrates between LTS releases and invalidates every runner-local cache. GitHub announced ARM64 runners on Actions in June 2024 (GitHub blog); they are 37 % more energy-efficient than x86 on equivalent workloads (Cloudflare ARM, AnandTech's Graviton3 deep dive).

Lever F: Pin third-party actions to full SHA

Recommended by GitHub’s own hardening guide and by NIST SP 800–218 SSDF. Same lever covers eco and supply-chain.

4. The decision tree

Is the workflow rebuilt on every commit?
 ├── YES → add `concurrency.cancel-in-progress` (Lever D)
 │          then `paths-filter` so docs/markdown changes don't rebuild code
 └── Does it pull dependencies from the network?
     ├── YES → `actions/cache` or `setup-*` cache key (Lever C)
     └── Does it build a Docker image?
         ├── YES → fix layer order (Lever A) → pin base by digest (Lever B)
         └── Is the runner `ubuntu-latest`?
             ├── YES → pin to `ubuntu-22.04-arm` (Lever E)
             └── Are 3rd-party actions pinned to SHA? (Lever F)
                 ├── NO → pin them, today, before the next supply-chain incident
                 └── DONE

You don’t have to do all six on day one. Do Lever D first (it’s a 3-line YAML edit and reaps the most CO₂ per second of effort), then lever C, then lever E, then the Dockerfile-side levers when the next image refresh comes around.

5. What it’s worth, in numbers

| Lever                  | Typical reduction                       | Source                                                                                                                                                                                   |
|------------------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Layer order fix (A)    | Cache hit-rate +30 to +80 %             | [Docker BuildKit](https://docs.docker.com/build/cache/)                                                                                                                                  |
| Pin base by digest (B) | Cache miss rate −50 to −90 %            | [OCI Image Spec](https://github.com/opencontainers/image-spec), [Sysdig 2024](https://sysdig.com/2024-cloud-native-security-and-usage-report/)                                           |
| Dependency cache (C)   | Build wall-time −30 to −60 %            | [GitHub Docs](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)                                                                             |
| Cancel-in-progress (D) | CI minutes −20 to −50 %                 | [GSF Patterns](https://patterns.greensoftware.foundation/)                                                                                                                               |
| ARM runners (E)        | Runner energy −30 to −40 %              | [GitHub blog 2024](https://github.blog/2024-06-03-arm64-on-github-actions-powering-faster-more-efficient-build-systems/), [Cloudflare 2018](https://blog.cloudflare.com/arm-takes-wing/) |
| Pin actions to SHA (F) | Cache stable + 0 supply-chain re-builds | [GH-Hardening](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)                                                                                 |

Stack all six on a mid-sized monorepo, and you typically divide CI-minutes by 3 to 5. That is the largest single eco-design lever after region selection (see article 7).

6. Beyond static rules: orchestrate measurement

Tools like creedengo-infra flags the source-side patterns through CI workflows, and layer order / pinning. For the byte-level evidence, you need runtime observation. Here some useful companions:

a- **eco-ci**: Green Coding Berlin’s energy estimator for GitHub Actions runners, exposes joules per job in the workflow summary.

b- **Kepler**: kernel-level eBPF energy attribution; runs on the CI cluster, exports Prometheus metrics per pod.

c- **Cloud Carbon Footprint: the methodology behind every “**CI runner = X gCO₂” estimation in this article.

Wire them once, leave them running, and the dashboard becomes the regression test.

7. Want to learn more?

Next in the series EcoInfra 4/8: Runtime energy, what your pods do when nobody’s watching.


메타데이터
post_id
5aa04cfefbef
slug
ecoinfra-3-8-build-time-energy-the-invisible-kilowatts-in-your-ci-5aa04cfefbef
url
https://medium.com/just-tech-it-now/ecoinfra-3-8-build-time-energy-the-invisible-kilowatts-in-your-ci-5aa04cfefbef
canonical_url
https://medium.com/just-tech-it-now/ecoinfra-3-8-build-time-energy-the-invisible-kilowatts-in-your-ci-5aa04cfefbef
author_url
https://medium.com/@moustaphadaka
status
ok
fetched_at
2026-06-12 18:14:10