How Linux Spawns OCI Containers Using runc
This article explores the technical mechanics behind how the Linux
operating system uses the runc CLI tool to create and
manage Open Container Initiative (OCI) compliant containers. It covers
the consumption of the standard OCI container bundle, the invocation of
core Linux kernel isolation primitives such as namespaces and cgroups,
the filesystem pivoting process, and the execution transition to the
target containerized workload.
The OCI Container Bundle
The deployment process begins with the OCI container bundle. Before
runc interacts with the kernel, it requires a standardized
bundle directory containing two primary components:
rootfs/: A directory containing the complete root filesystem of the container image.config.json: A specification file that details process execution parameters, environment variables, capabilities, mount points, resource limits, and kernel features to enable.
Higher-level runtimes like containerd or
CRI-O unpack container image layers into this filesystem
layout and generate the config.json file according to the
OCI Runtime Specification before handing execution off to
runc.
Invoking runc and libcontainer
When invoked through commands such as
runc run <container-id> or
runc create <container-id>, the runc
binary parses the config.json file. It then uses its
internal Go package, libcontainer, to interact directly
with the Linux kernel via system calls. runc acts as an
ephemeral wrapper: its job is strictly to configure the kernel
primitives, launch the containerized process, and optionally exit while
handing supervisory duties over to a monitoring process (like
shim).
Kernel Isolation via Namespaces
To isolate the new container process from the host and other
containers, runc uses the clone(2) and
unshare(2) system calls with specific flag arguments to
instantiate Linux namespaces:
- PID Namespace (
CLONE_NEWPID): Isolates the process ID space, allowing the container's main process to become PID 1 inside the container while appearing as a standard unprivileged PID on the host. - Mount Namespace (
CLONE_NEWNS): Isolates the filesystem mount points, ensuring the container cannot see or manipulate host mounts. - Network Namespace (
CLONE_NEWNET): Provides independent network devices, IP addresses, routing tables, and firewall rules. - UTS Namespace (
CLONE_NEWUTS): Allows the container to have its own hostname and domain name. - IPC Namespace (
CLONE_NEWIPC): Isolates System V IPC and POSIX message queues. - User Namespace (
CLONE_NEWUSER): Maps user and group IDs inside the container to different UIDs and GIDs on the host, enabling root privileges inside the container without granting root access on the host.
Resource Constraints with Cgroups
Before the workload executes, runc applies resource
boundaries using Linux Control Groups (cgroups v1 or cgroups v2).
runccreates a new cgroup directory under/sys/fs/cgroup/(in cgroups v2, unified hierarchy).- It writes limits defined in
config.json—such as memory limits, CPU shares, I/O bandwidth, and pids limits—to corresponding control files (e.g.,memory.max,cpu.weight). - The process ID of the newly spawned setup process is added to the
cgroup's
cgroup.procsfile, ensuring all subsequently spawned child processes inherit these restrictions.
Filesystem Jail and Security Hardening
Once isolated by namespaces and constrained by cgroups,
runc secures the filesystem and restricts kernel
privileges:
- Pivoting the Root: Using the
pivot_root(2)system call (preferred overchroot),runcswaps the container'srootfswith the host's root filesystem, unmounts the old host root, and confines the process entirely to the new root directory. - Mounting Virtual Filesystems:
runcmounts container-specific pseudo-filesystems, including/proc,/sys, and/dev, ensuring device nodes and system information reflect the container's isolated context. - Capabilities: It drops unnecessary Linux
capabilities (e.g.,
CAP_SYS_ADMIN,CAP_NET_ADMIN) usingcapset(2)to limit the operations the containerized process can execute, even if running as UID 0. - Seccomp Filters:
runcparses system call rules defined in the specification and usesprctl(2)withPR_SET_SECCOMPto apply BPF (Berkeley Packet Filter) profiles, blocking restricted system calls at the kernel boundary. - LSM Profiles: It configures Linux Security Modules
like AppArmor or SELinux by writing the appropriate profile attributes
to
/proc/self/attr/.
Final Execution: execve
With all security boundaries, namespaces, mounts, and resource
controls established, the initialization process calls
execve(2). This system call replaces the temporary
runc initialization process with the container's actual
user-defined entrypoint binary (such as /bin/sh or a
compiled service). The application process now runs natively on the
Linux kernel with near-zero virtualization overhead, fully confined
within the OCI-compliant boundaries enforced by the OS.