
Most people understand containers from a user perspective:
Create a Dockerfile, build it with docker build, and once we have the image, I run it.
But to be a better DevOps engineer and Kubernetes user, we really need to understand how containers actually work. This knowledge is something many Kubernetes users lack. Once we understand the basics - Linux → Containers → Kubernetes - everything starts to make sense.
So, what's the big deal with containers?
Well, there are plenty of blog posts that explain the whole history, from virtual machines (VMs) to lightweight containers. I'll keep it simple because this is not a history lesson - it's a pragmatic post about how containers work.
The thing is:
- VMs are great, but heavy.
- A VM simulates a full operating system, which requires a lot of resources and storage.
- Even though VMs are "contained," they are hard to ship, start up, shut down, build, and run efficiently.
We needed a better solution - something that, instead of emulating an entire OS, could use the host OS directly and provide only the minimum environment needed to run an application.
Additionally, we needed:
- Process isolation - each container should run independently.
- Resource control - we must limit how much CPU, memory, and I/O each containerized process can consume.
And that's where containers come in.
Here's a corrected and polished version of your next section, keeping it clear and blog-ready while improving grammar, flow, and readability:
How Containers Achieve This:
Okay, let's break it down with a simple example.
Imagine we are running Ubuntu on our machine.
The first thing to consider is:
What can we reuse from the host, and how do we avoid duplication?
1. Reusing the Kernel
The part of the operating system that talks directly to the hardware is the Linux kernel.
- The kernel handles memory, CPU scheduling, I/O, and networking.
- It's shared across all containers running on the host.
- This means we don't need to package or ship the kernel inside our container.
Sharing the kernel is the key reason containers are so lightweight compared to virtual machines.
2. What's Not Shared
So, what isn't shared between the host and the container?
- User-space components like system libraries, utilities, and shell tools.
- Application dependencies (e.g., Python, Node.js, Java runtimes).
- Your application code itself.
Each container packages just enough of these components to run the application reliably, without affecting other containers or the host system.
3. How does this work
When you run a container (like a Docker container), it appears as a small, isolated filesystem that contains everything your application needs to run. Here's a detailed breakdown of what a container looks like in the filesystem and how dependencies are brought in:
Container Filesystem Structure
Inside a running container, the filesystem typically looks like a minimal Linux environment:
/
├── bin/ # Essential user binaries (ls, cat, sh)
├── sbin/ # System binaries
├── usr/ # User programs and libraries
├── lib/ # Shared libraries
├── lib64/ # 64-bit shared libraries
├── etc/ # Configuration files
├── var/ # Logs, temporary state
├── tmp/ # Temporary files
├── home/ # Optional, user home directories
└── app/ # (Optional) Your application code
However, containers don't have a full OS - they share the host kernel. What they provide is just the user-space filesystem needed to run the application.
4. How Dependencies Are Included
Containers bundle dependencies through layers that come from a Docker image:
1.Base Image
- Example: alpine, ubuntu, debian
- Provides the core Linux filesystem and essential libraries.
2.Application Dependencies
Installed via package managers or language-specific tools. For example:
- apt-get install (Debian/Ubuntu)
- yum install (CentOS)
- pip install (Python)
- npm install (Node.js)
- These are stored in the filesystem of the container (like /usr/local/lib).
3.Your Application Code
Copied into the container, often in /app or /usr/src/app.
4.Additional Layers
Each command in a Dockerfile (like RUN, COPY) creates a new layer.
Layers are read-only, and the container runtime uses a UnionFS (overlay filesystem) to merge them.
Example Dockerfile:
FROM python:3.12-slim # Base image (OS + Python runtime)
WORKDIR /app # Set working directory
COPY requirements.txt .
RUN pip install -r requirements.txt # Install dependencies
COPY . . # Copy app code
CMD ["python", "app.py"] # Start the app
This results in a layered filesystem:
- Python slim base - Has Debian + Python
- Pip dependencies - Installs all Python packages
- Your code - Adds your application
5. How Dependencies Are Store and how we optimize by using Layer.
On a Linux host, Docker usually stores all container layers and files under:
/var/lib/docker/
Inside this directory:
/var/lib/docker/
├── overlay2/ # Container & image layer storage (OverlayFS)
├── containers/ # Metadata and logs for each container
├── image/ # Metadata for images
└── volumes/ # Named volumes for persistent data
- overlay2/ (or aufs/btrfs/zfs depending on storage driver)
- Contains the actual layered filesystem used for containers.
- Each image layer and container writable layer is stored here.
OverlayFS Represents Container Filesystem:
Docker uses OverlayFS (UnionFS) to combine:
- Read-only layers - From the image (base OS + dependencies)
- Writable layer - For the running container's changes
Path example for a running container:
/var/lib/docker/overlay2/<layer-id>/
├── diff/ # The files for this layer
├── merged/ # The combined view (what the container sees)
└── work/ # Used internally by OverlayFS
When you docker exec into a container, you are looking at the merged directory in OverlayFS.
An why is that and why we use a multi layered architecture?
1.Avoid Duplication
- If multiple containers use the same base image (e.g., python:3.12-slim), the base layer is downloaded once and shared across all containers.
- Only the top writable layer is unique per container.
2.Efficient Downloads
- Docker images are pulled in layers. If a layer already exists on your host, Docker reuses it instead of downloading it again.
- Example: If two apps share the same Ubuntu base layer, only the app-specific layers are downloaded.
3.Faster Builds & Caching
Each Dockerfile instruction creates a new layer.
Docker caches these layers, so rebuilding the image is faster if earlier layers haven't changed.
4.Efficient Storage
Since layers are read-only and shared, one 200MB base image can be used by 50 containers without consuming 50×200MB.
One of the big advantages of containers is reusability and maintainability.
That's why containers are architected the way they are.
At this point, we have everything we need to run a process in a container.
But we are still missing two critical pieces:
- Isolation - How do we make sure containers run separately without interfering with each other?
- Resource management - How do we ensure that one container doesn't consume all the host's CPU, memory, or I/O and affect others?
Linux provides two core kernel features to achieve this: Namespaces and cgroups (control groups).
6. Namespaces - Process Isolation
Namespaces create the illusion that each container has its own operating system by isolating kernel resources.
Key namespaces used by Docker:
pid Isolates Process IDs (each container has its own PID 1)
net Isolates Network interfaces, IPs, and routing tables
mnt Isolates Mount points and filesystemsutsHostname and domain name
ipc Isolates Inter-process communication (shared memory, semaphores)
user Isolates User and group IDs, allowing rootless containers
Effect:
- A process inside a container only sees its own processes, network, and filesystem.
- Containers cannot see or affect other containers or the host directly.
8.Cgroups - Resource Management.
Control groups (cgroups) allow Docker to limit and allocate system resources per container:
- CPU quotas - Limit CPU cores or shares.
- Memory limits - Restrict RAM usage to prevent one container from consuming all memory.
- I/O throttling - Control disk and network I/O.
- Process count limits - Prevent fork bombs inside a container.
Effect:
- Each container only consumes its allocated resources.
- The host remains stable even under heavy container workloads.
9.How Docker Uses These to Run a Process
When you run a container:
- Docker creates namespaces for the process → isolates its view of the system.
- Docker applies cgroup rules → restricts its CPU, memory, and I/O usage these configurations appear in Kubernetes under request and limit for a pod or pod template.
- Docker mounts the layered filesystem via OverlayFS → provides the container's root filesystem.
- Docker starts the process as PID 1 inside that isolated environment.
From the container's perspective, it feels like a full, independent OS, but it is actually just a single isolated process on the host.
So I hope this gives you a more in depths picture of how a container works, how Docker makes it as efficient and reusable as possible, but always remember that the isolation is a bit of a fantasy and miss configurations can give you access to the host or the host file system.