Move an app from a dev machine to a test machine and a library goes missing; move it from test to production and the glibc version does not match — developers and ops teams both know the “it works on my machine” wrangling well. Containers are a packaging approach born to end exactly this wrangling: they box up the application together with its entire dependent environment, so it runs against the same stack wherever you move it. This post makes three things clear: what a container actually is, why it is lighter than a virtual machine, and when you should not use one.

First Analogy: The Standard Shipping Container

The word “container” literally means shipping container, and the Docker logo is a whale carrying one — the analogy is no coincidence.

Before the shipping container, loose cargo was loaded and unloaded by dockworkers piece by piece: barrels, sacks, irregularly shaped items; every change of transport mode (ship → train → truck) meant a fresh round of loading and unloading, and damage and loss were routine. Once the standard container took hold, everything changed: the boxes were uniform in size, the cargo inside no longer mattered, and ships, trains, and trucks all handled the same boxes with the same gear.

Containers do the same thing for software. The image is that standard box, holding application code, runtime, libraries, and configuration; the host machine, the CI pipeline, and cloud providers’ container services are the ships and trucks — as long as a machine can run containers, the app inside the box behaves identically, with no need to “load and unload” dependencies again for every environment.

Second Analogy: Villa and Apartment

This analogy explains where the “light” comes from.

A virtual machine is a detached villa. Every household brings its own foundation, plumbing, and boiler — a complete guest operating system — with each household’s system calls handled by its own kernel, fully independent; the cost is that every villa occupies a large plot and maintains a full set of equipment.

A container is an apartment. The whole building shares the foundation, plumbing, and boiler — the host kernel; each household has its own door locks, room layout, and furniture (filesystem, processes, network stack), cannot see inside each other’s units, but the pipes are shared.

Shared pipes have two direct consequences: apartments build fast and occupy little land — containers start in seconds and images are typically MB to a few hundred MB; when the pipes break, the whole building suffers — a kernel vulnerability affects every container. The fitness-boundary section later follows directly from this sentence.

What It Is

A container is a lightweight software package that bundles an application with all of its dependencies (code, runtime, system tools, libraries, configuration) into a standard image and runs it as an isolated process on the host kernel.

Unpacking the three keywords in that definition:

  • Packaging: the image is a read-only template, and a container is a running instance of an image. One image can spin up ten containers on a single machine without interfering with each other.
  • Isolation: provided by two Linux kernel mechanisms — namespaces define “what you can see”, and control groups (cgroups) limit “how much you can use”.
  • Shared kernel: containers emulate no hardware and carry no kernel of their own; all system calls from all containers are handled by the host kernel. This is the watershed between containers and virtual machines.

Containerization is not a new concept — OS-level virtualization existed decades ago. The real starting point of modern containerization was the 2013 release of Docker: it drastically lowered the barrier to using these underlying capabilities, and containers became the mainstream choice for developers and enterprises from then on.

How the Isolation Works

Namespaces manage the view, control groups manage the quota — both are pre-existing Linux kernel capabilities.

Namespaces determine what a container can see:

  • PID namespace: processes inside the container are numbered from 1, and cannot see the other processes on the host;
  • Mount namespace: the container has its own set of filesystem mount points;
  • Network namespace: an independent network interface, IP, ports, and routing table;
  • UTS / IPC / User namespaces: hostname, inter-process communication, user ID mapping.

Control groups (cgroups) determine how much a container can use: CPU quota, memory ceiling, IO bandwidth, process count limit. Without cgroups, a runaway process inside one container could eat all the host’s memory and drag its neighbors down with it.

These two layers of mechanism are no magic — one command reveals them: the bash started inside a container is process 1 (the PID namespace effect), and cgroups v2’s memory.max interface faithfully exposes the memory quota (max when unset):

Figure 1

Docker did not invent these two mechanisms; its contribution was wrapping them into a usable toolchain: a unified image format, a repeatable build process, and image registry distribution.

Containers vs Virtual Machines

Dimension Virtual Machine Container
Virtualization layer hardware-level, a Hypervisor emulates the full device set OS-level, shares the host kernel
Operating system a complete guest OS per instance only the application and dependencies are packaged
Startup speed minutes, a full boot sequence seconds, essentially starting a process
Image size GB scale MB to a few hundred MB
Isolation strength strong, hardware-level isolation weaker, a kernel vulnerability can affect all containers
Resource overhead each VM carries its own kernel and system-service memory shared kernel, the same machine runs more instances
Cross-kernel capability yes, a Linux host can run Windows VMs no, Linux containers need a Linux kernel

One-sentence conclusion: virtual machines isolate more strongly but weigh more; containers start faster, run denser, and isolate more weakly. The selection question is not “which is more advanced” but “how much isolation do I need, and how much overhead can I tolerate”.

How to Use It: Docker in Four Steps

On the operations side, Docker’s standard flow is four steps: write a Dockerfile (a text description of how the image is built layer by layer: base image, copy code, install dependencies, start command — the file itself is versionable) → build the image → run the container → manage containers.

