Docker as a mirror for operating system designs
Lately I’ve been learning a lot about deployments of different (web-) applications. It feels like everyone and their cat are using Docker images and Kubernetes to deploy a system.
I’ve learned to build software the old UNIX way.
First, you build everything by hand. For a C project, this would include using your favorite C compiler to turn source code to object files, which then are linked together as a single executable.
Because this is all done in the shell, it’s stupidly simple to automate this task. You just have to write down all commands you would normally type in the shell and put them in a file. By giving it an appropriate shebang and setting the executable bit on the file, you can build the complete app by invoking a single script.
Now imagine the program has a lot of dependencies, and those dependencies don’t change very often. This also means that their compiled counterparts, their object files, won’t change often.
With the simple technique above, our build script would compile the dependencies every time. Doesn’t sound very efficient, does it?
Luckily, you can just write your build script as a Makefile instead. With a Makefile, you can capture these dependencies in your build script. Make detects if a target file exists. If it does, and the dependencies didn’t change since it was created, Make just skips the associated build rule, thus reducing overhead.
Now you can just package the final build in a tar archive or similar. You could even make it a package that is managed by your operating systems package manager. Sounds good, right?
So what exactly does Docker do, that you can’t do the UNIX way?
In my opinion, there are three main components to Docker: The Dockerfile, DockerHub, and actually running a container You can equate these to the Makefile, the package manager, and actually running an executable, respectively.
Dockers build process via the Dockerfile simply has overall worse caching behaviour compared to the usual build systems, and the only benefit to version control using Docker is that you easily can run multiple versions of software at the same time. This seems nice at first, but also has the drawback of easily running outdated versions with possibly severe vulnerabilities.
Whatever makes Docker so appealing has to do with the container runtime then. Looking at how Docker manages permissions, it’s quite easy to see why:
native runtime | Docker runtime | |
---|---|---|
local file system namespace | ❌ | ✔️ |
easily restrictable network | ❌ | ✔️ |
easy resource limiting | ❌ | ✔️ |
It turns out that managing permissions in a least privilege fashion is useful. I can see why developers like to use Docker for webservers and other deployments because it makes managing permissions easy. Sadly, managing fine grained permissions is not a first class citizen on most platforms. This makes it difficult to use permissions were they matter.
Normal programs that a user installs should be using permissions to. Flatpak tries to limit the capabilities of programs, but falls short for stuff like file system access. Android and iOS use permissions more generally, but have their own issues. 1
Let’s look a image editing software like Gimp, Krita, or Photoshop.
Do you have to access the internet to edit some pictures? Strictly speaking, no. Does such an app require file system access? How can you open or save your images without it, you might ask. That’s pretty simple. When you try to open a file in basically any program, what exactly happens? A file explorer shows up.
It’s easy to see how only the explorer would need full file system access. Once you as the user select some file to open, it transfers the permission to edit that single file to your image editing software. The same thing applies to the ‘save as..’ dialog.
This general pattern should be how file permissions are handled. Sadly, all permission systems that I know of fail at implementing this least privilege scheme.
Once you think about permissions in this light, you can imagine how stuff like automatic software updates might work. Your image editing software would call into some DNS service with context like “Give me whatever is at URL updates.imagesoftware.example, please”.
The user then sees a pop-up saying “$imagesoftware wants to access $URL for updates”, with allow and deny buttons. If you think these pop-ups might get annoying the 3rd time you have to click it way, just imagine you could click a ‘remember permission’ checkbox.
Each app could have a requirements file for general settings. Imagine something like ‘requires 2GB of RAM’ or ‘give me a directory with at least 2MB of backing memory for settings’. A nice side effect of this system is that you could configure a permission provider that manages where in your file system these settings are stored. This would mean no more XDG_CONFIG_HOME ambiguities anymore. If your program wants to have permanent memory, it has to request a permission, which has to go through the user or a permission manager at some point.
On the systems side of things you could provide default permissions for disc and RAM usage, so you don’t have to click through every little implementation detail of the app. And a package manager wouldn’t pollute a global namespace by scattering package files all around. Instead, each package could specify it’s dependencies and only have those mounted into their namespace at runtime.
It feels like Docker is rubbing salt in the wound of first class permissions. With better operating system support for permissions, Docker wouldn’t really be needed anymore, and the tech world would be a better, although stricter, place.
- Yes, your location can be tracked with Bluetooth, but should that mean you have to give the ‘Access to location’ permission as well? That seems to make tracking only easier for malicious apps, for example.↩