Late binding is a very precious idea.
Say, you have a docker image someone decided to use for the development. One created a huge bundle with all tools, bells, and whistles.
As time passes and you try to use it and then you miserably fail. Dependencies don’t met requirements anymore, most of the tools are outdated and should be bumped to newer version, initial SQL data has been changed and etc.
Configs are hidden and manipulated in docker image, composer.json is outside and you end up starring at this pile of crap.
Here is the recipe.
Use so called the unix way by splitting tools apart and combine again into some custom pipeline. Think about makefile, or shell script that assembles required steps together.
Divide things into composable steps and make binding of everything together as late as possible.
Say in the example above you should split single docker image into a bunch of smaller ones, preferrably official, i.e. supported by someone else. The benefits follow: no need to rebuild the whole thing every time, no need to have huge depencencies in one place: python, rust, dev libs, you name it.
This is quite common principle you actually can and probably should apply everywhere.
Example:
Java and modules classpath. Spring framework and dependency injection. Using old plain Makefile allows you to explicitly specify depencencies.