Containers and orchestration
Docker, Kubernetes, and an honest answer about when you need neither.
What Docker actually solves
beginnerA container is a process on the host kernel with a restricted view of the world. Two Linux features do the work: namespaces, which decide what a process can see, its own process table, network stack, mount table and hostname, and cgroups, which decide what it can use, its share of CPU, memory and IO. There is no virtual machine and no second kernel, which is why it starts in milliseconds.
The problem it solves is environment drift. Before containers, works on my machine was a real and expensive category of bug, because the machine included a particular library version, a particular locale, a particular environment variable set by someone in 2019. An image is the environment, so the same artefact runs identically on a laptop, in CI and in production, and the failure mode moves from mysterious to reproducible.
A virtual machine gives stronger isolation by running its own kernel, at the cost of size and startup time. That distinction matters for security: a container escape is a kernel exploit away, which is why untrusted multi-tenant workloads use VMs, or lightweight VMs such as Firecracker that keep the isolation while approaching container startup times.
The other half of the value is immutability. A container is replaced rather than updated, so there is no configuration drift accumulating on long-lived servers and no question of what has been done to a machine since it was provisioned. That property is what makes rollbacks trivial: the previous image still exists and still runs exactly as it did.
What containers do not solve is worth stating plainly. They are not a security boundary strong enough to run untrusted code, they do not make a stateful service stateless, and they do not remove the need to think about how data outlives the process. A database in a container is a database with a storage question, not a solved problem. So the argument is reproducibility first and density second. If the deployment already produces one identical artefact, a static binary, a zip for a serverless runtime, the reproducibility case is weaker and containers buy less, which is worth acknowledging before adopting the ecosystem that comes with them.
Why do multi-tenant platforms run untrusted code in VMs rather than containers?
Evidence · 1 source
- 01Firecracker: lightweight virtualisation for serverless applications (NSDI 2020)
That multi-tenant untrusted workloads on Lambda and similar platforms run in microVMs rather than shared-kernel containers, because a container escape is a kernel exploit away.
Answers are generated and can be wrong. The topic above is the reviewed version.
Docker or Kubernetes, and why
intermediateDocker builds and runs containers on one machine. Kubernetes schedules them across many: restarting what fails, rolling out new versions, scaling on demand, routing traffic to whatever is currently healthy. They are not alternatives, and the question that matters is not which to choose but whether an orchestrator is needed at all.
What Kubernetes genuinely gives you is a declarative control loop. You describe the desired state and controllers work continuously to make reality match it, which is why a killed pod comes back and a failed node's work moves elsewhere without anyone acting. That model is the reason it won, and it is genuinely powerful when there is a fleet to manage.
The weight is equally real. Networking with its own model, ingress, RBAC, storage classes, cluster upgrades on a cadence you do not control, and an API surface large enough that expertise in it is a full-time skill. Debugging spans several layers, each with its own failure modes, and the mean time to understand an incident goes up before it comes down.
The middle option is the one most teams skip. A managed container platform, Cloud Run, ECS Fargate, App Runner, takes the same image and runs it with autoscaling, rollouts and health checking while handing you almost none of a cluster. For a handful of services with similar shapes, that is most of the benefit for a small fraction of the cost, and it covers a great many systems that reached past it.
The honest signals that Kubernetes is right: many services with different scaling profiles that benefit from bin-packing, a platform team standardising across several product teams, workloads that need scheduling features such as affinity or GPUs, or a multi-cloud requirement that is real rather than aspirational. One service and three engineers is not that list.
The cost that decides it is people rather than money. A cluster needs somebody who understands it when it misbehaves at three in the morning, and if that person is also the only backend engineer, the orchestrator has been bought with the time that was going into the product.
You run three services with modest, similar traffic. What is the strongest argument against Kubernetes?
Answers are generated and can be wrong. The topic above is the reviewed version.
Image size, layers and security
intermediateImages are built as layers and each layer is cached by the instruction that produced it and everything above it. Ordering a Dockerfile so dependency installation happens before source is copied means a code change rebuilds one small layer rather than reinstalling every package, which is the highest-leverage build optimisation available and costs nothing but attention. Multi-stage builds are the second. Compile in an image with the full toolchain, then copy only the artefact into a minimal runtime image, and hundreds of megabytes of compilers, headers and package caches never ship. The build stays convenient and the thing you deploy is small, which shortens every pull on every node on every deploy.
Smaller is also safer, because most of a large base image is packages you never call and every one of them can carry a vulnerability that a scanner will report and someone will have to triage. Distroless images contain your application and its runtime and not a shell, which removes an entire category of post-exploitation. Alpine is small for a different reason, musl rather than glibc, which is occasionally a compatibility problem worth knowing about in advance.
Secrets must not end up in layers. A file copied in and deleted in a later instruction is still present in the earlier layer and still readable by anyone with the image, which is a recurring way credentials leak. Build secrets exist for this, mounted for one instruction and never committed to a layer, and they are worth learning before the alternative teaches you.
Tags deserve more care than they get. Deploying latest means the image you tested and the image that runs are related only by hope, and a rollback has nothing to point at. Tag with the commit, or better with the image digest, so a deployment names exactly one artefact and can be reproduced next year.
Two habits complete the picture: run as a non-root user, because the default is root and a container escape from root is a much better day for an attacker, and scan images in the pipeline so a known vulnerability is a build result rather than a discovery.
A credential is copied into an image and deleted in a later instruction. Is it gone?
Answers are generated and can be wrong. The topic above is the reviewed version.