Containers won.

They escaped the development machine, ate the server, and became the unit of cloud computing. We put our programs in containers, put the containers in pods, put the pods in clusters, and hired entire teams to keep the clusters alive.

It was an impressive victory.

It is also over.

Containers solved a real problem. Software needed a repeatable way to carry its dependencies from one machine to another. A filesystem archive, some metadata, namespaces, and cgroups were a practical answer.

Then we kept going.

We took a packaging format and asked it to become a security boundary. We took a process and asked it to pretend to be a machine. We built registries, image builders, layer caches, schedulers, sidecars, service meshes, policy engines, and several careers around maintaining the illusion.

The container is not the application anymore. It is the paperwork surrounding the application.

Containers Are Not Machines

A container shares the host kernel.

This is the useful thing about containers. It is also the dangerous thing about containers.

Namespaces can hide processes, filesystems, users, and networks from each other. Cgroups can account for and limit resources. Seccomp can reduce the system calls a process is allowed to make. These are good tools. They make processes easier to organize.

They do not turn one kernel into many kernels.

When the workload is trusted, this distinction may not matter. When the workload is an autonomous agent installing packages, running build scripts, opening browsers, executing generated code, and exploring an unfamiliar repository, it matters quite a lot.

We can harden a container. We can run it without root, narrow its capabilities, filter its system calls, scan its image, sign its provenance, restrict its network, and wrap it in enough policy that we eventually feel comfortable letting it execute arbitrary code.

Or we can use a virtual machine.

Hardware virtualization gives each workload its own kernel. A compromised guest still has a serious boundary to cross before it reaches the host. This is not perfect security. Nothing is. But it begins with the isolation model we actually need instead of asking a collection of process-management features to impersonate it.

The industry knows this. That is why the strongest container platforms increasingly put containers inside microVMs.

Read that sentence again. We added a machine underneath the container to make the container safe.

Once the machine is already there, what is the container for?

The Cheap Process With an Expensive Entourage

A container process is cheap. Container advocates are right about this, and arguing otherwise misses the point.

The expensive part is everything we built around it.

Before the program can run, somebody has to write a Dockerfile. The build client sends a context. The builder resolves a base image, executes a sequence of filesystem mutations, hashes the results, and stores them as layers. The image is tagged and pushed to a registry. The server pulls the manifest, downloads the missing layers, extracts them, assembles a root filesystem, configures a runtime, and finally starts the process we wanted to run in the first place.

Then we keep a daemon around to supervise the whole arrangement.

This is sensible when the artifact must run across a large, heterogeneous fleet owned by different people. It is absurd when the platform already controls the machine, the operating system, the scheduler, and the workload lifecycle.

At Clusterbase, we already isolate persistent workloads in Firecracker microVMs. Our first implementation still ran Docker inside those VMs. It looked like this:

microVM -> Linux -> dockerd -> containerd -> runc -> application

The microVM was the security boundary. The rest was duplication.

So we removed it for native workloads. A compiled program now runs as a normal supervised process inside the guest:

microVM -> Linux -> application

No Docker daemon. No image layers. No private registry required just to move one binary from the build to the machine. Fewer moving parts, less memory sitting idle, and less time spent unpacking a filesystem before the application can begin.

A process should be allowed to be a process.

Dockerfiles Turn Waiting Into Architecture

The Dockerfile is often praised because it makes builds reproducible.

Sometimes it does. It also makes a cache invalidation puzzle part of the application architecture.

Move one instruction and a useful layer becomes useless. Change one lockfile and the dependency layer gets rebuilt. Use a large build context and pay to inspect or transfer files that the compiler will never read. Build for a different CPU and discover that the local cache, remote cache, and registry all have opinions about it.

The response has been to build better caching systems around Docker. We cache layers locally, export them remotely, import them into CI, mount special caches during builds, and carefully reorder commands to appease the hash function.

This is not a build system. It is a build system trapped inside a serialization format.

I know this all too well. I manage hundreds of production services—and the servers they run on—in real data centers, and I run a small data center in my basement. When a build loses the wrong cache, the old Haswell CPUs in my basement start screaming as they rebuild layers full of software we already built five minutes ago. The struggle is not theoretical. I can hear it through the floor.

Compilers already know how to compile programs. Package managers already know how to fetch and verify dependencies. Build tools already know their dependency graphs. We should let them do their jobs inside a prepared machine, preserve the useful caches, and collect the resulting artifact.

Our build machines start from Firecracker snapshots with the toolchains already installed and common dependencies already warm. Their disks are copy-on-write clones. A build gets a real Linux machine, runs the repository's actual commands, produces an artifact, and disappears when the work is done.

There can still be a container image at the end when someone actually needs one. It is an output format, not the universe in which the build must live.

Agents Need Computers

Containers were designed for an era when a human prepared an application and a scheduler ran it.

Agents change the shape of the workload.

An agent does not merely execute one known process. It reads a repository, installs tools, edits files, runs tests, starts servers, opens a browser, watches logs, changes its mind, and tries again. It may work for minutes or days. Its useful state includes the working tree, compiler caches, package caches, background processes, credentials, shell history, and everything it learned about the environment by touching it.

In other words, an agent needs a computer.

We can force that computer into an immutable image pipeline. Every meaningful change can become a rebuild, a push, a pull, and a restart. Every missing tool can become a Dockerfile edit. Every long-lived bit of state can be pushed into another service because the container itself is supposed to be disposable.

Or we can give the agent a machine.

The machine can be created from a known snapshot, strongly isolated, allowed to change, paused when idle, resumed with its state intact, and destroyed when the job is complete. We can make machines as disposable as containers without pretending that their filesystems must be immutable while they are alive.

This is the important inversion.

The old cloud made machines long-lived and containers disposable.

The agentic cloud makes machines disposable and the work inside them durable.

Two Primitives Are Enough

Not every program needs a machine.

Small request handlers benefit from an even smaller abstraction: a language isolate. This blog runs on Clusterbase's serverless runtime in a V8 isolate. The request that served this page did not need to boot Linux, start a container daemon, or pull an image. It needed JavaScript, a bounded execution environment, and the files in this deployment.

For a web handler, use an isolate.

For an agent, a database, a build, or a persistent service, use a microVM.

One is lighter than a container. The other is more isolated than a container. Together they cover the workloads we kept trying to squeeze through the same tarball-shaped hole.

Containers will not vanish tomorrow. There are too many registries, deployment systems, and useful images for that. We will support them when they are the artifact someone already has or the compatibility format a workflow requires.

But compatibility is not destiny.

The container has lost its place as the default abstraction for compute. It is too weak where we need a security boundary, too elaborate where we only need a process, and fundamentally mismatched with software agents that need a real place to work.

Containers won the last era of cloud computing.

Let them retire with dignity.