docker build
's way of working relies heavily on caches and layers to speed things up.
If no inputs change, it reuses the results of a previous run.
Usually this is a good thing,
unless you've decided you want to run CI tooling as part of your docker build process,
and of course those should never be cached.
Today I saw a dockerfile that did:
FROM index.docker.io/alpine:3.15.2 AS image
RUN apk add bash
FROM image AS scan
COPY --from=index.docker.io/aquasec/trivy:latest /usr/local/bin/trivy /usr/local/bin/trivy
ARG CACHEBUST=1
RUN trivy filesystem --ignore-unfixed --exit-code 1 /
and you built it with something like docker build --build-arg CACHEBUST=$(date) .
.
Interesting way to run tools that shouldn't be cached, but now you have extra cruft in your final images, and no fine grained control over the tooling.
The more "correct" way would be something like:
FROM index.docker.io/alpine:3.15.2 AS main
RUN apk add bash
FROM scratch AS scan
COPY --from=output-image / /
COPY --from=index.docker.io/aquasec/trivy:latest /usr/local/bin/trivy /usr/local/bin/trivy
RUN trivy filesystem --ignore-unfixed --exit-code 1 /
and built with:
docker build --target main -t output-image .
docker build --target scan --no-cache .
This keeps your output image clean from any CI only tools, while ensuring they always run fresh. With this pattern, multiple tools can be run in parallel too (using multiple docker build commands with different targets).