DEV Community

Ryan Giggs
Ryan Giggs

Posted on

Containers: The Building Block of Cloud Native Architecture

How a decades-old Linux idea became the foundation of modern software deployment and why containers solved the problems that VMs couldn't.

Based on cloud native curriculum from the @Linux Foundation | CNCF


The Problem Containers Were Built to Solve

Before containers, deploying even a simple Python web application was a multi-layered coordination exercise. You had to align the operating system configuration, the language runtime, extensions, system libraries, networking rules, and third-party connections like databases, all across development, staging, and production environments. One environment ran Python 3.9; another ran 3.11. A library worked on the developer's machine and failed in production. Packages installed globally on one server conflicted with another application running beside it.

This created significant operational friction between developers, who wrote and tested code in their own environments, and sysadmins, who managed the infrastructure those apps ultimately ran on. Deployment became error-prone. Debugging production issues was painful. Maintaining consistency across servers was a manual, fragile effort.

Resource inefficiency compounded the problem. To get reproducible, isolated environments, organizations turned to Virtual Machines (VMs). A VM emulates an entire physical server, including its own operating system and kernel. This worked, but running dozens or hundreds of VMs meant each carried the full overhead of a guest OS: gigabytes of memory and disk per instance, slow boot times, and rising infrastructure costs.

Containers attacked both problems simultaneously.


What Is a Container?

A container bundles an application together with all its dependencies (runtimes, libraries, configuration files, environment variables) into a single portable unit. The environment a developer tests in is exactly the same environment that runs in production. Sysadmins no longer need to manually reconstruct the right conditions on each server. The container carries those conditions with it.

The key resource efficiency breakthrough is this: containers share the host machine's kernel rather than running their own. This eliminates the full overhead of a guest OS per instance. Containers are lighter and faster than VMs, making it practical to run many isolated environments on the same physical hardware, delivering the isolation benefits of VMs without the cost.

A container is not a lightweight VM. It is an isolated process running on a shared host kernel, with its view of the system restricted by Linux kernel features.


A Brief History of Container Technology

Container concepts are older than most people realize.

1979 - chroot: The earliest predecessor was the chroot command, introduced in Unix Version 7. It allowed users to isolate a process from the main filesystem by creating a chroot jail, a restricted environment that simulated a new root directory. Within this jail, a process could only access files in its own assigned directory, effectively isolating it from the rest of the system while the files remained physically present on disk. Chroot directories can be created at various points in the filesystem hierarchy.

2006 - cgroups: Google engineers built "process containers" for internal resource isolation, which were contributed to the Linux kernel and renamed control groups (cgroups). Merged in Linux 2.6.24 in 2008, cgroups gave the kernel the ability to limit and account for the resource usage of process groups, the foundation of what containers rely on today.

2008 - LXC (Linux Containers): LXC was the first practical container runtime, combining cgroups and namespaces into a usable tool. It was powerful but complex to operate, with no image standard and a difficult CLI.

2013 - Docker: Solomon Hykes and the dotCloud team wrapped LXC (and later their own libcontainer) in a developer-friendly interface. Docker's key innovations were images: portable, layered snapshots of a container's filesystem, and a registry (Docker Hub) to distribute them. For the first time, developers could truly "ship their environment."

2015 - OCI and CNCF: As the ecosystem grew and multiple container formats emerged, industry leaders formed the Open Container Initiative (OCI), co-founded by Docker, CoreOS, and the Linux Foundation, to standardize the container image and runtime specifications. The same year, the CNCF was founded to promote containers and cloud native technologies across the ecosystem. Docker donated its runtime as runc, the reference implementation of the OCI runtime spec. containerd was later split out as the higher-level container lifecycle manager, and is now the standard runtime in production Kubernetes clusters.


How Containers Work: The Linux Kernel Primitives

Modern containers rest on two core Linux kernel features: namespaces and cgroups. These are not Docker inventions; they are kernel primitives that Docker and other runtimes made accessible.

