Back in the 1990s, shared libraries were all the rage. Instead of having to ship a 20 MB
*.exe file to your customer in various floppy disks, you could cut some code out, put it in a set of
*.dll files, and reuse that code across all your products. Every vendor would then install lots of DLL files in your system, and they would be reused by other apps from the same vendor.
Why did they do that? Well, storage space was expensive back then. An internal 1 GB hard disk in 1995 costed me around a thousand Swiss Francs, and it filled fast with all those apps (well, I also had Windows 3.1 and OS/2 Warp installed simultaneously.) So reusing code in DLLs appeared, at least at first sight, as a valuable mechanism to reduce bloat for software vendors, and to avoid repeating themselves.
The truth is that those DLLs multiplied themselves like bacteria, and led to a concept that made headlines back in 1995: DLL Hell. The concept was so pervasive and problematic that it got its own Wikipedia entry.
Talk about being a celebrity.
Even Microsoft couldn’t cope with the DLL explosion; apps would crash at runtime because they would load DLLs with the same name, but with different versions of the same functions inside. It was really a mess.
The most egregious example of this mess was Visual Basic itself, and its suite of
VBRUN300.DLL libraries, of which it seemed there were gazillion versions in shareware collections everywhere. Which one to use? Well, you’re out of luck; try them one by one until your game
runs doesn’t crash anymore.
The best way to prevent DLL Hell was (still is) to link binaries with libraries at build time. But of course, if your storage is expensive, well, it’s not the best way to do things, because the final executable will be more bulky at the end. But, look ma! No DLL loading at runtime! Static binaries are the best.
README is your friend, and then
npm install or
pip install or
gem install until everything works.
We’re in 2022 now, almost 30 years later, and statically built binaries have won, and even for scripting languages! They are called containers now; Docker containers, pods, what have you, and they encapsulate not only the final executable and its libraries, but also any other piece of runtime code they might need. Like the whole .NET framework, or the PHP runtime, or a complete version of Ruby and Ruby on Rails including all the gems your app needs to get things done.
The downside is that with those languages your final container image could easily stretch into the gigabytes… not very convenient if you want to run many copies of those containers in the same load-balanced Kubernetes deployment. Oops.
This all means that compiled languages are making a nice comeback right now; Go, Rust, D, C++, .NET, Crystal, C, they can all be used to generate a small self-contained executable, and with it, a very small final container image.
Specify in your
Dockerfile a base image
FROM scratch, copy your binary, and your container image is now just 15 MB big. Push it, share it, pull it, and reuse it. And apparently .NET 7 (the latest version) includes a new AOT feature that makes really small native binaries. Finally. Oh, and you can even
dotnet publish directly as a container.
Talk about convenient. Make small container images, people.