1. A Docker image is the intersection of literally 50 years of technology, starting from Unix in the 1970s, none of which are perfect. Decisions from back then still have an impact, e.g. you need to get signal handling right if you want shutdown to work (https://hynek.me/articles/docker-signals/).
2. Getting it right is all about _details_. Lots and lots of details, all of which need to be correct. Signal handling, where logs go, security updates, on and on.
3. Every organization does things slightly differently.
So there are multiple approaches:
== Build an abstracted tool ==
A tool will support certain conventions and ways of doing things... but as soon as you want to diverge too much you'll start having issues, because it only supports its particular way of doing things.
Pros: Easy for users.
Cons: Only so long as they don't want something the tool can't handle.
The problem is that it's quite difficult to build a tool that works for _all_ organizations. If you've built a custom buildpack for your company, you've built a custom tool, which a pretty good solution, but still every organization has to build their own tool.
== Build a configuration language ==
In order to support all the edge cases, you build a configuration language... and pretty soon it's super complicated because there are so many different things people want to do. In fact, you end up with something with complexity of Dockerfile. Probably can do better than "it's a shell script", but it's not going to be simple, and you're back to "users have to figure out all the details on their own".
Pros: Flexible.
Cons: You still need to get details right.
== Template ==
Here you do something tool-like: it knows about conventions, is customized for specific common use cases. But, you copy the code into you application repository, so you can then customize anything that doesn't fit.
I have built a template for production Docker packaging of Python applications (https://pythonspeed.com/products/pythoncontainer/) and this is the approach I went with.
Pros: Works most of the time out of the box, can be customized when it doesn't.
Cons: Hard to update if you're ending up doing too many per-application customizations.
---
Obviously this is a simplification, and these options tend to merge at the edges. But fundamentally it's a hard design space and there is no magic bullet, just tradeoffs. Longer version, presented slightly differently: https://pythonspeed.com/articles/developing-tools-for-ops/