Ask HN: Docker vs simple DLLs?

45 points by jdthedisciple ↗ HN
I'm already feeling bad for creating yet another docker-related post but I figured f** it, I just don't get the hang of it so somebody pls conclusively, once-and-for all, explain the need for and blessing of docker in a simple, fool-proof fashion:

What I did understand so far is "Docker is useful if you have multiple applications with different versions of the same dependencies running on the same machine, as well as for deploying onto any machine".

MY QUESTION: (Why) don't dynamically linked libraries (.dll) already solve this problem?

I come from the .NET world and there I can run any number of applications all with their respective dependencies, e.g. NuGet packages, as .dll files in their own isolated directories - doesn't this already solve the problem of isolation and avoid dependency-conflicts? This is also easy to ship to any machine as I just compile the whole thing for the respective architecture and then copy and paste it over, set it up as a service/daemon, whatever.

To me a standalone application is simply a directory with an executable and all dependency-dlls in the same directory / some subdirectory. This is already a software-wise isolated application (obv not on the hardware side but lets leave that out for a second), of which I can run multiple on the same system. Why do I need docker on top of that?

I am probably misunderstanding something fundamental here so for the 21375th time, what is an idiot-proof explanation for the need of docker in light of dlls?

64 comments

[ 3.2 ms ] story [ 137 ms ] thread
Circa 2005 or so I had perfected methods for running large numbers of web applications on a server, which mostly involved being disciplined about where you keep files, configuration, etc.

Most people aren’t disciplined though.

(comment deleted)
If you have a standalone application, then great, maybe docker doesn't add a lot

Maybe your application requires xyz lib to be installed via apt or something similar

Now docker is useful, as it can maintain that environment easily

Now imagine you work in a company with many different teams, and just want a similar way of deploying all services without requiring knowledge of how the application dependency tree looks, just use docker everywhere.

That is why imo you see docker used in places it doesn't really offer a benefit, because a standardized way of deploying is a benefit in itself.

Docker helps with two problems DLLs dont:

1) what if I want to run your app on my Linux server and I dont have dotnet set up. I dont want to deal with installing and maintaining dontnet. And I dont trust your setup script

2) You want run a database and message as part of your application, and they aren't dotnet.

> And I dont trust your setup script

Worth pointing out that if you don't trust their setup script, then you probably shouldn't trust their Docker container either.

Yes, although there are (at least) two different meanings of "I don't trust your setup script". If I don't trust them to not do nefarious/malicious things, then I also shouldn't trust their Docker container either. If I just don't trust them to write a good setup script, eg, getting permissions right, putting pid files in the right location, not stomping on other stuff in various directories, etc. Docker solves that problem. They can put whatever badly written crap they want in the container and I do have some guarantees that it won't break the system it's running on.
There are also more considerations. For instance, a QR code library I use in .NET has a few Debian packages as dependencies for Linux (like Imagemagick). With Docker, these installed packages ship with the product as well.
You can run multiple (.net or other) services that are required by a project or application suite and manage it in a generalised way.

For example a database container a application container a web server.

Maybe you’re confusing docker with something like flatpak. .Net applications AFAIK are already simple to package and run so the benefit there is limited.

Context I’m a web engineer.

How to you deal with resource allocation (memory limits, cpu limits, etc.) ? How to give a different IP address to each application ? What if an application fails and starts filling your disk space and thus impacts others ?

Docker, and the tools on top of that, can make these problems easier

Also, the orchestration becomes easier - you can run the same docker container on multitude of different OSes and configurations. In addition to that, you get great isolation.
You can do resource control with cgroups. With systemd based distros you can use "systemd-run" and pass various flags that define the exact limits. Non systemd distros can use libcgroup or use the sysfs cgroup interface directly.

The IP address situation can be handled with the "unshare" and "ip" commands. Unshare allows putting a process in a new namespace and ip allows configuring the interfaces and moving them between namespaces.

Both of these solutions do not require any container runtime which is a big advantage efficiency wise!

I am not saying that using docker for these purposes is bad though, I just wanted to mention some lightweight alternatives in case anyone was interested.

Because DLLs are old and Docker is new.

DLLs have the unmistable stench of C++, COM and deep plumbing around them. Docker smells of json-over-http and familiar things.

Becuase your solution is specific to your specific software.

Then you have lads who use python who can use virtualenv, and then the ruby ones who use rbenv and then you have the CPP developers which are usually fine but on Tuesday Joe messed up the linker settings and all of a sudden we're not statically linked, thanks Joe.

And then our Java team forgot to tell the sysadmins that they upgraded the JVM version.

