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:
1FROM index.docker.io/alpine:3.15.2 AS image
2RUN apk add bash
3
4FROM image AS scan
5COPY --from=index.docker.io/aquasec/trivy:latest /usr/local/bin/trivy /usr/local/bin/trivy
6ARG CACHEBUST=1
7RUN 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:
1FROM index.docker.io/alpine:3.15.2 AS main
2RUN apk add bash
3
4FROM scratch AS scan
5COPY --from=output-image / /
6COPY --from=index.docker.io/aquasec/trivy:latest /usr/local/bin/trivy /usr/local/bin/trivy
7RUN trivy filesystem --ignore-unfixed --exit-code 1 /
and built with:
1docker build --target main -t output-image .
2docker 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).