Post

Linux Containerization Deep Dive: Beyond Docker and runC

Take a deep dive into Linux containerization technologies beyond just Docker and runC. Explore alternative runtimes (CRI-O, containerd), container orchestration without Kubernetes

Linux Containerization Deep Dive: Beyond Docker and runC

Linux Containerization Deep Dive: Beyond Docker and runC

When developers and operations engineers think of containers, Docker is often the first name that comes to mind. It popularized Linux containers and made them accessible. However, the container ecosystem is a rich, layered landscape of specifications, runtimes, and tools. Understanding what lies beneath Docker and its default runtime, runC, is crucial for building resilient, secure, and performant systems.

This article peels back the layers of the container stack. We’ll explore the kernel features that make it all possible, dissect the different runtimes, and look at powerful container management tools that operate without a central daemon or a full-blown Kubernetes cluster.

What You’ll Get

  • Kernel-Level Insight: A clear breakdown of the Linux kernel features—namespaces, cgroups, and seccomp—that are the true foundation of container isolation.
  • Runtime Alternatives: An exploration of high-level runtimes like containerd and CRI-O, and low-level runtimes like crun.
  • Daemonless Management: An introduction to powerful, Kubernetes-free tools like Podman and systemd-nspawn.
  • Standards Explained: Understanding the roles of the Open Container Initiative (OCI) and the Container Runtime Interface (CRI).
  • Practical Comparisons: A look at the security and performance trade-offs between different approaches.

The Kernel’s Magic: The Real Foundation of Containers

Containers aren’t virtual machines; they are isolated processes running on a shared Linux kernel. This isolation is a clever illusion created by several powerful kernel features working in concert.

Namespaces: The Illusion of Isolation

Namespaces are the primary feature for isolating a container’s view of the system. A process inside a namespace can only see resources associated with that same namespace. Linux provides several types:

  • PID (Process ID): Isolates the process tree. A container can’t see or signal processes outside its PID namespace. The first process in the namespace gets PID 1.
  • NET (Network): Provides an isolated network stack with its own private network devices, IP addresses, routing tables, and firewall rules.
  • MNT (Mount): Isolates filesystem mount points. Processes in different mount namespaces can have different views of the filesystem hierarchy.
  • UTS (UNIX Timesharing System): Isolates the hostname and domain name.
  • IPC (Inter-Process Communication): Isolates System V IPC and POSIX message queues.
  • User: Isolates user and group IDs, enabling a process to have root privileges inside the container but be an unprivileged user on the host. This is the cornerstone of rootless containers.
  • Cgroup: Isolates the cgroup view, preventing a container from seeing the host’s cgroup hierarchy.

Control Groups (cgroups): Resource Management

While namespaces provide isolation, control groups (cgroups) provide resource accounting and limiting. They ensure a single container can’t exhaust host resources like CPU, memory, or I/O.

Cgroups allow you to:

  • Limit memory usage: Prevent a container from consuming all available RAM.
  • Set CPU priority: Allocate CPU shares to containers, ensuring critical workloads get processing time.
  • Throttle I/O: Limit the read/write speed to block devices.
  • Restrict device access: Control which devices a container can access (e.g., /dev/sda).

Modern systems are migrating to cgroups v2, which offers a cleaner, more unified hierarchy and more predictable behavior than the legacy v1.

Seccomp & Capabilities: Hardening the Sandbox

To further secure the container, we need to limit what it can ask the kernel to do.

  • Seccomp (Secure Computing Mode): This acts as a filter for system calls (syscalls). A seccomp profile defines a whitelist of allowed syscalls, and any attempt by the container to use a forbidden one is blocked. Container runtimes apply a default profile that blocks notoriously dangerous syscalls.
  • Linux Capabilities: Traditionally, the root user (UID 0) bypasses all kernel permission checks. Capabilities break down root’s monolithic power into distinct, assignable units. A container can be granted only the capabilities it needs (e.g., CAP_NET_BIND_SERVICE to bind to ports below 1024) instead of full root access.

Key Takeaway: Containers are not magic. They are regular Linux processes artfully constrained by namespaces, cgroups, and security profiles. Your choice of container tool is really about how you want to manage these kernel features.

The Runtime Rumble: OCI, CRI, and the Players

The container world is built on standards that ensure interoperability. These standards define the layers between user-facing tools (like docker or kubectl) and the kernel.

graph TD
    subgraph "User/Orchestrator Level"
        A["kubectl run pod"]
        B["podman run image"]
    end

    subgraph "API / Shim Layer"
        C["kubelet"]
        D["Podman Libpod Library"]
    end

    subgraph "High-Level Runtimes (CRI/Daemon)"
        E["CRI-O"]
        F["containerd"]
        G["Podman (Direct OCI)"]
    end

    subgraph "OCI Low-Level Runtimes"
        H["runc"]
        I["crun"]
    end

    subgraph "Linux Kernel"
        J["Linux Kernel<br/>(Namespaces, cgroups, seccomp)"]
    end

    A --> C
    C -- "CRI API" --> E
    C -- "CRI API" --> F
    B --> D
    D --> G

    E -- "OCI Spec" --> H
    F -- "OCI Spec" --> H
    F -- "OCI Spec" --> I
    G -- "OCI Spec" --> H
    G -- "OCI Spec" --> I

    H --> J
    I --> J

The Standards: OCI and CRI Explained

  • Open Container Initiative (OCI): A Linux Foundation project that provides open standards for container formats and runtimes. Its two key specifications are:
    • Runtime Specification (runtime-spec): Defines how to run a “filesystem bundle” as a container. Tools like runc are implementations of this spec.
    • Image Specification (image-spec): Defines the format of a container image, ensuring an image built with Docker can run on a system using Podman or CRI-O.
  • Container Runtime Interface (CRI): A plugin interface for Kubernetes. It allows the kubelet (the primary node agent) to use a wide variety of container runtimes without needing to recompile. This decoupled kubelet from the Docker Engine and spurred innovation.

