Hey everyone! 👋 I've been on a mission to slash our CI/CD bill, and I think I've found one of the biggest levers: **moving Docker layer builds out of the CI pipeline entirely.**
Our team runs a lot of feature branch builds and deploys multiple microservices. I noticed we were rebuilding the same base layers, installing the same dependencies, over and over on every single push. Our CI minutes were burning up just on `apt-get update` and `npm install`.
Here's what I did and the impact:
* **The Old Way:** Every CI job started with a `docker build` that ran all steps, even for layers that rarely changed.
* **The New Way:** I set up a separate, scheduled job (using a simple cron GitHub Action) that rebuilds our core base images and dependency-heavy intermediate layers **once per day**. These are then pushed to our private container registry.
* **CI Pipeline Change:** Our main CI `docker build` now uses `--cache-from` to pull these pre-built layers. The build only executes for the layers unique to that specific service or change (usually just the COPY of the application code).
**The results after a full month?**
Our compute minute usage dropped by **~65%** for the same number of builds. The cost of the scheduled layer-build job is negligible. The biggest wins came from:
* Not reinstalling system packages every time.
* Not downloading npm/pip packages from the internet on each build.
* Drastically reduced build times, which also means faster feedback for devs.
The key is identifying the slow, static parts of your Dockerfile and outsourcing them. It feels obvious in hindsight, but seeing the invoice drop that much was a shock. It's not full self-hosting, but it's a smart hybrid approach.
Has anyone else tried similar optimizations? Curious if you've hit any cache-invalidation issues or have other tricks.
ā Amanda
Show me the accuracy numbers.
That's a really smart approach. We did something similar but we hit a snag when our scheduled layer build failed silently - the CI builds would then fall back to building everything from scratch, which actually burned *more* minutes than before until we caught it.
We ended up adding a quick health check that pings a status dashboard if the base image is more than 24 hours old. It made the system a bit more complex, but saved us from those surprise bills. Have you thought about failure scenarios for your cron job?
api first