Linux Containers: what they are and why all modern software is packaged in containers
https://cloudificationgmbh.blogspot.com/2025/04/linux-containers-what-they-are-and-why.html
Linux Containers: what they are and why all modern software is packaged in containers
In today’s IT world, containers are far beyond the stage of buzzword – they are the foundation of how modern software is built, shipped, and operated. Whether you’re browsing your favorite app, processing transactions in a banking platform, or managing infrastructure in the cloud, there’s a very good chance that Linux containers are quietly doing the heavy lifting in the background.
But what exactly is a container? Why has this technology become central to cloud computing, system engineering, DevOps, and modern software architectures? And how do platforms like Kubernetes and solutions like our very own c12n leverage containers to unlock new levels of efficiency?
In this article, we’ll dive deep into the world of Linux containers and discover how they work, why they matter, and the role they play in modern infrastructures.
Let’s unpack the container magic 🪄
What is a Linux container?
A container is a standardized unit of software that packages up code along with all the dependencies it needs to run – libraries, binaries, dependencies, configuration files, and more – into an isolated environment. Imagine you’re moving into a new house. You could throw your stuff loose into the truck, or you could pack everything into labeled boxes, sealed and stackable. Containers are those boxes, but for software. It is a form of operating-system-level virtualization that allows you to run multiple isolated applications on a single Linux kernel.
Containers are smaller and faster than traditional virtual machines (VMs), because they do not require a full guest operating system (OS). Instead, containers run as isolated processes on top of the host OS. They share the host system’s Linux kernel while remaining fully sandboxed from other processes which makes them incredibly efficient, portable, and perfect for cloud-native workloads.
You might remember from our previous blog post about hypervisors that traditional virtualization adds an extra layer called a hypervisor, which manages virtual machines and can be either Type 1 (bare-metal) or Type 2 (hosted). Like virtualization, containers rely on an extra layer of software to manage isolation, however, instead of a hypervisor, they use Linux kernel features like namespaces (which provide process and network isolation) and Control Groups (aka cgroups, which control resource allocation like CPU and memory) to achieve lightweight, process-level isolation.
Together, these mechanisms create lightweight, fast-starting environments that can be deployed consistently across different infrastructures – whether on a developer’s laptop, a testing environment, or a production cluster in the cloud. It works even if kernel versions or Linux distributions are different. The developer could run Fedora, and the test or production environment could be using Ubuntu.
TL/DR
A Linux container is a lightweight, standalone unit that bundles:
Your application
All its dependencies (libraries, binaries, etc.)
And containers share the host OS Linux kernel to run stuff.
The result? You can run your app anywhere – from a developer’s laptop to a cloud data center – without the “but it worked on my machine!” headache.
Why Containers Matter
Before containers, software was often deployed in a “works on my machine” style. Developers would build applications on their local machines with specific libraries or configurations, only to see them break in staging or production environments due to slight discrepancies. A common example would be that a developer would have a newer version of a required library locally where production runs an older version, which is incompatible. So, things work, unit tests pass, but production breaks right after new deployment.
Containers solve this by making environments portable and predictable. A container runs the same, no matter where you launch it. That consistency accelerates development cycles, simplifies testing, and greatly reduces deployment risks.
Moreover, containers are incredibly lightweight compared to VMs. They consume fewer resources because they share the host OS kernel and don’t need to run their own kernel at all. They launch in seconds, and are easy to duplicate (clone), scale, or discard. This efficiency translates directly into reduced infrastructure costs and faster delivery pipelines.
So... how do Linux containers actually work?
Linux containers create isolated environments for applications to run in, without needing a full operating system for each app. This is achieved by combining several key features built into the Linux kernel.
- Containers Use the Host’s Linux Kernel
Unlike virtual machines (VMs), which emulate hardware and run a separate OS, containers don’t need to bring their own operating system. They use the Linux kernel of the host machine but isolate everything else – filesystems, processes, networking, etc.
That’s what makes them fast and lightweight. You’re not booting up a whole OS for each container – you’re running isolated processes on the same kernel which has no idea that it runs in a container.
- Linux Namespaces: the Illusion of Separation?
Containers rely on Linux namespaces to make them feel like self-contained environments.
Namespaces create isolated views of:
Processes (PID namespace) – each container sees only its own processes with ids.
File systems (Mount namespace) – eProvides an isolated view of the file system for each container.
Networking (Net namespace) – containers can have their own virtual interfaces and IPs and are not aware of the underlying hardware NICs.
Users (User namespace) – containers can map users differently from the host and have their own UIDs (user ids).
IPC Namespace – Separates shared memory and message queues to prevent cross-container communication.
Mount Namespace – UTS Namespace – Lets containers set their own hostname and domain name.
Cgroup Namespace – Isolates access to cgroups, which control and limit system resource usage.
This feature gives containers the illusion that they’re completely running in their own separate OS, when in fact they’re sharing the host OS with 30 other containers.
- Control Groups (cgroups): Resource Management
Namespaces isolate, where cgroups control.
Control groups are another Linux kernel feature that lets you limit and monitor the resources of the host. Cgroups control how much CPU, memory, disk I/O, and network bandwidth a process inside a container can use. This ensures one container doesn’t take over the entire system resources.
What is considered a resource:
CPU time
RAM
Disk I/O
Network bandwidth
So if one container tries to eat all your RAM during a memory leak, cgroups can limit the “blast radius” and kill the process when going over a configured RAM limit (e.g. 1Gb) without letting the whole host OS freeze if RAM runs out.
Such functionality is essential in multi-tenant environments, or when you’re running lots of containers side-by-side. You don’t want one container hogging all the resources and impairing neighbor containers or the host OS itself.
The image below shows how the host CPU and RAM hierarchy has two groups with their own tasks. Tasks refer to the processes that are members of a specific control group. In the below example that is crond with its fork in case of /cg1 group and httpd in case of /cg2 group. We can also see that the httpd process is a part of /cg3 group on the right. The net_cls hierarchy allows tagging network packets with a class identifier, allowing to identify and manage traffic originating from specific cgroups. This handy mechanism provides us a way to manage network traffic based on the cgroup the process belongs to.
- Layered Storage
Containers typically use UnionFS (or OverlayFS), which stacks filesystem layers. This means they can share common filesystem layers (like the OS or libraries), while each container has its own read/write layer on top. This is essential as it allows sharing a base container image while providing each container with its own readable and (optionally) writable filesystem layer.
Here’s how it works:
A base image (for example, ubuntu:22.04) forms the bottom layer
Your app and its dependencies go on top of it in a new layer
Any changes during runtime are stored in a separate write layer
The write layer can be ephemeral and discarded automatically when container exits
Or, the write layer can be persistent if the data has to survive container restart
Such a layer-based model is super efficient because:
Base images can be shared between multiple containers
Only the differences need to be saved
Start-up times are fast as we don’t need to copy all data all over
That’s why when you first pull a container image from Docker Hub it might take a minute or two depending on your connection. But after, when you start another container with the same base image – it is already cached and started instantaneously! This saves space, makes containers faster to pull and deploy and allows to do version control via image tags.
- Container Runtimes: the engine that runs the show
To create, run, and manage containers, you’ll need a container runtime installed.
Some of the popular container runtime examples:
runc – the low-level container runtime (used by Podman, Docker, containerd, etc.)
containerd – a higher-level runtime used by Kubernetes
CRI-O – a lightweight alternative runtime optimized for Kubernetes
The runtime is the piece of software that actually uses Linux features (namespaces and cgroups) to spin up and manage containers under the hood. Sometimes, container runtimes are categorized as “high-level” and “low-level”, based on their abstraction and complexity levels. In a nutshell, high-level