Docker is the de facto solution for packaging most server-side applications these days. The technical merits of Docker are nifty – cgroups and other mechanisms are certainly useful – but there’s one particular aspect of running an application in Docker that has been unequivocally beneficial for the industry: concretely defined inputs and outputs.
I was reading the Gitea installation docs the other day when I realized why I gravitate immediately to the “Installation with Docker” section even when Docker may not be my deployment strategy. I do so because a file like docker-compose.yml
is a manifest that defines in certain terms how a program interacts with its environment in a concise, standard way.
When I run an application in an environment like my homelab, there’s a constant list of things that I, as an operator, need to know:
- Where does the application persist data? For a container to hang on to anything it writes to disk, that needs to be expressed as a volume.
- What network services does it provide? Port forwarding rules list out each listener for me to handle either at my reverse proxy or other network layers.
- What environment variables (or, commonly, secrets) does this application expect? Most containers don’t inherit everything from the environment, so this list of variable names provides a convenient list of what the application needs.
Part of why each of those bullets is useful is that, without those various settings, a container is – almost by definition – an inert unit of compute and nothing more. It can only rely on persistent storage or inbound traffic if you explicitly configure the container to do so. Is that Gitea container leaving other files scattered around or leaving a port listener undocumented? Probably not, because otherwise the userbase would find out about broken or buggy containers pretty quickly. The container image can’t receive traffic on a port that the developers have failed to document as part of a container configuration.
Frankly, this is why my gut feeling is that the kubernetes “revolution” that is eating DevOps feels marginally less significant to me than Docker itself. Sure, k8s is a nice abstraction layer over your hypervisors or container runtime, but the real meat – the juicy, high-fructose center – of what Docker brought to us was the application manifest. Do you feel a really gut-wrenching pull to docker run
rather than invoking an ./app
statically-compiled binary based primarily on the runtime? I’ll probably end up putting the ./app
in a cgroup’d systemd Unit
anyway, but I absolutely want the instructions to run it to be consistent.