The thing with docker is that you just get a complete package with everything delivered directly by the developer and it's very hard to mess up.

Also docker can run applications that aren't shipped by people in your company and also does more stuff (like more sandoboxing and it also helps you with the resource management).

>And then our Java team forgot to tell the sysadmins that they upgraded the JVM version.

That's just a sing for bad communication/practices/documentation and processes, and why don't the dev's test the application on the dev/test-environment?

BTW: Why can the JAVA-team even install something on the dev/test/prod-env? That's completely against the rules.

> why don't the dev's test the application on the dev/test-environment?

They did, it failed, then the JVM was upgraded for dev/test (but not yet for prod as that was still running the prev-JVM version of the code).

>They did, it failed, then the JVM was upgraded for dev/test

See first error, dev/test has to be a exact mirror of production, otherwise it makes no sense. And if not even the admins from dev/test/production can communicate with each other...you really have deep rooted problem.

Because in reality, with large teams, communication is expensive.

Containerisation allows reproducibility, in a self-documenting manner. Delivery can be automated, depended upon to run, in a totally system configuration agnostic manner. It allows projects to deliver their software in a way that it can be ran, instantly, with a single command (image download not withstanding).

I've worked heavily in service software engineering for over a decade now, and with docker since 2013, and it has changed everything. There will be some people who don't fully understand it, but perhaps they don't have the problems that it solves and thus don't need it.

Alternatively, they /could/ just be suffering without realising there is a solution.

Yes. However, with more and more teams, and more and more languages, this communication, and especially planning and executing these upgrades and changes while providing rollbacks for all languages becomes harder and harder. It's very possible to do this, but it takes a lot of effort.

Containerization largely eliminates this need for communication. There are new problems - security of running containers, image sizes, building images safely and so on, but this problem disappears.

Well yes you are absolutely right, the first comment who sees the down and upsides clearly.
You can think of docker files and other manifests as a highly structured and more direct-to-machine form of communication. Instead of a developer informing other developers to ensure that the dev machines and servers are upgraded to the right version of the JVM via a confluence page or slack message, that developer has left machine-readable instructions in a consistent format in a readily accessible place.

This is a good way to fix those problems with communication/practices/documentation and processes.

>This is a good way to fix those problems with communication/practices/documentation and processes.

No it's not because the dev who made the database-container forgot to inform you that you need the new jdbc.jar etc...it's the same just with one more layer.

> The thing with docker is that you just get a complete package with everything delivered directly by the developer and it's very hard to mess up.

The downside is: you are also, at the same time, excluding yourself from standard patch management. Whereas in ye olde times for a situation like Heartbleed or log4shell the 24/7 on-call sysop team would run a simple "apt-get upgrade openssl" and restart all affected daemons, under modern "devops"/docker-centric models you need to wait for the developers or, worse, the container image vendor to deliver updated versions.

Is depending the container image vendor actually worse? I was under the impression that they were about as "on it" as the distro maintainers with updates. And if you wanted full control then you could just have your sysadmins manage their own base images. Then upgrading system dependencies becomes as simple as doing a regular deploy.
Sometimes it is. Depends on the vendor, of course, but I've seen them take many hours/days after a security fix is released.
I feel like I get this a lot with the NPM cli tool ... No matter how often I update my dockerfiles to the latest version, I'll always end up with a big banner warning message in my build output telling me I'm outdated 'cos it seems NPM updates (and publishes) constantly.
I don't use npm all that often, but I don't think I've ever run "npm install" in a project and not gotten any warnings about outdated dependencies and security vulnerabilities in them.
it gives a bit of a 'broken window' effect, 'cos while it still irks me now, I'll probably start turning a blind eye to all these warnings only to miss something really critical later on.
Not really; if you have a few servers vs. a few containers the patch is the same with the difference that the server one is probably "live" patching on a running system and the docker one is patching it in the docker file and re-deploying which makes it an atomic change.

It also really makes a difference if you are combining some tasks and ownership in a DevOps concept where it's not just developers handing off some binary and telling the operations team "good luck eh". That classic setup mostly brings finger-pointing problems and not really much of a solution.

What a patch management concept would look like is much a more modern version of the server thing: you have some system that allows you to query all the servers "what is your current version of thing X" and if you then want to change that you build an updated machine image and deploy that to all those servers. Cattle, not pets.

The problem is, most ops-focused people don't have much knowledge of the boatload of different application-side tooling, and most devs (especially those out of "coding bootcamps" or practical academia) don't have a single ounce of knowledge about OS management or even how basic UNIXoid fundamentals work.

DevOps has always been more about companies attempting to "save" money by shoe-horning together roles that require completely different skill sets.