High-Level Runtimes: containerd vs. CRI-O

High-level runtimes manage the full container lifecycle: pulling images, managing storage and networking, and handing off to a low-level runtime to execute the container.

  • containerd: Originally part of the Docker project, it was spun out into the CNCF to become a general-purpose, industry-standard runtime. It is robust, feature-rich, and used by Docker, Kubernetes (via its CRI plugin), and other projects.
  • CRI-O: A lightweight alternative to containerd, built specifically to be a stable, minimal CRI implementation for Kubernetes. It has no mission beyond serving the Kubernetes CRI, making it a focused and streamlined choice for Kubernetes-only environments.

Low-Level Runtimes: runC, crun, and Beyond

These are the OCI-compliant binaries that do the actual work of creating the container by interacting with the kernel.

  • runC: The reference implementation of the OCI spec, originally from Docker. It’s written in Go and is the de facto standard low-level runtime.
  • crun: A fast and low-memory OCI runtime written in C. In many benchmarks, crun outperforms runc in container startup time and memory footprint, making it an excellent choice for edge computing or high-density environments. You can often swap runc for crun in containerd or CRI-O configurations.
1
2
3
# Example: Configuring CRI-O to use crun as the default runtime
# In /etc/crio/crio.conf under the [crio.runtime] table:
runtime_path = "/usr/bin/crun"

Beyond the Daemon: Kubernetes-Free Container Management

Not every use case requires a Kubernetes cluster. For single-host management, development, or simpler orchestration, daemonless tools offer significant advantages in security and simplicity.

Podman: The Daemonless Contender

Podman is a daemonless container engine that provides a Docker-compatible command-line interface. Its key difference is its architecture: there is no central, root-owned daemon managing the containers.

  • Daemonless Architecture: podman commands fork-and-exec directly, interacting with the kernel. This removes a single point of failure and a major attack surface.
  • Rootless by Default: Podman is designed to run containers as a non-root user out of the box, leveraging user namespaces. This is a massive security win.
  • Pods Concept: Podman natively supports the concept of “pods”—groups of containers that share resources—just like Kubernetes.
1
2
3
4
5
6
7
# Podman's CLI is a drop-in replacement for Docker's
$ podman run -dt -p 8080:80 --name my-nginx nginx

# Run a multi-container pod
$ podman pod create --name my-app -p 8080:80
$ podman run --pod my-app -d --name my-nginx nginx
$ podman run --pod my-app -d --name my-sidecar alpine sleep infinity

systemd-nspawn: The “chroot on Steroids”

For those deeply integrated with systemd, systemd-nspawn offers a lightweight, OS-level containerization tool. It’s less about application containers (like Docker) and more about booting a full OS filesystem tree in an isolated environment. It’s an excellent tool for debugging, building, and testing system images.

1
2
3
4
# Download a minimal Fedora image
$ sudo dnf install -y fedora-release-container
# Run a shell inside the Fedora container with private networking
$ sudo systemd-nspawn -D /var/lib/container/fedora-rawhide --network-veth -b

Practical Considerations: Security and Performance

Your choice of runtime and management tool has real-world consequences.

The Security Landscape

A daemonless, rootless model like Podman’s is inherently more secure. A compromised container running under Podman as a regular user has far fewer privileges to escalate than one breaking out of a container managed by a root-owned Docker daemon.

The choice between containerd and CRI-O is less about security and more about scope. CRI-O’s minimal focus on Kubernetes means a smaller attack surface, while containerd’s broader feature set might be a better fit for more diverse environments.

Feature Docker Engine Podman containerd (as a runtime)
Architecture Client-Server (Daemon) Daemonless Client-Server (Daemon)
Rootless Support Yes (experimental/post-setup) Yes (by default) Limited (work in progress)
Primary CLI docker podman (docker-compatible) nerdctl / ctr
Primary Use Case All-in-one developer experience Secure, daemonless host mgmt Backend for Kubernetes, Docker
OCI Compliant Yes Yes Yes

Performance Tuning and Trade-offs

For most workloads, the performance difference between runtimes is negligible. However, in scenarios requiring extremely fast startup or minimal overhead (like serverless functions or edge computing), choices matter.

  • crun vs. runc: Using crun can shave milliseconds off container start times and reduce the memory footprint of each container launch.
  • Daemon vs. Daemonless: The overhead of a daemon’s API is typically minimal, but in high-throughput scenarios where thousands of short-lived containers are created, the direct fork-exec model of Podman can be more efficient.

Final Thoughts: The Right Tool for the Job

The Linux container ecosystem is a testament to the power of open standards and layered design. While Docker provided an accessible entry point, the real power lies in understanding the components and choosing the right stack for your needs.

  • For a Kubernetes cluster, your choice is between containerd and CRI-O, with performance-sensitive users considering crun as the underlying OCI runtime.
  • For local development and host management, Podman offers a more secure, flexible, and modern alternative to the traditional Docker daemon.
  • For deep system integration and OS-level virtualization, systemd-nspawn remains a powerful, if lower-level, tool.

Don’t be afraid to experiment. Install Podman alongside Docker. Configure a Kubernetes node to use CRI-O with crun. The deeper you understand this stack, the more control you will have over the security, performance, and reliability of your containerized workloads.

Further Reading


🚀 Ready to get hands-on? Spin up an interactive AI or Kubernetes Sandbox at Aicademy Labs for free.

This post is licensed under CC BY 4.0 by the author.