crun(1)
β¨ crun(1) β¨ β General Commands Manual π
okay so bestie, meet crun, from the General Commands Manual section. she's a whole vibe and she runs containers. let's get into it π«Ά
πΈ NAME πΈ
crun β a fast and lightweight OCI runtime β¨
π SYNOPSIS π
crun [global options] command [command options] [arguments...]
π DESCRIPTION π
crun is a command line program for running Linux containers that follow the Open Container Initiative (OCI) format. iconic behavior honestly. she's a runtime, she runs your containers, that's the whole thing, periodt π
π COMMANDS π
okay so these are all the things you can ask crun to do. literally the whole menu:
createβ Create a container. the runtime detaches from the container process once the container environment is created. heads up tho: after this you have to usestartnext to actually get it going. it's giving "two-step process" energy.deleteβ Remove definition for a container.execβ Exec a command in a running container.listβ List known containers.killβ Send the specified signal to the container init process. if you don't specify a signal,SIGTERMis used. (no cap, it defaults on you.)psβ Show the processes running in a container.runβ Create and immediately start a container. the main character tbh, does both steps at once β¨specβ Generate a configuration file.startβ Start a container that was previously created. π¨ important: a container cannot be started multiple times.stateβ Output the state of a container.pauseβ Pause all the processes in the container.resumeβ Resume the processes in the container.updateβ Update container resource constraints.checkpointβ Checkpoint a running container using CRIU.restoreβ Restore a container from a checkpoint.
π¦ STATE π¦
okay so where does crun keep its stuff? it depends on who you are, bestie, and that's so real of her.
By default, when running as root user, crun saves its state under the /run/crun directory. πΎ
As unprivileged user, instead the XDG_RUNTIME_DIR environment variable is honored, and the directory $XDG_RUNTIME_DIR/crun is used. πΈ
And if you want to override the whole thing? the global option --root overrides this setting. baddie behavior. π
π GLOBAL OPTIONS π
these work everywhere, they're the universal besties:
--debugβ Produce verbose output. (basically crun telling you everything she's doing, oversharing era π)--log=LOG-DESTINATIONβ Define the destination for the error and warning messages generated by crun. If the error happens late in the container init process, when crun already stopped watching it, then it will be printed to the container stderr. π--log-format=FORMATβ Define the format of the log messages. It can either betext, orjson. The default istext.--log-level=LEVELβ Define the log level. It can either bedebug,warningorerror. The default iserror.--no-pivotβ Usechroot(2)instead ofpivot_root(2)when creating the container.--root=DIRβ Defines where to store the state for crun containers.--systemd-cgroupβ Use systemd for configuring cgroups. If not specified, the cgroup is created directly using thecgroupfsbackend.--cgroup-manager=MANAGERβ Specify what cgroup manager must be used. Permitted values arecgroupfs,systemdanddisabled.-?,--helpβ Print a help list.--usageβ Print a short usage message.-V,--versionβ Print program version
β οΈ ok wait, real talk about --no-pivot: This option is not safe, and should be avoided. like, genuinely. bestie please don't. π
π the log destination format
It is specified in the form BACKEND:SPECIFIER.
These following backends are supported:
file:PATHjournald:IDENTIFIERsyslog:IDENTIFIER
If no backend is specified, then file: is used by default.
π CREATE OPTIONS π
crun [global options] create [options] CONTAINER
--bundle=PATHβ Path to the OCI bundle, by default it is the current directory.--config=FILEβ Override the configuration file to use. The default value isconfig.json.--console-socket=SOCKETβ Path to a UNIX socket that will receive theptmxend of the tty for the container.--no-new-keyringβ Keep the same session key--preserve-fds=Nβ Additional number of FDs to pass into the container.--pid-file=PATHβ Path to the file that will contain the container process PID.
π₯ RUN OPTIONS π₯
crun [global options] run [options] CONTAINER
--bundle=BUNDLEβ Path to the OCI bundle, by default it is the current directory.--config=FILEβ Override the configuration file to use. The default value isconfig.json.--console-socket=SOCKETβ Path to a UNIX socket that will receive theptmxend of the tty for the container.--no-new-keyringβ Keep the same session key.--preserve-fds=Nβ Additional number of FDs to pass into the container.--pid-file=PATHβ Path to the file that will contain the container process PID.--detachβ Detach the container process from the current session. π
π DELETE OPTIONS π
crun [global options] delete [options] CONTAINER
--forceβ Delete the container even if it is still running. (aggressive, but make it fashion π )--regex=REGEXβ Delete all the containers that satisfy the specified regex.
β EXEC OPTIONS β
crun [global options] exec [options] CONTAINER CMD
--apparmor=PROFILEβ Set the apparmor profile for the process.--console-socket=SOCKETβ Path to a UNIX socket that will receive theptmxend of the tty for the container.--cwd=PATHβ Set the working directory for the process toPATH.--cap=CAPβ Specify an additional capability to add to the process.--detachβ Detach the container process from the current session.--cgroup=PATHβ Specify a sub-cgroup path inside the container cgroup. The path must already exist in the container cgroup. (no creating it yourself, it's already gotta be there π)--env=ENVβ Specify an environment variable.--no-new-privsβ Set the no new privileges value for the process.--preserve-fds=Nβ Additional number of FDs to pass into the container.--process=FILEβ Path to a file containing the process JSON configuration.--process-label=VALUEβ Set the asm process label for the process commonly used with selinux.--pid-file=PATHβ Path to the file that will contain the new process PID.-t,--ttyβ Allocate a pseudo TTY.-u USERSPEC,--user=USERSPECβ Specify the user in the formUID[:GID].
π LIST OPTIONS π
crun [global options] list [options]
-q,--quietβ Show only the container ID. (minimalist era, we love to see it)
π©Έ KILL OPTIONS π©Έ
crun [global options] kill [options] CONTAINER SIGNAL
--allβ Kill all the processes in the container.--regex=REGEXβ Kill all the containers that satisfy the specified regex.
π§ PS OPTIONS π§
crun [global options] ps [options]
--format=FORMATβ Specify the output format. It must be eithertableorjson. By defaulttableis used.
π SPEC OPTIONS π
crun [global options] spec [options]
-b DIR,--bundle=DIRβ Path to the root of the bundle dir (default".").--rootlessβ Generate aconfig.jsonfile that is usable by an unprivileged user.
π UPDATE OPTIONS π
crun [global options] update [options] CONTAINER
--blkio-weight=VALUEβ Specifies per cgroup weight.--cpu-period=VALUEβ CPU CFS period to be used for hardcapping.--cpu-quota=VALUEβ CPU CFS hardcap limit.--cpu-rt-period=VALUEβ CPU realtime period to be used for hardcapping.--cpu-rt-runtime=VALUEβ CPU realtime hardcap limit.--cpu-share=VALUEβ CPU shares.--cpuset-cpus=VALUEβ CPU(s) to use.--cpuset-mems=VALUEβ Memory node(s) to use.--kernel-memory=VALUEβ Kernel memory limit.--kernel-memory-tcp=VALUEβ Kernel memory limit for TCP buffer.--memory=VALUEβ Memory limit.--memory-reservation=VALUEβ Memory reservation orsoft_limit.--memory-swap=VALUEβ Total memory usage.--pids-limit=VALUEβ Maximum number of pids allowed in the container.-r,--resources=FILEβ Path to the file containing the resources to update.
π§ CHECKPOINT OPTIONS π§
crun [global options] checkpoint [options] CONTAINER
--image-path=DIRβ Path for saving CRIU image files--work-path=DIRβ Path for saving work files and logs--leave-runningβ Leave the process running after checkpointing--tcp-establishedβ Allow open TCP connections--ext-unix-skβ Allow external UNIX sockets--shell-jobβ Allow shell jobs--pre-dumpβ Only checkpoint the container's memory without stopping the container. It is not possible to restore a container from a pre-dump. A pre-dump always needs a final checkpoint (without--pre-dump). It is possible to make as many pre-dumps as necessary. For a second pre-dump or for a final checkpoint it is necessary to use--parent-pathto point crun (and thus CRIU) to the pre-dump.--parent-path=DIRβ Doing multiple pre-dumps or the final checkpoint after one or multiple pre-dumps requires that crun (and thus CRIU) knows the location of the pre-dump. It is important to use a relative path from the actual checkpoint directory specified via--image-path. It will fail if an absolute path is used. π¨--manage-cgroups-mode=MODEβ Specify which CRIU manage cgroup mode should be used. Permitted values aresoft,ignore,fullorstrict. Default issoft.
π RESTORE OPTIONS π
crun [global options] restore [options] CONTAINER
-b DIR,--bundle=DIRβ Container bundle directory (default".")--image-path=DIRβ Path for saving CRIU image files--work-path=DIRβ Path for saving work files and logs--tcp-establishedβ Allow open TCP connections--ext-unixβ Allow external UNIX sockets--shell-jobβ Allow shell jobs--detachβ Detach from the container's process--pid-file=FILEβ Where to write the PID of the container--manage-cgroups-mode=MODEβ Specify which CRIU manage cgroup mode should be used. Permitted values aresoft,ignore,fullorstrict. Default issoft.--lsm-profile=TYPE:NAMEβ Specify an LSM profile to be used during restore.TYPEcan be eitherapparmororselinux.--lsm-mount-context=VALUEβ Specify a new LSM mount context to be used during restore. This option replaces an existing mount context information with the specified value. This is useful when restoring a container into an existing Pod and selinux labels need to be changed during restore.
π« Extensions to OCI π«
run.oci.mount_context_type=context
Set the mount context type on volumes mounted with SELinux labels.
Valid context types are:
context (default)
fscontext
defcontext
rootcontext
More information on how the context mount flags works see the mount(8) man page.
run.oci.seccomp.receiver=PATH
If the annotation run.oci.seccomp.receiver=PATH is specified, the seccomp listener is sent to the UNIX socket listening on the specified path. It can also set with the RUN_OCI_SECCOMP_RECEIVER environment variable. It is an experimental feature, and the annotation will be removed once it is supported in the OCI runtime specs. It must be an absolute path.
run.oci.seccomp.plugins=PATH
If the annotation run.oci.seccomp.plugins=PLUGIN1[:PLUGIN2]... is specified, the seccomp listener fd is handled through the specified plugins. The plugin must either be an absolute path or a file name that is looked up by dlopen(3). More information on how the lookup is performed are available on the ld.so(8) man page.
run.oci.seccomp_fail_unknown_syscall=1
If the annotation run.oci.seccomp_fail_unknown_syscall is present, then crun will fail when an unknown syscall is encountered in the seccomp configuration.
run.oci.seccomp_bpf_data=PATH
If the annotation run.oci.seccomp_bpf_data is present, then crun ignores the seccomp section in the OCI configuration file and use the specified data as the raw data to the seccomp(SECCOMP_SET_MODE_FILTER) syscall. The data must be encoded in base64.
It is an experimental feature, and the annotation will be removed once it is supported in the OCI runtime specs.
π§ͺ heads up bestie: two of these seccomp annotations are explicitly marked experimental, and their annotations will be removed once the OCI runtime specs support them. just so you know what you're getting into β¨
run.oci.keep_original_groups=1
okay so if this annotation is there, crun just... skips the setgroups syscall entirely π
. And that syscall is normally the thing that either sets the extra groups you listed in the OCI config, or resets the whole extra-groups list when you didn't list any. So yeah, this one is lowkey a "trust me bestie, hands off my groups" button.
run.oci.pidfd_receiver=PATH
this is an experimental feature and it will get removed once the thing lands in the OCI runtime specs. so don't get attached π
If it's present, you're telling crun where to send the pidfd for the container process β specifically the path to the UNIX socket that receives it.
run.oci.systemd.force_cgroup_v1=/PATH
If the annotation run.oci.systemd.force_cgroup_v1=/PATH shows up, crun overrides that mount point /PATH with a cgroup v1 mount built out of a single hierarchy none,name=systemd. Very much giving "I need this to work" energy β the point is running containers on a cgroup v2 system when the container's systemd is old and has zero cgroup v2 support π§.
Note: your container host has to already have the cgroup v1 mount sitting there, otherwise this straight up won't work. And if you wanna run the container rootless, the user it runs under needs permissions on that mountpoint. no permission, no party π
For example, as root:
mkdir /sys/fs/cgroup/systemd
mount cgroup -t cgroup /sys/fs/cgroup/systemd -o none,name=systemd,xattr
chown -R the_user.the_user /sys/fs/cgroup/systemd
run.oci.systemd.subgroup=SUBGROUP
This one overrides the name of the systemd sub cgroup that gets created under the systemd scope, so the final cgroup comes out looking like:
/sys/fs/cgroup/$PATH/$SUBGROUP
If you set it to the empty string, then no sub cgroup gets created at all. rip little guy π
If you don't specify it at all, it defaults to container on cgroup v2, and to "" on cgroup v1.
e.g.
/sys/fs/cgroup//system.slice/foo-352700.scope/container
run.oci.delegate-cgroup=DELEGATED-CGROUP
If the run.oci.systemd.subgroup annotation is specified, then yet another sub-cgroup gets created and the container process gets moved into it. nesting dolls but make it cgroups πͺ
If a cgroup namespace is used, that cgroup namespace is created before moving the container to the delegated cgroup. order matters bestie.
/sys/fs/cgroup/$PATH/$SUBGROUP/$DELEGATED-CGROUP
The runtime applies no limits to the $DELEGATED-CGROUP sub-cgroup β it only uses $PATH/$SUBGROUP.
The container payload fully manages $DELEGATE-CGROUP, and the limits applied to $PATH/$SUBGROUP still apply to $DELEGATE-CGROUP.
Since cgroup delegation is not safe on cgroup v1, this option is supported only on cgroup v2. periodt.
run.oci.hooks.stdout=FILE
If the annotation run.oci.hooks.stdout is present, crun opens that file and uses it as the stdout for the hook processes. It's opened in append mode, and it gets created if it doesn't already exist. very "make it if it's not there" of it β¨
run.oci.hooks.stderr=FILE
Same deal for stderr, bestie: if run.oci.hooks.stderr is present, crun opens the specified file and uses it as the stderr for the hook processes. Opened in append mode again, created if it doesn't already exist.
run.oci.handler=HANDLER
experimental feature alert π¨
If specified, it runs the specified handler for execing the container. The only supported values are krun and wasm.
krunβ whenkrunis specified, thelibkrun.soshared object is loaded and it's used to launch the container usinglibkrun. iconic.wasmβ if specified, run the wasm handler for the container. This lets you run wasm workloads natively. It accepts a.wasmbinary as input, and if you hand it a.watit'll automatically be compiled into a wasm module. The stdout of the wasm module is relayed back viacrun. π¦
tmpcopyup mount options
If the tmpcopyup option is specified for a tmpfs, then the path that's shadowed by the tmpfs mount gets recursively copied up into the tmpfs itself. so nothing gets left behind π
copy-symlink mount options
If the copy-symlink option is specified, then when the source of a bind mount is a symlink, the symlink is recreated at the specified destination instead of attempting a mount that would resolve the symlink itself.
And if the destination already exists and it is not a symlink with the expected content, crun will return an error. no sneaky business π
r$FLAG mount options
If a r$FLAG mount option is specified, then the flag $FLAG is set recursively for each children mount. it's giving "and all their little friends too" π
These flags are supported:
"rro""rrw""rsuid""rnosuid""rdev""rnodev""rexec""rnoexec""rsync""rasync""rdirsync""rmand""rnomand""ratime""rnoatime""rdiratime""rnodiratime""rrelatime""rnorelatime""rstrictatime""rnostrictatime"
idmap mount options
If the idmap option is specified, then the mount is ID mapped using the container target user namespace.
This is an experimental feature and it can change at any time without notice. fr fr, don't build your whole life on it π
The idmap option supports a custom mapping that can be different than the user namespace used by the container. slay, options.
The mapping can be specified after the idmap option like: idmap=uids=0-1-10#10-11-10;gids=0-100-10.
For each triplet, the first value is the start of the backing file system IDs that are mapped to the second value on the host. The length of this mapping is given in the third value.
Multiple ranges are separated with #.
These values are written to the /proc/$PID/uid_map and /proc/$PID/gid_map files to create the user namespace for the idmapped mount.
The only two options currently supported after idmap are uids and gids.
When a custom mapping is specified, a new user namespace is created for the idmapped mount.
If no option is specified, then the container user namespace is used.
If the specified mapping is prepended with a '@', then the mapping is considered relative to the container user namespace. The host ID for the mapping is changed to account for the relative position of the container user in the container user namespace.
For example, the mapping: uids=@1-3-10, given a configuration like
"uidMappings": [
{
"containerID": 0,
"hostID": 0,
"size": 1
},
{
"containerID": 1,
"hostID": 2,
"size": 1000
}
]
will be converted to the absolute value uids=1-4-10, where 4 is calculated by adding 3 (container ID in the uids= mapping) and 1 (hostID - containerID for the user namespace mapping where containerID = 1 is found).
The current implementation doesn't take into account multiple user namespace ranges, so it is the caller's responsibility to split a mapping if it overlaps multiple ranges in the user namespace. And in such a case, there won't be any error reported. π you just gotta know, iykyk
Automatically create user namespace
When running as a user different than root, a user namespace is automatically created even if it's not specified in the config file. The current user is mapped to the ID 0 in the container, and any additional id specified in the files /etc/subuid and /etc/subgid is automatically added starting with ID 1. bestie gets a namespace whether she asked or not π
CGROUP v1
Support for cgroup v1 is deprecated and will be removed in a future release. okay so pour one out π₯²
CGROUP v2
Note: cgroup v2 does not yet support control of realtime processes and the cpu controller can only be enabled when all RT processes are in the root cgroup. This will make crun fail while running alongside RT processes.
If the cgroup configuration found is for cgroup v1, crun attempts a conversion when running on a cgroup v2 system. glow-up conversion arc β¨
These are the OCI resources currently supported with cgroup v2 and how they are converted when needed from the cgroup v1 configuration.
Memory controller
OCI (x) β cgroup 2 value (y) pattern, here we go:
limitβmemory.max, conversiony = x, no comment. clean, no notes πswapβmemory.swap.max, conversiony = x - memory_limit. comment: the swap limit on cgroup v1 includes the memory usage too. sneaky math fr πreservationβmemory.low, conversiony = x, no comment. periodt.
PIDs controller
limitβpids.max, conversiony = x, no comment. that's it, that's the whole table π
πΈ CPU controller πΈ
ok so here's the tea on how the OCI values get converted into cgroup 2 values, mapping x β y, no cap:
sharesβcpu.weightβ conversion:y = (1 + ((x - 2) * 9999) / 262142), and the comment is that it converts from[2-262144]to[1-10000]π«periodβcpu.maxβ conversion:y = x, comment:periodandquotaare written together π₯quotaβcpu.maxβ conversion:y = x, comment:periodandquotaare written together (yes bestie, same team, they go together always) π
π blkio controller π
this one's the big one, so buckle up:
weightβio.bfq.weightβ conversion:y = x, no comment, it's just that simple β¨weight_deviceβio.bfq.weightβ conversion:y = x, still no comment πweightβio.weight (fallback)β conversion:y = 1 + (x-10)*9999/990, comment: convert linearly from[10-1000]to[1-10000]πweight_deviceβio.weight (fallback)β conversion:y = 1 + (x-10)*9999/990, comment: convert linearly from[10-1000]to[1-10000](same math, different friend) π«Άrbpsβio.maxβ conversion:y=xwbpsβio.maxβ conversion:y=xriopsβio.maxβ conversion:y=xwiopsβio.maxβ conversion:y=x
ngl those last four are the chillest rows in the whole page, they literally just pass the value through π
π¦ cpuset controller π¦
short and slay, we love her:
cpusβcpuset.cpusβ conversion:y = xmemsβcpuset.memsβ conversion:y = x
no comments, no drama, iconic behavior π
π hugetlb controller π
one whole row, she's minimal:
.limit_in_bytesβhugetlb..maxβ conversion:y = x
that's it bestie, that's the entire controller, and honestly? queen π
aaaand that's the whole page!! π thanks for reading all three parts, you're literally a main character for making it to the end π«Ά
so real quick about the footer, since we're not copy-pasting it: this page lives in theUser Commandssection and it'scrun(1), and it's got its own source/version and date stamps down there β so if you ever need to cite it or check what version you're reading, that's where to look π
go forth and containerize, babe π³β¨
π¦ where this page comes from
- ubuntu resolute: crun 1.21-1ubuntu3 Β· GPL-2.0-or-later
- ubuntu stonking: crun 1.21-1ubuntu3 Β· GPL-2.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms