I think you're conflating software build with environment builds - they are not the same and have different use cases people are after.
> Why on Earth does copying in data require spinning up a container?
It doesn't.
> Containers are read-write by default, not read-only.
I don't think you really understand containers since COW is the default. Containers are not "read-write" by default in the context of the underlying image. If you want to block writing to the file system that is trivial.
> Things that are logically imports and exports do not have descriptive names. So your container doesn't expose a web service called 'API'; it exposes port 8000. And you need to remember it, and if the image changes the port, you lose, and there is no good way for the tooling to help. Similarly, volumes need to be bound to paths, and there is nothing resembling an interface definition to help get it right. And, since containers are read-write by default, typoing a mount path results in an apparently working container that loses data.
Almost all of this is wrong.
> And the compose files and quadlets are, of course, not really compatible with each other, nor are they compatible with Kubernetes without pulling teeth.
What? This gets wilder as you go on. Why would you expect compose files to be "compatible" with k8s? They are two different ways to orchestrate containers.
Pretty much everything you've outlined is, as I see it, a misunderstanding of what containers aim to solve and how they're operationalized. If all of these things were true container usage, in general, wouldn't have been adopted to the point where it's as commonplace as it is today.
> I think you're conflating software build with environment builds - they are not the same and have different use cases people are after.
They're not so different. An environment is just big software. People have come up with schemes for building large environments for decades, e.g. rpmbuild, nix, Gentoo, whatever Debian's build system is called, etc. And, as far as I know, all of these have each layer explicitly declare what it is mutating; all of them track the input dependencies for each layer; and most or all of them block network access in build steps; some of them try to make layer builds explicitly reproducible. And software build systems (make, waf, npm, etc) have rather similar properties. And then there's Docker, which does none of these.
> > Containers are read-write by default, not read-only.
> I don't think you really understand containers since COW is the default. Containers are not "read-write" by default in the context of the underlying image. If you want to block writing to the file system that is trivial.
Right. The issue is that the default is wrong. In a container:
$ echo foo >the_wrong_path
works, by default, using COW. No error. And the result is even kind of persistent -- it lasts until the "container" goes away, which can often mean "exactly until you try to update your image". And then you lose data.> > Things that are logically imports and exports do not have descriptive names. So your container doesn't expose a web service called 'API'; it exposes port 8000. And you need to remember it, and if the image changes the port, you lose, and there is no good way for the tooling to help. Similarly, volumes need to be bound to paths, and there is nothing resembling an interface definition to help get it right. And, since containers are read-write by default, typoing a mount path results in an apparently working container that loses data.
> Almost all of this is wrong.
I would really like to believe you. I would love for Docker to work better, and I tried to believe you, and I looked up best practices from the horse's mouth:
https://docs.docker.com/get-started/docker-concepts/running-...
and
https://docs.docker.com/get-started/docker-concepts/running-...
Look, in every programming language and environmnt I've ever used, even assembly, an interface has a name. If I write a function, it looks like this:
void do_thing();
If I write an HTTP API, it has a name, like GET /name_goes_here. If I write a class or interface or trait, its methods have names. ELF files expose symbols by name. Windows IIRC has a weird old system for exporting symbols by ordinal, but it’s problematic and largely unused. But Docker images expose their APIs (ports) by number. The welcome-to-docker container has an interface called '8080'. Thanks.At least the docs try to remind people that the whole mechanism is "insecure by default".
I even tried asking a fancy LLM how to export a port by name, and LLM (as expected) went into full obsequious mode, told me it's possible, gave me examples that don't do it, told me that Docker Compose can do it, and finally admitted the actual answer: "However, it's important to note that the OCI image specification itself (like in a Dockerfile) doesn't have a direct mechanism for naming ports."
> > And the compose files and quadlets are, of course, not really compatible with each other, nor are they compatible with Kubernetes without pulling teeth.
> What? This gets wilder as you go on. Why would you expect compose files to be "compatible" with k8s? They are two different ways to orchestrate containers.
I'd like to have some way for a developer to declare that their software can be run with the 'app' container and a 'mysql' container and you connect them like so. Or even that it's just one container image and it needs the following volumes bound in. And you could actually wire them up with different orchestration systems, and the systems could all read that metadata and help do the right thing. But no, no such metadata exists in an orchestration-system-agnostic way.
> If all of these things were true container usage, in general, wouldn't have been adopted to the point where it's as commonplace as it is today.
Software doesn't look like this. Consider git: it has near universal adoption, but there is a very strong consensus in the community that many of the original CLI commands are really bad.
Containers are not a software development platform, but a platform that can be used in the build phase of software development. They are very different. Docker is not inherently a software development platform because it does not provide the tools required to write, compile, or debug code. Instead, Docker is a platform that enables packaging applications and their dependencies into lightweight, portable containers. These containers can be used in various stages of the software development lifecycle but are not the development environment themselves. This is not just "big software" - which makes absolutely no sense.
> Right. The issue is that the default is wrong. In a container: $ echo foo >the_wrong_path
Can you do incorrect things in software development? Yes. Can you do incorrect things is containers? Yes. You're doing it wrong. If you are writing to a part of the filesystem that is not mounted outside of the container, yes, you will lose your data. Everyone using containers knows this and there are plenty of ways around it. I guess in your case you just always need to export the root of the filesystem so you don't foot gun yourself? I mean c'mon man. It sounds like you'd like to live in a software bubble to protect you from yourself at this point.
> If I write an HTTP API, it has a name, like GET /name_goes_here. If I write a class or interface or trait, its methods have names. ELF files expose symbols by name. Windows IIRC has a weird old system for exporting symbols by ordinal, but it’s problematic and largely unused. But Docker images expose their APIs (ports) by number. The welcome-to-docker container has an interface called '8080'. Thanks.
You clearly don't understand Docker networking. What you're describing is the default bridge. There are other ways to use networking in Docker outside of the default. In your case, again, maybe just run your containers in "host" networking mode because, again, you're too ignorant to read and understand the documentation of why you have to deal with a port mapping in a container that's sitting behind a bridge network. Again you're making up arguments and literally have no clue what you're talking about.
> Software doesn't look like this. Consider git: it has near universal adoption, but there is a very strong consensus in the community that many of the original CLI commands are really bad.
OK? Grab a dictionary - read the definition for the word: "subjective", enjoy!
> Containers are not a software development platform, but a platform that can be used in the build phase of software development. They are very different. Docker is not inherently a software development platform because it does not provide the tools required to write, compile, or debug code.
You seem to be arguing about something entirely unrelated. GNU make, Portage, Nix, and rpmbuild also don’t provide tools to write, compile, or debug code.
> Can you do incorrect things in software development? Yes. Can you do incorrect things is containers? Yes. You're doing it wrong.
This is the argument by which every instance of undefined behavior in C or C++ is entirely the fault of the developer doing it wrong, and there is no need for better languages.
And yes, I understand Docker networking. I also understand TCP and UDP just fine, and I’ve worked on low level networking tools and even been paid to manage large networks. And I’ve contributed to, and helped review, Linux kernel namespace code. I know quite well what’s going on under the hood, and I know why a Docker container has, internally, a port number associated with the port it exposes.
What I do not get is why that port number is part of the way you instantiate that container. The tooling should let me wire up a container’s “http” export to some consumer or to my local port 8000. The internal number should be an implementation detail.
It’s like how a program exposes a function “foo” and not a numerical entry in a symbol table. Users calling the function type “foo” and not “17”, even though the actual low-level effect is to call a number. (In a lot of widely used systems, including every native code object file format I’m aware of, the compiler literally emits a call to a numerical address along with instructions so the loader can fix up that address at load time. This is such a solved problem that most programmer, even agency assembly programmers, can completely ignore the fact that function calls actually go to more or less arbitrary numerical targets. But not Docker users — if you want to stick mysql in a container, you need to type in the port number used internally in that particular container.)
There are exceptions. BIOS calls were always by number, as are syscalls. These are because BIOS was constrained to be tiny, and syscalls need to work when literally nothing in the calling process is initialized. Docker has none of these excuses. It’s just a handy technology with quite poorly designed tooling, with nifty stuff built on top despite the poor tooling.
Because that’s how networking works in literally every system ever. Containers don’t magically "export" services to the world. They have to bind to a port. That’s how TCP/IP, networking stacks, and every server-client model ever designed functions. Docker is no exception. It has an internal port (inside the container) and an external port (on the host), again, when we're dealing with the default bridge networking. Mapping these is a fundamental requirement for exposing services. Complaining about this is like whining that you have to plug in a power cable to use a computer. Clearly your "expertise" in networking is... Well. Another misunderstanding.
> The tooling should let me wire up a container’s 'http' export to some consumer or to my local port 8000.
Ummmm... It does. It's called: Docker Compose, --network, or service discovery. You can use docker run -p 8000:80 or define a Docker network where containers resolve each other by name. You already don’t have to care about internal ports inside a proper Docker setup.
But you still need to map ports when exposing to the host because… Guess what? Your host machine isn't psychic. It doesn’t magically figure out that some random container process running an HTTP server needs to be accessible on a specific port. That’s why port mapping exists. But you already know this because "you understand TCP and UDP just fine".
> The internal number should be an implementation detail.
This hands-down the dumbest part of the argument. Ports are not just "implementation details." They're literally how services communicate. Inside the container, your app binds to a port (usually one) that it was explicitly configured to use.
If an app inside a container is listening on port 5000, but you want to access it on port 8000, you must declare that mapping (-p 8000:5000). Otherwise, how the hell is Docker (or anyone) supposed to know what port to use? According to you - the software should magically resolve this. And guess what? You don’t have to expose ports if you don’t need to. Just connect containers via a shared network which happens automagically via container name resolution within Docker networking.
Saying ports should be an "implementation detail" is like saying street addresses should be an implementation detail when mailing a letter. You need an address so people know where to send things. I'm sure you get all sorts of riled up when you need to put an address on a blank envelope because the mail should just know... Right? o_O