Fat OCI images are a cultural problem
discourse.nixos.org
discourse.nixos.org
Ardour (my project) is (also) distributed in this way. You get a directory tree that contains the executable, all required dynamically-linked libraries and a bunch of non-code files. The actual program itself is a tiny shell-script that sets LD_LIBRARY_PATH to point into the app directory tree. The result runs on any Linux distro that can provide libstdc++ and Xlib.
Except, ironically, Nixos (because they screw with LD_LIBRARY_PATH use).
Another way of doing this that has been around on Unix for decades is to use the linker's rpath argument, but on many Unix systems for a long time, the runtime linker could not handle relative rpaths, and so this mechanism fell into relative disuse.
And of course, macOS has been doing something very similar with its "app" packaging, which relies on launchd to set DYLIB_LIBRARY_PATH (their equivalent to LD_LIBRARY_PATH) to include the app tree itself. The result is largely independent of the version of macOS the install occurs on, although that is dependent on some additional behind the scenes magic with the macOS run time linker.
There is a variable called DYLD_LIBRARY_PATH, but it mostly exists for development/testing purposes. The way it works is that before the dynamic linker resolves a dependency path from a binary, it first searches DYLD_LIBRARY_PATH for a file matching the basename of the path, ignoring the rest of the path. Any such file will take precedence over the original path.
I have seen some apps that rely on a wrapper shell script that sets DYLD_LIBRARY_PATH. I'm not sure if they just have the wrong dependency paths, or if they're dealing with other special cases like calls to dlopen with a bare filename.
That said, I still have this vague memory that launchd does in fact reset some linker-related vars before exec-ing the app binary. Could be wrong about that too.
Multi stage images into a scratch final stage fix this, but you have to care about the problem first, and many people don't. Software with many unpredictable dependencies also makes it harder (it's a lot harder to make a really thin image if your program is python, for instance, than if it's compiled).
Edit: actually reading TFA, it's a really good description of the issue. It focuses on nix, but the problem and difficulties are much more general.
You can write a Dockerfile that inherits from "scratch," which projects an almost entirely empty overlay volume into the container's root volume. (I think it writes a .dockerenv file still.) From there, you can add a minimal rootfs that provides exactly just the system libraries your app needs to run.
This is the crux of the problem and a big part of the argument in this article: many devs don't know which dependencies they need from the system, so they create base images from the biggest Ubuntu base images they can find and then apt-get their way into a working system.
This works, but now you have a 3Gi image that has a ton of stuff outside of your apps's core supply chain that introduce risk.
It depends if the process you are going to run is compiled or interpreted. In the latter case you can use multi stage builds, with the former stages building what you need and the final one putting it all together. But it simply makes no sense for cases like Python where it would be terribly inefficient to repeat the same work everybody else is doing hence the FROM python:3.11-slim-bookworm usage.
I find it pretty easy to write a program to run in a very minimal docker image, but compiling existing software for the same can be quite hard, and legacy libraries and application code make it even harder. For somebody who is just learning to program, they could easily never really learn the right techniques, because the wrong ones really do just work fine for them. Like electron, these things are very wasteful, but they do work pretty reliably.
To be clear, Docker doesn't exist because it's impossible to properly configure a contained environment for applications already; Docker has to be implemented somehow, after all, and you could just do what it does and have yourself a container. Docker exists because people want a simple way to launch a container that is already fully contained - filesystem, network, CPUs/GPUs, etc - and doing this configuration manually is Work™.
The issue is that if you're an engineer coming up with the toolchains for your packaging and you say "I opted to write scripts for packaging our stuff as tarballs and running the app in chroots", your lead will ask "why are you wasting time on that and not using Docker?" And it will probably be a difficult conversation.
* Yeah, nix defaults to giving you a "distroless" image. This is one of the nice things about it:)
* The space savings turn out to not be that significant compared to just using multi-stage Dockerfile builds - if your image is >100MB of application and associated necessary data files, paying another 3-30MB (alpine is 3, debian slim is 30) for a base image just doesn't matter that much. Compared to Alpine, nix could even give you a bigger image, though I don't recall seeing it happen.
* (Similar to previous point) As the Discourse thread notes, nixpkgs isn't really geared towards producing small outputs; it's more like Debian than Alpine.
* Not having anything but the application sounds good, but it has the very significant disadvantage that the result is (by design) missing coreutils and a shell, which means if your container breaks, you can't `kubectl exec` into it to troubleshoot. (And we never found a way to fix this; Ephemeral Containers looked like they were going to fix it but they don't share mounts with the target container and that really undermined the whole thing for us)
If I want a smaller package than what the official package offers, I can do just that. Just create a new package on the fly by extending existing package definitions. Disabling features, adding compile flags, dropping dependencies, patching the source of existing packages is very easy to do in Nix. With Debian or Alpine, I have to rebuild software from scratch if some package doesn't fit my needs. With Nix, it's only a few lines of code. For example:
nginx.override { withPerl = false; }
Nix also lets me choose what I want or don't want to include in my image. A Nix built image is just another Nix "package," so it has enough information to include only
the things I need. Docker can't do that because it has no insight whatsoever about files included in an image. So I'm left guessing which files are needed and which aren't and have to tediously copy those in a completely new image.Finally, Nix lets me choose how I want to run my applications, including if I even want to run them in containers or not. Way too often, Docker-native applications becomes so tied to the image that it can't be run any other way. If I want to change some aspect of the environment that an application runs in, I have to fork the whole thing. Worse, when it comes to building Docker images, there are no reproducibility guarantees. With Dockerfiles essentially being no more than a shell script to make tarballs, they're very much a "works in my environment" way of building software.
We might need a “X Factors” list to popularize what checkboxes properly “containerized” products should tick, to help move the needle.
This makes it convenient to build&package some Python libraries that require a build step. The process looks like this:
1. Use `debian11` as build env, install `libpython3-dev` 2. Pip install the required packages into a prefix 3. Use `python3-debian11` as the final base image, copy installed file from build env.
Obviously there are other ways to achieve the same goal, but distroless being Debian based makes it easy to rule out any compatibility issues, if you also use Debian as your build env.
For a concrete example here's how I package beancount + fava: https://github.com/yegle/fava-docker
So the trap for me is: mixing up dependency management with Docker, which is just a wrapper for your runtime in 2023™ .
There are already tried and proven tools out there (Deb, rpm) which solve the dependency management problem. Better publish your artifacts as a package backed by a dependency manager and then use the managers tools to make an OCI image as a target.
Using docker images for dependency management does not work. Your FROM will explode with all the combinations you will have to keep in the registry (I.e. „I need nodejs with a C++ image library“)
Docker multistage builds on top seem to confuse people, and they mistake Docker as a CI system to copy artifacts between their multistage builds and maybe start implementing their own caching behavior.
Therefore I suggest to clearly separate the reaonbilities of Docker from a CI runner and from a dependency manager. Otherwise patch management or dependency resolution does not scale for multiple teams.
Back when they called containers "jails", I did both distroless/single-binary and full-install containers. I like the former a lot. They really make it difficult for attackers to live off the land. On the flip side, they're a huge pain in the ass to maintain, particularly if your programming language of choice doesn't make static linking easy.
Nowadays, where system admins aren't usually programmers, doing a full install of an operating system's userland into a chroot---sorry, I mean "layer"---is pretty damn handy. If you're careful about what packages you install, you can get most of the benefits of a distroless container (e.g., making LoL hard) without having to know too much about how the runtime environment works.
It's all a balance, how much time and money you waste engineering the perfect `FROM scratch` container versus getting actual work done using a "fat" image that's good enough. And I think we all know, deep down, that fat OCI images make the Docker world go 'round.
Except it never works this way. Most of the time you get told the image won't be used so to do it quickly and forget optimizing. Months later you find other teams "borrow" it and suddenly the whole company is using it.
I’ve toyed with buildpacks and when the builder images don’t suck, and the project isn’t doing weird shit with modules, it seems to work with little effort.
The point is it was a weekend experiment. It's by no mean ideal but it's absolutely functional and viable. With a starting investment of a half engineer-weeks it's affordable to pretty much any org.
We are in the nodejs world now where speed is all that matters. No one wants to understand what is going on under the hood. And no one takes the time to learn something new when your job only cares about the next feature. At the end of the day all that matters is money.
Is that actually true or is the company just so focused on short term metrics that it actively discourages this behavior?
In a way it's not their fault. Management never allocates time for such things. It isn't in OKRs / KPIs. No 1 cares because they're not made to care. I've seen developers that tried and get "punished" for it so stop caring.
I've seen people spend their own time to make everyone's life happy e.g. CI/CD build times. End result is management complaining why they don't work on the "priority" i.e. requested features.
> No one wants to understand what is going on under the hood.
That's always been the case because "business" took over. This notion of "tech debt" is 1 of the worst offenders in that to non-technical people debt is something that can be left forever (as long as you pay interest) i.e. they'll never care.
If not, than really why should anyone care about image size? If it does not have a meaningful impact on company results, it’s really a form of self-gratification isn’t it?
Docker images can still have a drift in layer delta if one would not pin system package versioning of the applications being installed, even if the instructions to generate those layers remain the same.
The output being an image does not make all iterations of those instructions the same thing.
NIXOS = *NIX (UNIX, Linux) Operating Systems
If you want to say what you think is important about an article, that's fine, but do it by adding a comment to the thread. Then your view will be on a level playing field with everyone else's: https://hn.algolia.com/?dateRange=all&page=0&prefix=false&so...
(Submitted title was "Fat OCI images are a cultural problem")
[0] https://discourse.nixos.org/t/oci-images-is-there-something-...
I've changed the URL and title now. Sorry for the misunderstanding!