When I introduced the original distroless images to the world, I said "I am in pursuit of a better way of building containers." Much of the need for this ties back to one thing: RUN. This powerful and sticky Dockerfile directive in many ways enabled the ascension of Docker to the ubiquity it has today. However, this directive is the root of so many problems as well. The imperative nature of RUN requires activating the container and running things inside of it, which makes things like multi-tenancy (e.g. hosted build services) and multi-architecture (e.g. amd64 and arm64) builds hard. The fact that it lets users execute arbitrary commands also makes any claims of reproducibility pretty trivially refutable.
My journey in this space started in 2014 with Blaze at Google (now Open Source as Bazel), where I wrote the original rules_docker (which has now been deprecated and replaced by rules_oci) with the aim to provide every directive of Dockerfile except RUN. These were without a doubt the first generally reproducible docker build rules (likely by several years), and by the time I ultimately left Google there were hundreds of teams building with these internally.
Blaze BUILD files were a natural early vehicle for this work because of their declarative representation of what developers intend to build. In declarative models, users express their desired state vs. imperative models where they express the procedure to get there (a la RUN); it is a declaration of intention. While Bazel (open source Blaze) BUILD files did not exactly become a runaway sensation, it is not alone in its use of declarative expression, and there are two great examples which have gained massive mindshare: Kubernetes and Terraform (both have cameos below, but no spoilers). Terraform has come to define the category known as “Infrastructure as Code” and uses a declarative expression of the infrastructure a user wants deployed. It also inspired the title of my pursuit of declarative image builds: “Images as Code”.