> and most devs (especially those out of "coding bootcamps" or practical academia) don't have a single ounce of knowledge about OS management or even how basic UNIXoid fundamentals work.

I have to vehemently disagree with this statement. I had 10 years in IT before I did my bootcamp and quickly found myself in deployment engineer roles by accident. Most of my bootcamp cohort ended up at smaller companies wearing many hats and learning quickly where a good half of them are in deployment type roles by necessity.

After an additional 10 years in this industry, I'm finding invariably that it's the FAANGs and companies hiring fresh grads out of the best schools where engineers aren't learning anything about the underlying systems. It's the feature factories where people put their headphones on, the heads down and code like their life depends on it.

They do things like write microservices that implement basic functionality of opensource loadbalancers, because they've never heard of nginx or haproxy before, don't know how it solves their specific problem and wouldn't know how to configure it. When all you do is write code, every problem looks like it should be solved by writing code.

That hasn't been my experience in the last decade or so, and new people or people with different specialisations are indeed unlikely to be good at everything on the first day.

Before the overlap between development and operations there were multiple teams involved in every step, everything was slow and expensive and you'd be lucky to deploy once a week. After making sure that where overlap exists the people that have the skills to work in that area can do what they need to do (i.e. build, verify, release and instrument the systems they are responsible for) we get every team releasing every system multiple times per day in small increments. This is both faster, easier for everyone, and worth the ROI on people and tech far more than trying to invent borders and fiefdoms between hyper-specialised teams.

There are container management systems that can inform you of container images requiring patching. You can specify the policy for how to deal with these container images: do you quarantine them, do you stop running that application, or whatever. TBH, you're presenting a happy path scenario that often fails to materialize. Sure, "apt-get upgrade openssl" and restart all affected daemons, keeping your fingers crossed everything will still work. It often doesn't, and now your system is borked. That's not a good way to run an operation.
You personally don’t need Docker. Another example where it’s pretty much useless is if you can compile your code as a static binary.

Now if you use a wide variety of programs written in a wide variety of languages (e.g. a C database server and a Ruby web app and a JS daemon), then Docker is a very efficient way of staticizing your app just like you did with your .Net dlls or like you would by statically linking a Rust app.

I had the same issue and we ended up skipping docker all together.

We went from hosting on prem to directly azure web apps.

Something in between I think is where docker would be helpful.

I personally think that proprietary solutions that want to lock you in their infrastructure rank lower than standard software containers.

It probably makes sense for your business though.

> Docker is useful if you have multiple applications with different versions of the same dependencies running on the same machine, as well as for deploying onto any machine

Different languages get a different amount of boost here. Ruby is an example which strongly benefits from docker deploys. You don't need to worry about what version of ruby (if any) will be installed on the host. And you don't need to worry about getting your gem dependencies installed on the host or figure out how to bundle them into your app code.

I think older dot net apps required the runtime to be installed on the host. Docker would have helped then.

The other big draw of docker is uniformity in deployment (the shipping container metaphor). Deploying code of any language and third party apps can be very similar. This is a powerful enough idea that giant abstractions like kubernetes have come to exist. This would be much more fiddly if each application needed to be special cased.

It's best to think of Docker containers as mini virtual machines. They let you have a system that always boots up in the same state. Thats not really possible with standalone apps because the state of the OS is not isolated, even changes to the PATH can easily screw things up. The problem is 10x worse in Linux with all its distros and that's where Docker was invented. Try making a standalone app in Linux and you're soon looking into flatpaks and AppImages which have their own problems.
System wide dependencies and settings become completely encapsulated. You can even run shell command lines repeatedly from the same system state with the "input" of the command being changed files in a mounted volume. DLLs don't solve those problems.
To add to this: Docker usually gives you also network isolation and process namespace isolation.

You can of course achieve this by using other tools, but you'd effectively de re-implementing a container runtime with that.

The logo for Docker is a shipping container. Most shipping containers have well-defined standardized dimensions. This way cranes/boats/trucks/trains all over the world can handle any container, regardless of what is in it.

Docker does this for software. A docker image packaging an application has a well-defined interface to start/stop/configure/log the application, regardless of which language it was written in.

This way infrastructure tools (for example container orchestration like Kubernetes, docker-compose, swarm,... but also CI/CD tools and local development environments) can work with all sorts of applications in the same way. Docker (or containers) provide a uniform interface to all of these tools.

It’s a different conversation from the dev side and from the sysadmin side, and neither might apply to you.

