Your pipeline pulls half a gigabyte to run a test suite.
Every job in every pipeline starts by fetching a PHP image. On a generic image that means a full distro userland plus a compiler toolchain your tests never touch — on every branch, every push, every retry. You pay for that twice: in minutes waited and in minutes billed.
A generic PHP image
docker build to add your extensionsFour steps before the job does the thing you actually wanted.
A purpose-built image
The image is already composed. There is nothing left to build.
One extension changes, one layer moves.
Every extension sits in its own layer. When one gets a security update, that is the only layer your runner has to fetch — everything else is already in its cache. On a monolithic image a single change invalidates the lot.
Nothing to build means nothing to break.
No docker build step in your pipeline is also no Dockerfile to maintain,
no build cache to warm, and no build that fails for reasons unrelated to your code.
The image arrives finished.
Same image in CI and production.
The thing your tests ran against is the thing you deploy — byte for byte, because it is the same pullable image rather than one rebuilt per environment. Fewer surprises of the "green in CI, broken on the server" kind.
Measure it yourself
Compare against what your pipeline pulls today.
Build an image from your composer.lock on the start page, then put its size next
to the one your CI currently fetches. No account needed for that comparison.
How much you save depends entirely on your stack, your runner's cache and how often your pipeline runs — so we are not going to invent a percentage. The structural point holds either way: a smaller image transfers faster, and a prebuilt one removes a build step from every single job.