Tiny Container Challenge: Building a 6kB Containerized HTTP Server
devopsdirective.com
devopsdirective.com
Also, https://busybox.net is a 1.0MB static binary that provides all of the standard posix-compliant tools (sh, grep, tail, etc) and actually comes with an httpd implementation. So if you wanted a more useful container at about 1MB, you could use it.
Edit: here's a previous thread on UPX that has 80 comments talking about the tradeoffs it makes. You should definitely consider these if you intend to use UPX: https://news.ycombinator.com/item?id=15456980
But yeah, the tradeoff of that 1MB is that you get a very functional shell environment vs just an http server. It's impressive just how small ut is, given everything that it is capable of
They do distribute statically linked individual binaries [0]. For just HTTPD and nothing else is about 85kb. But it is a lot more than just a static file server - it comes with CGI support.
Maybe.. I suspect a well-designed Forth image could pack similar capability in less than 100kB. So it might seem low, but not a miracle.
On the other hand, if it could be demonstrated that modest increases to binary size correlated to significantly improved compressability, then maybe that's something that should just be considered by the compilers themselves.
[1] https://docs.docker.com/storage/storagedriver/overlayfs-driv...
So in fact, it’s not might use more resources, it’s guaranteed to use more resources.
The only benefit of UPX is saving disk space, which is unlikely to be super useful to most people these days.
Similar to how saving disk is less useful to most people these days, RAM on servers in big docker/kubernetes clusters is also likely not much of a concern for binaries that are well under 100MB if you're not running a ton of copies on each server. In particular, it seems like this tradeoff is most useful for static go binaries, which don't benefit from shared system libraries and aren't likely to have many copies per server, but of course that definitely depends on the use case and I definitely do not recommend blindly using UPX without considering the tradeoffs.
It would be an amazing integration though if it were possible to integrate UPX with docker's layer compression such that the binary is extracted to the FS before running. But given how it all works, that's pretty challenging to implement and the network is only getting faster...
After applying UPX, the result should be nearly uncompressable, so if UPX performs worse than layer compression then it might be better for network usage to not use it at all.
In this scenario where you are unpacking it in advance, that eliminates the main benefit of UPX which is that it is self-extracting.
It added 18.5 kB on top of busybox, so, not as good as the 6kB ASM server code. With busybox included, the whole image is 1.18 MiB, or 751 kB compressed.
If anyone cares to take a look: https://hub.docker.com/r/swalog/tiny-hello/
$ ls -shL `nix-build -E '(import <nixpkgs> {}).pkgsCross.musl64.busybox.override { enableStatic = true; enableMinimal = true; }'`/bin/sh
128K /nix/store/qkq9cj81mcvnfr5y04jmf6nqvnrfg2z1-busybox-1.31.1-x86_64-unknown-linux-musl/bin/sh
I figured there were a bunch of ways to shrink that one, and I had a C based image that was in between my GO and ASM images, but decided to cut them for the sake of brevity!
gives you a multiprocess http server in 612K uncompressed if you compile -Drelease-small
In a world of xTB thumbdrives, why are we so worried about containers being <1MB? Security is one thing, minimalism is another, but I do sometimes wonder about this.
Anyway, if you want to build minimal containers and want to do it with a bit more backing than this one (pretty well written) blog post, probably also check out distroless:
https://github.com/GoogleContainerTools/distroless
I personally find most of the time alpine-based containers are more than small enough and good enough with nice ergonomics (sometimes you just want to shell in to the container), but maybe it makes sense to build containers with and without debugging/inspection tools (a `project:vx.x.x` and a `project:vx.x.x-debug`) and switch them out when problems arise.
When software was still delivered in physical boxes, I noticed the same thing and I even found products in the past where the .exe had megabytes of random data attached to it to make it look bigger.
With Docker images it's all a lot easier to bloat things up and 'hide things'; I used to pack up our main product in a chroot tgz (this is early 2000s) with an installer, so I did not have to actually think about different linux distro's or installers; I know for a fact that some people bought our product over competitors because the installer was so large in size so it looks like better value. People told me when I asked them afterwards why they did not go for the competition. And this was not intentional; it just had a full Debian install in a chroot.
Particularly flinging $100 bills at peasants and how RDBMS set pricing for their products. If you do have an implicit awareness of Oracle and their tactics, you will find this an amusing piece of nostalgia when people were starting to understand just how to build efficient and nice web sites in the 1997 time frame.
You not only store these images once in the registry, but on all container hosts that they might run on. And you need to transmit them, if they aren't cached there, yet. And you may want to redeploy new images several times a day, as they get updated, new ones added, etc.
To be fair, the above is not the use case containers were originally intended for. And if your 10 GiB container image only runs on 3 or 5 nodes and only gets updated once a week, you obviously don't have to worry about the overhead too much.
But at scale (beyond 10 nodes) and/or on very fast development cycles (=more then one deployment per day), size IMHO starts to matter.
I can certainly see that it can be a painful problem (10-40GiB is huge), but I expect most smaller shops that aren't running on their own servers/colo as such these image sizes were never even an option (I can't imagine trying to upload 40GB across the public internet to launch one instance!).
It's also not usually total-person-clock-time.
It's number-of-distinct-manual-steps.
(5 minutes manual) + (15 minutes automation) + (5 minutes manual) + (30 minutes automation) = everyone hates doing it
(10 minutes manual) + (45 minutes automation) = everyone's happy and can use their time productively
Not only do I have to do something, I also have to set an alarm / watch the clock to know when to do something.
If you separate the heavy stuff into a base-image you still have to load it on the beginning of CI, which without beefy machines with local SSD caching can take a loooong time.
A general observation on trade-offs: With containers layering and COW you get fast development, but have to pay the performance bill when you happen to download and apply an image for the first time. Similarly, taking a snapshot of a LV under LVM or on a ESXi VM is fast, but applying the snapshot is slow.
We had therefore at one point considered using Ceph RDB and their COW clone snapshots[0]. It would let us do "cheap" restores of snapshots. Our initial tests showed that the network bandwidth requirements[1] would have needed some serious infrastructure re-engineering in order to keep up with our I/O expectations. And again, the containers slot in nicely with commonly available local resources and allow working offline, to a degree.
[0] https://docs.ceph.com/en/latest/rbd/rbd-snapshot/ [1] the environment in question uses 10 - 40 Gb/s network links, 40 on the gitlab git and container registry side, 10 on the blade side.
That said, dependencies do take up a lot of space, but machines these days also come with a lot of space. People also run prune and develop caching strategies (ex. NPM) as a result. It's not like every container will be a GB in size, as it's not reasonable to have every container have that much content, but how many people are out there using 950MB dependency-filled base containers with 50MB of actual relevant application code? Most people learned the run-it-on-alpine lesson like 5+ years ago at this point.
Just recently I found that I had like 100s of GBs used up on my hard drive because I'd installed and used podman for a while and it's image store was separate and just... sitting there, long after I'd moved back to docker for reasons.
I've written guides on how to boot Alpine (even with GUI) over PXE [1] and how to set up a perfect file server that runs from a RAMdisk USB Thumb drive with full disk encryption [2]
[1] https://blog.haschek.at/2019/build-your-own-datacenter-with-... [2] https://blog.haschek.at/2020/the-perfect-file-server.html
https://blog.haschek.at/2020/the-perfect-file-server.html is made obsolete by SmartOS, which does the exact same thing, in addition to offering Triton (enterprise web GUI for virtual systems), OpenZFS (end-to-end data protection, ease of administration), zones (full-blown, running-at-the-speed-of-metal virtual UNIX servers), DTrace (for production-safe, real-time, deep machine state instrumentation and inspection), Bardiche (for building firewalls), FireEngine / Crossbow (high-performance TCP/IP stack for building virtual switches and routers) and imgadm(1M) / vmadm(1M) (for virtual server provisioning and software management), and last but not least, a reference implementation of the NFS V4 protocol.
or by proxmox or by any other OS. That's not what it's about. If it comes with a web gui, it's already bloated for me. This project was about the minimal perfect setup for my needs and I only need SSH access
As far as I am aware, and please correct me if I am wrong, but "proxmox" does not have nearly the same list of capabilities as SmartOS, all the while running from a ~650 MB, read-only RAMDisk.
Base Ubuntu/Fedora used to be absolutely huge, and no one was smart/patient enough to pick out all the dynamic libs that you needed with your JAR or script to go into production so you usually just shipped the whole fat image.
Thank you for those links, I am about to gobble up those blog posts -- I recently went on a benchmarking kick[1] and diskless alpine instantly struck me as the perfect server setup. ECC memory + running from ram would give me full use (to put in RAID/whatever else) of the NVMe drives, it's something I'm going to try out as soon as I get a chance to.
I am so interested in the infrastructure space, I know exactly two hosting providers that will give me PXE level access (so I could use something like tinkerbell[2]):
- OVH [3][4]
- Vultr[5]
- LeaseWeb[6]
Unfortunately my personal favorite hosting provider, Hetzner[7] (I fell in love the moment I came across the robot marketplace) does not offer it yet, though I've automated going through their rescue system at this point so it's OK.
[0]: https://www.tfir.io/meet-the-creator-of-alpine-linux-natanae...
[1]: https://vadosware.io/post/k8s-storage-provider-benchmarks-ro...
[2]: https://docs.tinkerbell.org/
[3]: https://github.com/gmasse/ovh-ipxe-customer-script
[4]: https://geekgonecrazy.com/2020/09/07/tinkerbell-or-ipxe-boot...
[5]: https://www.vultr.com/docs/ipxe-boot-feature
[6]: https://kb.leaseweb.com/products/dedicated-server/installing...
Saving 10% in size may actually translate to a non-trivial cost saving - both obvious and cost-as-in-paid-waiting-time.
But the vast majority of teams in my experience have spent ages farting around with these kind of micro-optmisations, whilst there were far better opportunities to improve the infrastructure. And in the process, they actually made it more fragile and harder to debug.
Simply put, this is a niche skill with niche value. Most people trying to do this probably can't and shouldn't do it.
I found that most people focus on size and forget about reuse.
Sure, each layer adds size to your final image, but you can reuse them, so that you don't need to transfer the complete image but just the layers on top of the one that changed.
Flatten everything and you need to ship the whole stuff.
Good question! Security should be a priority instead.
A lot of containers on docker hub have serious vulnerabilities, as proven by various research papers. Removing bloat is good for security.
However, using a well-tested distro and receiving security updates from its security team is more important than having a tiny footprint.
This is a good reason to use Debian stable in containers and avoid alpine.
This is exactly what we do. Once the first build of our application is installed on our customer's servers, it rebuilds itself from source each time. This is also really good at firewalling CI/CD compromises away from your customers, as long as you don't merge cryptolockers/miners into your private github repos.
In one place in Africa driving to another site 3 hours away gets network speed 10x better, so we do this from time to time.
This container ends up being 436 bytes https://github.com/sigmonsays/smallest-docker-httpd
paulfurtado in this comment section mentioned UPX[0] as a way to get binaries even smaller. Funnily enough UPX even managed to decrease the size of the asmttpd web server for me:
### build stage ###
FROM ubuntu:18.04 as builder
RUN apt update
RUN apt install -y make yasm as31 nasm binutils git
RUN git clone https://github.com/nemasu/asmttpd.git asmttpd
RUN mv asmttpd/* .
RUN rm -rd asmttpd
RUN make release
# i have the upx binary on my machine in this case
COPY ./upx-3.96-amd64_linux/upx upx
RUN ./upx --brute asmttpd
### run stage ###
FROM scratch
COPY --from=builder /asmttpd /asmttpd
COPY ./index.html /web_root/index.html
CMD ["/asmttpd", "/web_root", "8080"]
With a 4 byte index.html my total image size is now 5.3kB!Thanks for the kind words. I didn't know about UPX.
I should add an update to the end with this trick!
Here is an image with a statically linked chown binary from the busybox tools cowering this use case: https://hub.docker.com/r/privatebin/chown (I am the author of that image)
Benefits of using a small image are less attack surface and faster operation (less data to download, lower startup time, less memory, etc.).
My (somewhat silly) use case for the tiny container was trying to cram 10,000 pods onto a k8s cluster without breaking the bank: https://www.youtube.com/watch?v=1y2nRNexRVk
https://github.com/opencontainers/image-spec/blob/master/ima...
But this is our flow too. We have a jenkins job that builds our master branch and uploads deb packages to jfrog. And then another to create a GCP image with these psckages, and a third to actually deploy these images to GCE. Containers are used for the various jenkins jobs, but not on the actual GCE instances.
In a previous incarnation I wrote https://github.com/regularfry/au to take the grunt-work out of packaging up ruby apps, and pretty much all it does is generate the minimum set of files that `dpkg-deb` needs to spit out an archive.
Reminder: the Debian Packaging Guidelines are meant for official Debian packages.
Making a package with dpkg-deb -b <dir> <packagename> is very easy and gives all the features of APT (dependency tracking, config management, atomic deployment).
Containers do provide benefits.
Restated, my point here is that while containers can be more complex and have pitfalls (a lot of which have been worked out somewhat at this point), there is no complexity free lunch -- `[docker|podman|crictl] run --rm --cpus 2 --memory 500mb ...` is pretty darn easy, and more so than writing properly portable and well-considered systemd unit files (and putting them in the right place, with the right permissions, under the right slice, etc). It's easier than most of the options out there (including the old methods of per-user resource segregation).
--cpus 2 --memory 500mb
to the argument vector liberates these options' definer from "understanding [...] the subsystems that power it", and reflect on their potential impact. And I'd argue that THIS is the real complexity incurred by these kinds of resource constraints that seem so simple on the surface - not the specific syntax or location you have to use to introduce them. All of which makes systemd unit files and their settings' implications (which are amazingly well-documented btw) as good as any other option, imho.If I want to run a useful piece of software like redis let's say, but I want to run it with a resource constraint to make sure that it doesn't take more than 2CPUs and 500MB of memory, it is far easier to do that with the following command line:
docker run --rm redis --cpus 2 --memory 500mb -p 6379:6379
Than to write the equivalent systemd unit file, set up the isolated filesystems that docker would let you easily bind mount in, etc. This is like comparing systemd-nspawn to systemd -- if systemd-nspawn isn't simpler than systemd then what are we even doing.Docker won because of it's developer ergonomics (containers weren't new), systemd won because of it's feature set, convenience and sturdiness. They're different tools with different primary use-cases.
As a (former) developer I d rather ran redis in a container during development, but for production I d rather rely on boring VMs unless some scaling is required. (Managed k8s case set apart)
Agree, but my view on this is that the implicit answer of how you do all that (i.e. the file system, syslog) is now gone. There will be pain (complexity) in the short term, but at the end of the day, we're going to be able to build much better orchestration and systems. To kind of restate that, before you had to worry where a process wrote out it's output (stdout? /var/log/<program>? /etc/<program>/logs? /home/<user>/<program>/logs? syslog?), now you know want to get the non-stdout/stderr logs of the thing you're running, you'd better give it a volume to write to (which may be fake, and actually write everything to some remote storage or something), and I think that's a step forward.
Of course, I'm not saying containers should go everywhere -- relying on boring VMs over containers is fine too -- but I think rich world of functionality available to container-driven workflows is popular for good and bad reasons, and the good reasons are worth exploring/beneficial to me.
I thought the kernel has virtual memory and if you consume more it will swap some memory and thats it.
Couldnt you just manage memory from inside your app? if its redis, then check the db size and shutdown/cleanup gracefully, and not crash redis with OOM?
> I thought the kernel has virtual memory and if you consume more it will swap some memory and thats it.
Well just to make sure the right memory goes to the right places -- if someone uploads a large file and you've made a mistake in your code that tries to hold it all in memory instead of buffering it straight to disk for example, you'd want that process to crash, and not your machine.
Also you generally don't want to swap, so much so that Kubernetes disables it immediately[0]. Not that Google is the only group with the right answer but they seem to think nothing can come of a machine having to swap. Maybe they're right. Even if they're not, A world where one service swaps[1] (I've never done this with docker to try it though) is probably better than one where it uses all the memory and everything swaps.
> Couldnt you just manage memory from inside your app? if its redis, then check the db size and shutdown/cleanup gracefully, and not crash redis with OOM?
You'd be surprised -- some languages just don't have a way to very easily get feedback from GC[2]. It's also something that I don't think most people think about, messing with the -XmXx<setting>s in Java is definitely year 2/3/4 java development for most people.
[0]: https://github.com/kubernetes/kubernetes/issues/53533
[1]: https://docs.docker.com/config/containers/resource_constrain...
contrast it to Ops mentality of software is a cattle (here is your memory quota and if its OOM, just kill/restart the service and hope next run it wont run OOM)
Wow, given how expensive RAM is it is no surprise they will never allow kubernetes work with swap, becausr it directly translates to $$$ for GCP and other cloud providers. Also since everyone loves using Java/Spring/Node and other memory hungry frameworks - it print enormous $ for cloud providers to require users overallocate RAM and disable swap.
or am I just spitballing conspiracy theory here and there is no conflict btw decisions like these and vendors' revenue streams?
To install:
apt install mypackage
To edit the systemd unit (automatically creating the file in the right place): systemctl edit mypackage
Then add these lines to limit to 2 cpus and 500mb memory: [Service]
CPUAccounting=true
CPUQuota=200%
MemoryAccounting=true
MemoryHigh=500M
And then to start it now and on boot: systemctl enable --now mypackage
This is now integrated with package updates, starts on boot, logs to the same place that most other system utilities do and so on. --cpus 2 --memory 500M
is easier, and gets you the same results though they may not be as permanent or as well managed -- the management and external stuff is an orthogonal concern, and that's not the situation I was addressing. The original point was insinuating that throwing up a binary and getting it to run. your filesystem is also not available to the container by default, and in this way docker sort of fails closed. If you're running a rootless container, the story is even better.One thing you have not covered is filesystem isolation, which docker also does very easily. There is a lot to configure on the systemd side[0] and the parts that are overlapping are just easier to configure and run with docker. Systemd is the better tool to build repeatable installs for pet processes, but again, there is a lot of knowledge underneath that is related. People to this day still complain that systemd does too much (I personally like it a lot, and it's great to have everything in one place).
[EDIT] Just to make myself clear, systemd is an amazing tool -- I like it, I run it, I'm not smart enough to administer a more complicated setup -- but docker is easier, for a large part of the small subset of systemd's capabilities that docker covers.
Yes, you'd need to learn how to make a systemd unit file but honestly, that shouldn't be a problem at all. I've had much worse headaches fighting Docker's networks and firewall-overruling network configuration in the past. Every time I forget to specify a restart option, I have to dig through my shell history again to see how I launched the image so I can kill and recreate it, or I have to look up that oneliner that shows you the docker run command for a running container.
I run stuff in Docker for one simple reason: I have it installed and I'm too lazy to think about software sometimes. If you're packaging software, that laziness isn't a reason to pick a distribution tool.
Static binaries have other problems, perhaps most importantly package management. How do you distribute your app? Traditional deb/rpm packages? curl-to-bash installers? Snap? Flatpak? Binaries that people flog into /usr/bin? What about automated updates, do you set up a repository, do you include a self-updater in your code? The list goes on. Docker solves that by having one general source of packages with the option of adding a repo of your own (take note, Canonical, your shitty Snap Store is practically useless without that last bit).
For services I run on servers, I much prefer traditional packages with dynamically linked executables, for a simple reason: automatically fixing security issues and bugs across applications with a single update command, instead of having to wait for every maintainer of their statically-linked tool to update their dependencies.
It's one of the big problems I have with Rust; many dependencies and applications embed their own versions of dependencies with sometimes very specific versions, and when there will eventually be a massive security problem in one of the TLS packages I'm dependent on recompiles and code modifications from random open source maintainers.
I can only offer one explanation: marketing.
Our images are made from Go binaries, so thanks to Go modules, we can manage dependencies centrally from the main module and rebuild.
Containers are not a security measure. They're acceptable for some resource constraint considerations, but you should not ever use them because of security concerns. If that's something to worry about, use VMs at the very least.
This is repeatedly really commonly, but is bordering on wrong more and more every year. At the lowest level it's not even completely correct. Containers are resource + namespace isolation -- cgroups and namespaces -- isolation and kernel-assisted resource hiding/access control is absolutely a facet of good defence-in-depth. Enhanced security is not a core feature of containerization as a whole but it can absolutely help with your security posture.
For what % that satement is wrong, it's even more so because it fails to consider the container runtime. Container runtimes these days (containerd is the best one IMO) include ways to run containers at different levels of isolation (VMs and as micro kernels), so a move to containers can absolutely help security posture, because now you don't have to pull out packer/ansible/etc to build your VM, you can just take the same container you were running and change the runtime. Time to ship/launch improved security features is important, and containers can reduce that burden for teams.
Containers are a process isolation and resource control tool -- they can absolutely aid in security posture. Would you say the same about BSD Jails?
Just so there's some more value here, here are some projects that are somewhat close to cutting edge (though it's been a while) in this space that I like:
- nabla-containers.github.io/
- https://github.com/firecracker-microvm/firecracker-container...
- https://github.com/kata-containers/runtime (see https://github.com/kata-containers/documentation/blob/master...)
- https://rootlesscontaine.rs/
- https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2...
- https://starkandwayne.com/blog/the-capable-kernel-an-introdu...
The container ecosystem surfaces a lot of really cool resource limiting and isolation techniques and puts them in very close reach.
Well sure, this heavily depends on the people you're talking to and what their imaginations are like. If it's the "containers are lightweight VMs" crowd then sure -- that view is fundamentally wrong.
> Linux control groups provide no meaningful resource isolation.
So this is a pretty bold claim -- as far as I can see processes certainly get OOMkilled when they use more resources than allowed for by their cgroup.
Do you have a link you could share? Is some fundamental part of cgroups (v1? v2?) broken in some way I haven't heard of up until now such that everyone has patched around it and it does what it says on the tin but despite the code in the kernel?
Another example is a control group that is chronically out of memory. It may page vigorously, which will have external effects on other control groups through unaccounted resources like nvme controller time, memory fragmentation, global vmscan, etc.
A third popular way to abuse the resources of other containers is through networking. The way Linux handles network traffic is frankly hostile to proportional resource sharing. A control group with zero CPU quota can still easily cause external CPU time consumption.
> Like traditional containers, Firecracker microVMs offer fast start-up and shut-down and minimal overhead. Unlike traditional containers, however, they can provide an additional layer of isolation via the KVM hypervisor.
I think the security mechanism of note there is the hypervisor -- a VM technology. The fact that it is manageable via a daemon/API that was created for containers, does not mean it provides security via Linux containers, the mechanism.
A similar story can be told regarding capabilities, a mechanism that is orthogonal to containers -- all combinations of with and without capabilities and containers have their uses.
I do think GP is overly glib and assumes a particular threat model in order to dismiss Linux containers so thoroughly, and you are right to note "they can absolutely aid in security posture", etc. But GP is correct to note that VMs (though also imperfect) provide certain kinds of security/isolation that Linux containers, the mechanism, cannot yet match.
If you can easily break out of any containerized enviornment, I'd suggest you could register for bug bounty programmes and make quite a lot of money also you can get a reward from Jessie Frazelle by escaping from https://contained.af/
Obviously containers have a larger attack surface than say a VM hypervisor, but security is not an absolute and both containers and VMs have suffered from breakout issues in the past. No security measure in isolation provides perfect protection.
I let you research when that was, and wonder what container technologies might have used since then.
I know when they make sense and when they happen to be fashion.
Half the "static" binaries that used to float around weren't even fully static because of glibc, until building with musl (and getting bit by getaddrinfo) became widespread -- the situation is not as simple as you made it seem.
My point is that I see containers being overused nowadays, like "big data" that fits on a USB pen kind of scenario.
I think we've basically stumbled into a very effective and widespread packaging paradigm though -- now you don't even have to pick the right language/toolset to get static binaries easily -- just throw a container over the wall and your filesystem requirements (mounts), network requirements, etc will be made pretty obvious to the person doing the deployment
Then I remember Linus point of view about monolitic kernels and how they are supposed to beat micro-kernels, what for, when people put hundreds of virtualization layers on top.
It contains everything you need, can provide sandboxing, integrates well in the OS, can do deploy and rollback, manages configuration changes.
Scratch has no tooling or libraries or anything. It's empty. You typically add a binary into it and then just run that binary, but due to the lack of libraries, it just be statically compiled. So in answer to your question, you can run statically compiled C. You can't run Java due to the lack of JVM which I believe has its on set of filesystem dependencies, but I'm not a Java dev, so I'm not entirely sure.
Why aren't everyone just using scratch?
The naive solution using the golang image is nearly 1GB. Why carry this extra complexity around?
Static linking glibc is painful because the former maintainer has some strong opinions: https://www.akkadia.org/drepper/no_static_linking.html
Most distros force you to dynamically link every dependency if you want them to package it. So the default build for most projects is dynamic
You've stumbled on a holy war between distros and guys like you, me, and Linus Torvalds[1] that want to deploy a binary and just have it work everywhere.
1: https://youtu.be/5PmHRSeA2c8?t=295 (Highly recommend watching Linus's answer ~6 minutes in)
But here we are using Docker so we have full control over the application we are building. Why this craziness of having these huge images containing who knows what?
Is it just because we can? And the cloud providers like us when we do it?
I agree with you and the parent commenter, this should be the default, but some people are against static-linking, even in cases dynamic-linking provides no advantages.
If you need some basic OS files, Google's distroless is another option.
I submitted a patch to darkhttpd so folks can build the image themselves, but you can also download the images here. https://hub.docker.com/r/mileselam/darkhttpd/tags
https://zwischenzugs.com/2018/05/22/a-docker-image-in-less-t...
TL;DR can you get an image to run for test purposes that can fit in a screenful of base64?
Would anyone use this in production? I doubt so. You want a reliable battle-tested solution. Nginx can be compiled to 500kb without OpenSSL; if you need https then it's just a few megabytes, I find it acceptable for non-embedded solutions like containers.