As a dev, containers allow you to care a lot less about what’s installed on the server and in which version. You’ve already solved that in a different way for your setup, which is fine. I’d say the trick in your case isn’t the DLLs specifically (I’m not sure why you focus on that aspect), it’s the fact that .NET allows you to create a standalone application, including (in modern times) the .NET runtime. If that’s all you need, you’ve already minimized your dependencies on the OS, and you’re unlikely to benefit from using containers.

However, if you need anything else on the server (another runtime, a database server, a mail server), Docker could still help. Having the whole tech stack pinned in version control is really nice, and having the ability to make any changes to one’s setup at whim is very liberating.

For admins, there’s a different advantage. Especially when multiple technologies are in use somewhere, containers are a standard interface to host any kind of server application the same way. You don’t need to know or care that one technology just copies a directory, the other one requires a specific configuration of Apache and the third one requires setting up an extra cron job — just start the container and you’re done.

But it’s always important to remember that there’s no shame in not using a technology that HN has deemed cool, particularly if it doesn’t solve a problem you have.

> I’m not sure why you focus on that aspect

Ah that was because in the video I watched on docker, the guy gave the example of one application using node v14 and another application using node v9 on the same machine, so I was like why aren't they just separate precompiled files like nuget packages.

Great explanation though, thanks!

> I am probably misunderstanding something fundamental here

Don't blame yourself, it took me quite awhile to figure out the benefits as well.

Docker, or containerization in general is not a system that only helps you manage dependencies and isolation. It's about dependencies management, isolation (to a degree), fast and expectable deployment, automatic scaling and so on.

Now days, application backend are expected to run on multiple nodes. Sure, you can manually code your app to automatically deploy/update itself on multiple servers and so on, but that is a huge task, and the maintenance responsibility comes with it is even bigger.

Containerization platform such as Docker and Kubernetes provides a standard operating environment for operators and developers to manage the lifecycle of a scalable application, IMO that's where the gold is. Dependency management and isolation etc is just a natural part of that.

Of course, you're still responsible in creating a scalable application, but once you did that, deploying the application will be as simple as running a `git push`, then a remote system takes over, creating a container image according to your Dockerfile, and then deploy those containers onto necessary amount of servers. It all happens automatically, like a dream :)

The main reason to use Docker are the reproducible environments. Today, applications, have a lot of dependencies. In the application I am working, I have to install like ten different software programs and libraries only to be to run it, and I have to use concrete versions. If you work with other people, it is very tedious to have to install and configure all manually. With Docker you can make sure all of you are using the same kernel version with the same dependencies. So, also you can avoid, bugs derived from strange software versions combination.
1) Most software isn’t purely a collection of executable code. Usually there’s some configuration stored somewhere. (Files, registry, whatever.) Containerization ensures that we have one self-executing file that contains all of that state.

At this point you might respond with “I could zip up all that state and still make sure it unzips to a single folder” But that leads us to

2) Most production software has at least one dependency that needs isolation. Even if you’re good about making sure that you follow the rules to make folders self-isolating, eventually you’ll have to take a dependency made by someone who isn’t following the rules.

Suppose you have not one of your well behaved .NET apps, but 15 accumulated over time. You'd like them to share the same server, but they need different and incompatible OS fixes, different C runtime versions, multiple different executables with the same name in the PATH, carefully separated Windows Event Log categories, registry keys and whatever... sharing a server isn't so easy after all.

Docker lets you isolate a complete environment, not only your own application files.

The birds eye view of your application can be much more complex than code or library dependencies.

Your app might be expecting database X running on port Y, a specific OS version, Kafka running, etc...

Containers allow to configure all these "boxes" easily. Your co-worker can test the application with exactly the same "system" as you. And you can deploy it in that same "system" too.

> To me a standalone application is simply a directory with an executable and all dependency-dlls in the same directory / some subdirectory.

Not everyone who packages an app is willing to lock all their dependencies into a specific version.

Look at dependencies that are usually coming from the system package manager, such as OpenSSL, pandoc, libicu, the timezone database, and whatnot. Sometimes those require updates. Apps may want to pick up those updates. I understand every app packager who says, „I don’t want to cut a new release every time an OpenSSL vulnerability gets patched, or whenever some country decides to update their time zone.“

So vendors may prefer their app to pick up system-level packages rather than ship those along with the app. Docker will update those packages for you every time you build your image, and you don’t depend on the app vendor to cut a release whenever you do. At the same time, containerization helps isolate versioning conflicts that stem from those common dependencies.

A container is a happy place for a piece of software.

It has all the dependencies it needs, there is nothing else executing, the files are laid out just the perfect way, every configuration file or environment variable just magically appears.

It's very cheap to create and get rid of this happy place in contrast to dedicating an entire VM or bare metal. It's also cheap to move containers around so you could orchestrate them on multiple hosts.

