What is runc?
mainrunc is a CLI tool for spawning and running containers on Linux according to the OCI (Open Container Initiative) specification.repository·main·Indexed 11 days ago
https://github.com/opencontainers/runcA CLI tool for spawning and running containers on Linux according to the OCI (Open Container Initiative) specification. It serves as a low-level container runtime and includes libcontainer, a native Go implementation for managing container lifecycles, namespaces, cgroups, and capabilities. It supports rootless containers and integrates with CRIU (version 1.5.2 or higher) for checkpointing and restoring container states.
runc is a CLI tool for spawning and running containers on Linux according to the OCI (Open Container Initiative) specification.libcontainer is a native Go implementation for creating containers. It provides the primitives necessary to manage container lifecycles, including the configuration of:
It is designed to allow developers to manage the lifecycle of a container while performing additional operations after the container has been created.
runc implements the OCI Runtime Spec v1.3.0 specifically for the linux platform.runc provides two ways to handle standard IO (stdio):
terminal: true): runc creates a new pseudo-terminal (using the container's namespaced /dev/pts/ptmx). This is the recommended default because it allows tools like sudo to function correctly inside the container. In this mode, stdin, stdout, and stderr all point to the same underlying file to mimic a shell environment.terminal: false): runc uses the file handles already provided by the host environment. This is useful for non-interactive pipelines where you want to redirect stdout or stderr to specific files or pipes on the host.If the terminal key is missing from config.json, runc defaults to pass-through mode. If runc spec was used to generate the config, it defaults to new terminal mode.
The container's lifecycle involves a synchronization step between the parent process and the container's init process:
libcontainer should be compiled statically. The runtime uses exec to start the process inside the container. There should be no long-running init process within the container spec.dup2s the console as the container's STDIN, STDOUT, and STDERR, and mounts the console at /dev/console./etc/hosts, /etc/resolv.conf, /etc/hostname, and /etc/localtime.libcontainer integrates with CRIU to support checkpointing and restoring containers. This allows you to save the state of a running process inside a container to disk and restore that state into a new process, either on the same machine or a different one.
Requirements:
criu version 1.5.2 or higher is required.criu is not installed, you must install it manually (e.g., building from source) or use a Docker image where criu is pre-installed.The name of the systemd unit and its containing slice are derived from the Linux.CgroupsPath field in the container runtime spec (config.json).
Linux.CgroupsPathIf Linux.CgroupsPath is set, it must follow the format [slice]:[prefix]:[name]:
slice: The systemd slice under which the container is placed.system.slice.user.slice.- to represent a root slice.user-1000.slice).user.slice/user-1000.slice) are invalid.prefix and name: Used to compose the unit name as <prefix>-<name>.scope.name has a .slice suffix, the prefix is ignored and the name is used as is.If Linux.CgroupsPath is not set or is empty, runc defaults to the equivalent of :runc:<container-id>.
Container creation in libcontainer follows a two-step process. Because of this, you must provide a binary that will serve as the container's init process.
In libcontainer, the current binary (/proc/self/exe) is used as the init process by passing the argument init. This initial phase is referred to as the "bootstrap" process. To implement this correctly, your application must include an init function that serves as the entry point for the bootstrap phase.
Early-stage bootstrapping is handled by importing the nsenter package.
The nsenter package provides a special init constructor via Cgo that executes nsexec() before the Go runtime boots. This is necessary to call setns(2) and clone(2) to join Linux namespaces without encountering issues caused by the Go runtime's multi-threaded nature.
To ensure the nsexec() constructor is executed when your application starts, you must perform a blank import of the package. If you are using libcontainer directly and do not import nsenter, the re-exec process will fail to join the required namespaces.
Lifecycle of nsexec():
_LIBCONTAINER_INITPIPE environment variable.setns(2) to join existing namespaces.clone(2) with the provided flags (this is required even without CLONE_NEW* flags to allow entering the PID namespace).import _ "github.com/opencontainers/runc/libcontainer/nsenter"Cgroups are used to manage system resources like CPU, memory, and device access. The following subsystems are enabled by default in v1 containers:
| Subsystem | Enabled |
|---|---|
devices | 1 |
memory | 1 |
cpu | 1 |
cpuacct | 1 |
cpuset | 1 |
blkio | 1 |
perf_event | 1 |
freezer | 1 |
hugetlb | 1 |
pids | 1 |
Synchronization Note: The parent process of the container's init must place the init PID into the correct cgroups before initialization begins. This is synchronized via a pipe (passed to the init process via FD 3) to ensure no processes escape the cgroups during setup.
| Subsystem | Enabled |
| ---------- | ------- |
| devices | 1 |
| memory | 1 |
| cpu | 1 |
| cpuacct | 1 |
| cpuset | 1 |
| blkio | 1 |
| perf_event | 1 |
| freezer | 1 |
| hugetlb | 1 |
| pids | 1 |A container requires a root filesystem (rootfs) to jail and spawn processes. The runtime automatically sets up several essential filesystems within the container's mount namespace.
Key Filesystem Mounts:
| Path | Type | Flags | Data |
|---|---|---|---|
/proc | proc | MS_NOEXEC,MS_NOSUID,MS_NODEV | |
/dev | tmpfs | MS_NOEXEC,MS_STRICTATIME | mode=755 |
/dev/shm | tmpfs | MS_NOEXEC,MS_NOSUID,MS_NODEV | mode=1777,size=65536k |
/dev/mqueue | mqueue | MS_NOEXEC,MS_NOSUID,MS_NODEV | |
/dev/pts | devpts | MS_NOEXEC,MS_NOSUID | newinstance,ptmxmode=0666,mode=620,gid=5 |
/sys | sysfs | MS_NOEXEC,MS_NOSUID,MS_NODEV,MS_RDONLY |
Device Nodes:
After mounting filesystems, the runtime populates /dev with specific device nodes. A standard rootfs does not need to include these, as the runtime provides them:
| Path | Mode | Access |
|---|---|---|
/dev/null | 0666 | rwm |
/dev/zero | 0666 | rwm |
/dev/full | 0666 | rwm |
/dev/tty | 0666 | rwm |
/dev/random | 0666 | rwm |
/dev/urandom | 0666 | rwm |
Standard Symlinks:
Once /proc is mounted, the following symlinks are established for I/O:
/dev/fd $\rightarrow$ /proc/self/fd/dev/stdin $\rightarrow$ /proc/self/fd/0/dev/stdout $\rightarrow$ /proc/self/fd/1/dev/stderr $\rightarrow$ /proc/self/fd/2Root Jailing:
The runtime uses pivot_root to jail the process inside the rootfs. If the rootfs is inside a ramfs, MS_MOVE combined with chroot is used instead, as pivot_root is not supported in ramfs.
runc operates in two primary execution modes. Choosing the right one depends on whether you are using the CLI interactively or building a high-level container manager.
In this mode, the runc process remains running as a parent to the container process.
stdio is buffered through the foreground runc process.stdio management for you.runc process. If the runc process is killed (e.g., via OOM-kill or manual signal), the container will likely die due to SIGPIPE. It also prevents passing file descriptors directly to the container (unlike --preserve-fds).In this mode, runc exits once the container has started, leaving no long-running runc process.
stdio and acting as a subreaper.containerd or cri-o) that want full control over the container lifecycle without runc interference.prctl(PR_SET_CHILD_SUBREAPER, 1, ...) to prevent zombie processes from accumulating on the host.You can trigger detached mode using either of these methods:
-d flag with the run command: runc run -d ...runc create (to set up the container) followed by runc start (to begin execution).