1
2
3
4
docker build -t myapp:1.0 .          # -t sets the image name and tag; . is the current directory as build context
docker run -d -p 8080:80 myapp:1.0 # -d runs in background, -p maps host port 8080 to container port 80
docker ps -a # list containers, -a includes exited ones
docker stop <container> && docker rm <container>

An image is immutable once built — this is the key to consistency: the same image produces the same result on a dev machine, in CI, and in production, with the only differences coming from externally injected configuration and environment variables.

The Ecosystem: One Machine Solved, How About a Fleet

  • Kubernetes: an open-source platform for automated deployment, scaling, and management of containerized applications, solving “hundreds of containers across dozens of machines — who schedules them, who restarts the crashed ones, how traffic gets distributed”.
  • Helm: Kubernetes’ package manager, describing an entire application manifest with Charts and installing it with one command.
  • OpenShift: an enterprise platform built on Kubernetes, rounding out permissions, image registry, CI/CD, and other out-of-the-box capabilities.

When do you actually need Kubernetes? If a single machine plus Docker Compose can keep things under control, don’t rush in; adopt it once cross-machine scheduling, autoscaling, and self-healing become hard requirements.

Three Well-Trodden Use Cases

Microservices: monolithic applications struggle to support rapid iteration and high availability; microservices split the application into small, single-responsibility services that are developed, deployed, and scaled independently — and containers are exactly their runtime. An e-commerce site splits its system into user, order, and inventory services, each running in its own container, updated and scaled individually without dragging down the whole system.

DevOps and CI/CD: development, test, and production all run the same image, eliminating environment drift; every code change goes through the pipeline to build the image, run tests, and ship, sharply reducing problems caused by manual intervention and environment mismatch.

Multi-tenant isolation: on a cloud platform, different users’ workloads share the same physical resources; containers use namespaces and cgroups to encapsulate each customer’s applications independently, non-interfering, with resources kept separate.

Fitness Boundary: When Not to Use Containers

Containers are not a universal answer; the following needs are either impossible or uneconomical for them by nature.

  • Needing a different kernel: Windows applications, loading specific kernel modules, or custom kernel parameters — a container can only use the host kernel and cannot do any of this. Put these workloads on VMs.
  • Strong-isolation compliance requirements: finance, government, and other settings that mandate hardware-level tenant isolation — shared-kernel isolation is not strong enough. Either run containers inside VMs (a VM layer underneath as a backstop), or consider strongly-isolated runtime solutions like gVisor or Kata Containers.
  • Stateful, IO-heavy core databases: containers are stateless, and data has to be mounted in via volumes. It can be done, but backups, failover, and disk IO performance all need extra design — a physical machine or a dedicated VM is less hassle.
  • GUI applications: there is no display server inside a container; X11/Wayland require extra forwarding and the payoff is not worth it.

Conversely, what containers are best at: stateless services or services with externalized state, frequently released applications, workloads that must stay consistent across many environments, batch and CI jobs.

Three Problems Still Unsolved

  • Storage and data management: containers are stateless, so persistence requires volumes, and the volumes’ lifecycle, backups, and cross-node sharing all need separate design.
  • Network configuration and management: once microservices multiply, service-to-service communication, service discovery, and cross-host networking become their own discipline, usually managed with CNI plugins plus a service mesh.
  • Security: kernel-level vulnerabilities can affect all containers; images can also arrive carrying vulnerable dependencies, so image scanning and minimal base images are the safety net.

Where It Goes Next

Containers plus Serverless (hand over only code or an image, scaling is the platform’s job), edge computing (small images, fast startup — well suited to resource-constrained edge nodes), and intelligent orchestration that hands scheduling decisions to machine learning.

Summary

Back to the shipping-container analogy at the start: containers pack the application and its entire runtime environment into a standard box that behaves identically wherever it is moved — that is their core value.

  • The root reason containers are light is the shared host kernel, with namespaces isolating the view and cgroups limiting usage; virtual machines emulate the full hardware stack and every instance carries its own operating system.
  • The shared kernel is both the source of efficiency and the ceiling of isolation strength: a kernel vulnerability’s blast radius covers every container, and strong-isolation compliance scenarios either use VMs or run containers inside VMs.
  • Docker solves “how to run on one machine”; Kubernetes solves “how to manage a fleet of containers across machines”.
  • Cross-kernel needs, stateful core databases, and GUI applications are not suited to direct containerization.

References

  1. “What is a Container?”, Docker Official website. Link
  2. “Containers and Virtual Machines”, Red Hat Official website. Link
  3. “Kubernetes”, Kubernetes Official website. Link
  4. “Introduction to DevOps on AWS”, AWS Whitepapers. Link
  5. “Microservices”, Martin Fowler’s Blog. Link

This article was rewritten from the author’s CSDN blog posts originally published between 2020 and 2024.