Even though they raise others, they really answer to a lot of questions around developing, deploying and monitoring software reliably. That's why it's usually worth the hussle.

In addition to what the others have written, let's not forget the abstraction of storage and networking. Docker let's you present virtual network interfaces to the software, so the config can be the same everywhere. Every container thinks it's listening on port 3000 on its local host. Without Docker, if you want to run 2 or 3 different bits of software on the same host but listening on different ports, that's has to be part of the software config, not external to the software config. With Docker, every instance of the software inside a container can have the same network config, but external to the container you can do whatever network jiggery-pokery you want to line it up.

Likewise Docker volumes and mount points. The software gets an abstracted filesystem that's actually a "volume". Could be a chunk of local filesystem, an NFS mount, whatever. The software doesn't need to know. It just acts like it's the only software on the "system" and writes to any path that it can find. It can't corrupt any other containers also running on the same host by writing global configuration files or making "system-wide" changes because those system wide changes are contained to the container.

Docker also abstracts a ton of sysadmin/deployment type tasks. I have 1 container of app X running on host A. There's a load spike. I want a second instance of app X running on host B. Ok. Load spike is over, I want to get rid of the instance of app X on host B. Docker makes this kind of dynamic deployment/destruction automatable via APIs and there's huge libraries of software out there to do it. I don't know about the .NET packaging/deployment (NuGet, etc.) and whether that's as easily done.

It sounds like you know a lot about .NET, and I know very little. So it's entirely possible that Microsoft has solved the network and storage abstraction in ways I don't know about. In that case, if you don't NEED Docker, because you have this homogenous environment and effective isolation controls built into it, be comfortable in your lack of need.

Some dependencies are not DLLs. Actually docker needs a Linux kernel so there won't be any DLL, only .so, but that's basically the same thing. The point is that in a container we can encapsulate all libraries, files, other applications, etc, dump them in an image, ship, deploy and run them more or less as if they were in a VM of their own with no access to the rest of the system.

I don't know if there is a comparable technology for Windows. If you really want to appreciate the difference I think that you have to invest some time building and running docker containers and thinking if you really could do the same by switching DLLs. Don't forget the deployment story.

I agree.

Just FYI, for compatibility reasons, the .NET runtime uses the .dll extension for all libraries, even on Linux. Despite the extension, these files aren’t true Windows DLL files. They might have a stub PE header (not completely sure), but otherwise, they only contain what’s called Intermediate Language code that has to be JIT compiled by the runtime. The principle is similar to .jar files in Java, in case you’re familiar with those.

The .NET toolchain has a mode where it bundles all dependencies (.dll files) and your code (more .dll files) together with the runtime for the target platform (for example, Linux) into a redistributable directory. I think that’s what the OP is referring to.

Because it's not about some code or about some shared stuff. It's about atomic operations across different hosts without involving hard-barriered teams.

A container image can be seen the same way a machine image can be used: you ship an image of a solution instead of some files and instructions and 'hope for the best'.

If you imagine your application and it's required environment as a complete machine as a starting point, and then running/scaling that by simply having the only requirement on any host be "it must be able to run this image". That's a much smaller and more standard interface than individual files.

If you were to just make a 'smaller' version of that you'd end up with chroot/jails and if you want to use cgroups on linux for isolation you'd end up with containers (i.e. Docker containers). It's not the only method or the best method, but it's the easiest and most widely supported method.

Going even 'smaller' you could simply build static binaries with everything included and tell the OS that when it gets started it should do so with no permissions to access anything else on the host.

Smaller beyond that is unikernels where your application is the OS and runs bare on a host (be it bare metal or virtual).

The downside of the last two is that all the 'extra' files like assets and temporary data needs to be embedded inside that single file. The downside of chroot and jail are that there is no cross-host packaging method and it usually just works on one specific OS or host.

Virtual machine images and container images have the same benefits but the virtual machine has the drawback that it is much bigger, much slower to start up (generally) and has a much larger attack surface and maintenance overhead.

What containers aren't is:

  - A package manager
  - Application Server Containers
  - Configuration management (well it shouldn't)
  - A replacement for a single-purpose VM
Technically containers also require you to be stateless and read-only. This nearly universally leads to better applications, better operations and better security. That gets us to the cattle vs. pets part of containers: the idea is that you ship containers, not whatever happens to be "inside" those containers. As long as a container is "good", any host that can run containers should be able to perform exactly the same to the point where running it on your laptop, on a mainframe and on a standard bare metal server does exactly the same thing. If you then wipe the server, install a different OS that can run containers and start the container again, it still does exactly what it should do.