Namespaces: Isolation

Namespaces give each container its own isolated view of the system. A process inside a namespace cannot see or interfere with resources outside it. The Linux kernel currently provides seven namespaces:

pid - Process IDs. Each container gets its own process ID space, so the first process inside a container sees itself as PID 1, regardless of what's running on the host.

net - Network. Each container gets its own network stack: its own IP addresses, routing tables, firewall rules, and ports. Two containers can both listen on port 8080 without conflicting.

mnt - Mount points. Abstracts the filesystem view and manages which directories and filesystems a container can see.

ipc - Interprocess communication. Provides separation of named shared memory segments between containers.

user - User and group IDs. Gives containers their own set of user and group IDs, enabling rootless containers where a process runs as root inside the container but as an unprivileged user on the host.

uts - Unix Time Sharing. Allows each container to have its own hostname and domain name, independent of the host.

time - The newest namespace. Can be used to virtualize the clock of a system, useful for testing or sandboxing time-sensitive workloads.

cgroups: Resource Control

While namespaces control what a container can see, cgroups (control groups) control what it can consume. They limit and enforce how much CPU, memory, disk I/O, and network bandwidth a container is allowed to use. This prevents a single misbehaving container from starving others on the same host, a critical guarantee in multi-tenant environments.

Modern Linux systems use cgroups v2, which provides a unified hierarchy and better support for container runtimes like containerd.

OverlayFS: Efficient Layered Filesystems

A third important primitive is OverlayFS, which enables Docker's layered image model. Each Docker image is composed of read-only layers stacked on top of each other. When a container runs, a thin writable layer is added on top. Changes are written to this layer only, while the base image layers are shared across all containers that use them, dramatically reducing disk usage and speeding up image pulls.


Containers vs. Virtual Machines: The Real Difference

The fundamental distinction is clear once you understand the kernel.

A VM emulates an entire physical computer. It runs its own full operating system, kernel included, on top of a hypervisor. This provides strong isolation but carries substantial overhead: each VM needs gigabytes of memory just for the OS, and takes minutes to boot.

A container is just an isolated process on the host. It shares the host's kernel directly. There is no hypervisor layer, no guest OS, no boot sequence. A container starts in milliseconds.

This shared-kernel model is also where the security boundary matters: if there is a kernel vulnerability, it potentially affects all containers on that host. For high-security multi-tenant workloads like AWS Lambda or Google Cloud Run, providers actually run containers inside microVMs (using tools like Firecracker or gVisor) to get both the speed of containers and the stronger isolation of VMs.


The Container Runtime Stack

When you run docker run, several layers of software are involved:

Docker CLI / Kubernetes sends a request to containerd (the high-level container lifecycle manager), which calls containerd-shim (one per container), which calls runc (the low-level OCI runtime that actually creates the container using namespaces and cgroups).

Understanding this stack matters because Kubernetes dropped direct Docker support in 1.24, moving to containerd as the standard runtime via the Container Runtime Interface (CRI). Docker is still a perfectly valid developer tool, but in production Kubernetes clusters, containerd is what's running under the hood.


Why This Matters for Cloud Native Development

Containers are the unit of deployment in cloud native systems. Everything built on top, including Kubernetes, service meshes, CI/CD pipelines, and serverless platforms, assumes containers as the baseline.

Understanding what's inside a container (namespaces, cgroups, OverlayFS) makes you a better engineer. You can debug resource contention issues, reason about security boundaries, understand why two containers can share a port without conflict, and make informed decisions about when to use containers versus microVMs for sensitive workloads.

The ecosystem has matured significantly: OCI standards mean container images built with Docker run on containerd, Podman, or any OCI-compliant runtime without modification. The "build once, run anywhere" promise that Docker introduced in 2013 is now genuinely delivered by an open, standardized stack.


Learnings from the Linux Foundation's cloud native curriculum.

Top comments (0)