Examples of runc
A brief demonstration of using runc directly to create a container.
runc is a low-level tool meant to be used by higher-level tooling like
Docker and Podman. This example is from their README but uses apk
from Alpine to create a root file-system instead of extracting one from
an existing container with docker.
Set-up
Host
For Alpine Linux, install the runc package, via apk add runc or if
you you want to try crun instead then apk add crun.
The two packages are:
runcis CLI tool for spawning and running containers according to the OCI specification and is implemented in Go.crunis a Fast and lightweight fully featured OCI runtime and C library for running containers
With the key difference being runc is implemented in Go where as crun is written in C.
OCI Bundle
Create the root file-system and the spec (JSON file).
curl -LO https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.14.6/x86_64/apk.static
mkdir /opt/busybox-container
./apk.static --arch x86_64 -X https://dl-cdn.alpinelinux.org/alpine/edge/main/ --root /opt/busybox-container/rootfs --initdb --no-cache --allow-untrusted add busybox
(cd /opt/busybox-container && runc spec)
Run it
The runc start command looks in the current working directory for the config.json file, created by runc spec.
cd /opt/busybox-container
runc run busybox-1
You can see it running with runc list, for example on my system
~# runc list
ID PID STATUS BUNDLE CREATED OWNER
busybox-1 188 running /opt/busybox-container 2025-06-14T04:22:40.170715447Z root
The same bundle can be run with crun.
cd /opt/busybox-container
crun run bb-1
Likewise you can see it running, which is picked up differently from runc.
crun list
NAME PID STATUS BUNDLE PATH CREATED OWNER
bb-1 266 running /opt/busybox-container 2025-06-14T04:35:18.265240Z root
Filesystem writing with overlay FS
This section was added 2025-07-05.
If you simply, change root.readonly to true, it means any changes when running
will be written back to the rootfs so you would need to make a new rootfs
each time you want a fresh copy.
For this we will create an overlay filesystem similar to how higher-level container runtimes deal with it.
There are four parts to mounting an overlay file system.
- Lower directory - this is read-only access and is the base layer. In this case
this will be the
rootfsdirectory created above. - Upper directory - this is where modifications will be stored. This will be a
new directory, lets call it
goofy_davinci. - Working directory - where work in progress is written before being written to
the upper directory, this will be
goofy_davinci-work. - Mount point - the directory where the filesystem will be mounted.
In this case you can think of the lower directory being a single-layer container image, the upper directory is the file system for the particular container instance.
mkdir /opt/busybox-container/goofy_davinci /opt/busybox-container/goofy_davinci-work /opt/busybox-container/goofy_davinci-merged
mount -t overlay overlay -o lowerdir=/opt/busybox-container/rootfs,upperdir=/opt/busybox-container/goofy_davinci,workdir=/opt/busybox-container/goofy_davinci-work /opt/busybox-container/goofy_davinci-merged
So if you wanted to take the same base image and run another instance you could do:
mkdir /opt/busybox-container/gifted_jepsen /opt/busybox-container/gifted_jepsen-work
mount -t overlay overlay -o lowerdir=/opt/busybox-container/rootfs,upperdir=/opt/busybox-container/gifted_jepsen,workdir=/opt/busybox-container/gifted_jepsen-work /opt/busybox-container/gifted_jepsen-merged
Open the conifg.json and change root.path to goofy_davinci-merged and
root.readonly from true to false.
Now lets run it with:
runc run goofy_davinci
Within the container create some folders / files:
mkdir -p /hello/world
date >> /hello/world/today
exit
Now on the host, you should have
cat /opt/busybox-container/goofy_davinci/hello/world/today which will output
the current date and time.
The alternative to this is to use mount points so you can add content from the host and then do it.
File system writing with mounts
This section was added 2025-07-05.
This set-up gives you a path which you can write to within the container,
similar to the --volume argument on high level container engines such as
Podman and Docker.
Set-up
Create the directory where we will be mounting. Populating it with some files for later.
mkdir /root/from_container
echo "root says hello" >> /root/from_container/hello
Next modifiy the config.json to add the mount point, within the mounts
array, add the following item and remmeber to add comma into the array as
needed.
{
"destination": "/host",
"type": "none",
"source": "/root/from_container",
"options": ["rbind","rw"]
}
This will mount the "/root/from_container directory within the container as
/host with read-write access.
Run
crun run bb-1
Within the container run:
# cat /host/hello
root says hello
# echo "container says bye" >> /host/bye
# exit
Now outside the contain if you cat /root/from_container/bye it should say
container says bye.
Windows
I came across runhcs which as Microsoft mentions on their
container platform tools page, runhcs is the Windows container host
counterpart to runc.
A quick heads-up, I don’t get it working.
It a fork of runc for running applications packaging according to OCI format
however it runs natively on Microsoft Windows and can run either Windows or
Linux Hyper-V isolated containers. It can also run Window process containers,
which are commonly called process isolation in Microsoft’s documentation.
I was curious about the Linux containers as that sounds like it without needing to build upon WSL2 and presumably works by starting a Linux VM.
I built runhcs.exe, uxboot.exe and gcs which is the Guest Container
Services program for Linux.
The bundle from above was copied over to Windows to try to run.
runhcs.exe run bb-1 --shim-log shim.log --vm-log vm.log
This fails because it has no host / utility VM to use, so I looked at setting
up one. I come across uvmboot
uvmboot.exe -gcs lcow --boot-files-path D:\containers --kernel-file vmlinux --root-fs-type vhd -t exec "/bin/sh"
For the kernel, I grabbed the Alpine kernel image that I used with CrosVM and
run the extract-vmlinux script to extract the uncompressed vmlinux file from
it (i.e. a vmlinuz file).
From the the hcsim repository, I followed the instructions to build the Linux guest agent as well as a rootfs.
# Start by grabbing base.tar.gz from busybox - I don't think this is what is needed.
podman run -it docker.io/library/busybox:latest
# In second terminal run
podman export <container-id> --output base.tar
gzip base.tar
make -f Makefile.bootfiles out/rootfs.vhd
Copy the resulting rootfs.vhd to the boot files path mentioned above.
That makefile can also build a out/kernel.bin target however it requires
a IGVM tool which I didn’t follow-up on.
This however didn’t work, eventually it would start and then timeout.
The hcsdiag list command would show that the VM was running.
time="2025-06-14T20:28:54+09:30" level=error msg="failed to connect to entropy socket" error="context deadline exceeded" uvm-id=uvmboot-0
time="2025-06-14T20:28:54+09:30" level=error msg="failed to connect to log socket" error="context deadline exceeded" uvm-id=uvmboot-0
time="2025-06-14T20:28:54+09:30" level=error msg="failed to run UVM" error="failed to connect to entropy socket: context deadline exceeded" uvm-id=uvmboot-0
Even trying to run the container while it says running it would still fail.
runhcs.exe run bb-1 --shim-log shim.log --vm-log vm.log --host uvmboot-0
Looking at the [Test-LCOW-UVM.ps1][5] script to see how they test it, I tried the more complete command line they use:
uvmboot.exe -gcs lcow -fwd-stdout -fwd-stderr -output-handling stdout -boot-files-path d:\containers -root-fs-type vhd -kernel-file vmlinux-mount-scsi d:\containers\rootfs.vhd --tty --exec ash
Unfortunately, the artifacts used in testing are pulled from Azure and are not
public. This would include kernel, vmlinux and a rootfs tarball. The GitHub
Action for the hcsshim project get the boot files by essentially doing this:
az extension add --name azure-devops
az artifacts universal download --organization "https://msazure.visualstudio.com/" --project "ContainerPlatform" --scope project --feed "ContainerPlat-Dev" --name "azurelinux-uvm" --version "*.*.*" --path ./downloaded_artifacts
I didn’t end-up trying a Windows container bundle.