Minikube with rootless Docker on Void Linux
In this post I would like to document my struggles in getting Minikube to run with the rootless Docker driver on Void Linux in the hopes of helping others who want to achieve a similar setup. I am assuming that you already have a running rootless Docker daemon, you are using Void's native minikube package and that you are following Minikube's official docs for getting started.
I will first explain the issue I ran into and its cause, and then provide a step-by-step solution. Click here to skip straight to the solution steps.
The problem
Attempting to start the local cluster:
minikube start --driver=docker --container-runtime=containerd Results in errors:
+ echo 'ERROR: UserNS: cpu controller needs to be delegated'
ERROR: UserNS: cpu controller needs to be delegated
+ exit 1: container exited unexpectedly
PUs=2, Memory=3900MB) ...
iner. Running "minikube delete" may fix it: creating host:
create: creating: create kic node: container name "minikube":
log: 2026-06-24T13:26:33.659847132Z
+ grep -qw cpu /sys/fs/cgroup/cgroup.controllers
+ echo 'ERROR: UserNS: cpu controller needs to be delegated'
ERROR: UserNS: cpu controller needs to be delegated
+ exit 1: container exited unexpectedlyWhat is causing it
Minikube with rootless docker needs a thing called cgroup controller delegation, which is by default not enabled on Void Linux.
What is cgroup controller delegation
Cgroups are a way to set resource limits on processes. Cgroup controller delegation basically means that non-root users are allowed to set cgroup controllers -- the things that limit resources -- for processes. Minikube needs this, I assume, for pod resource quotas.
So then why not just enable it?
Minikube has docs on how to enable cgroup controller delegation... for systemd. Void Linux doesn't have systemd, so it should be even simpler, right? Just write some magic value to some magic sysfs path and everything should just snap in place... right?
Where it gets hairy
On a true minimalist Void Linux setup,
this might have been just as simple as that.
However, some of us -- including me -- run the elogind
session manager in order to take advantage of some application's features
that rely on it (e.g. mounting USB disks without sudo,
switching WiFi networks without
special user groups, etc).
Some seat management daemons -- such as elogind and its upstream
brother, systemd's own logind --
also rely on cgroups, although in a bit of an unexpected way.
The thing about cgroups is that they are hierarchical. Each cgroup can contain processes xor child cgroups, much like unix processes can have children. The crucial detail is that the cgroups tree exists independently of the process tree. A process may become orphaned from its parent, but it will keeps its cgroup. For this reason, seat management daemons use cgroups as a reliable way to "tag" processes, making little or no use of the resource limiting functionality of the cgroups API.
So, what is the holdup? The issue is the internal processes constraint, which means that a cgroup may contain either child processes or child cgroups, not both (remember when I said "xor" before?).
What this means, essentially, is that Minikube is fighting with elogind:
elogind has already created a cgroup and pumped it full of processes
to keep track of.
Minikube, on the other hand, needs and empty cgroup so that
it can create a child cgroup for its own internal use.
The solution: delegation
Delegation is what systemd came up with to solve this issue. It lets an unprivileged user create new cgroups and manage them without being blocked by the seat manager daemon. Luckily, a similar feature has been merged into elogind quite recently, allowing us to make use of delegation on systemdless distros such as Void Linux.
The solution then, becomes clear:
- First, upgrade to an elogind version that supports delegation
- Second, enable delegation, since it is not on by default
- Once per boot, run a script that creates a new delegated cgroup with the appropriate controllers needed by Minikube, and puts the rootless docker daemon into it.
The practical solution
Had enough theory? Just want to get things working? Without further ado, lets begin!
Part one: get the correct elogind version
- Make sure you are running elogind
- If you are not using elogind, then this guide is unfortunately not for you.
- Make sure your elogind is at least version 257.15
- At the time of writing, the official Void package is too old. If this is still the case at the time of reading, you can use my void packages repo to install a suitable version.
- There have been reports of issues
with newer versions of
elogind, so upgrade at your own caution!
Part two: enable elogind cgroup delegation
- Create a config file at
/etc/elogind/logind.conf.d/20-delegate-session-cgroups.confwith the following content:
[Login]
DelegateSessionCgroups=yes- Apply the configuration by either rebooting, or running
sudo loginctl daemon-reload- If you have
socklog set up,
you should
see a message like this in
svlogtail:
- If you have
socklog set up,
you should
see a message like this in
elogind[699]: Config file reloaded.Part three: create delegated cgroup and add rootless Docker to it
- Save this script and run it every time before starting the Minikube cluster:
#!/bin/sh
eecho () { echo " $ @" >&2; }
die () { eecho " $ @" ; exit 1; }
[ " $( id -u ) " -ne 0 ] || die "do not run this script as root"
[ -n " $ XDG_SESSION_ID " ] || die "XDG_SESSION_ID is not set"
cgroup_root ="/sys/fs/cgroup"
session_cgroup =" $ cgroup_root / $ XDG_SESSION_ID "
delegated_cgroup =" $ session_cgroup /delegated/k8s-rootless-docker"
dockerpid =" $( pgrep --newest --exact dockerd --uid " $( id -u ) " ) " \
|| die "could not find the PID of rootless dockerd process"
[ -n " $ dockerpid " ] || die "dockerd PID is empty"
eecho "Rootless dockerd PID: $ dockerpid "
eecho "Rootless dockerd cgroup:"
eecho
cat /proc/" $ dockerpid " /cgroup >&2 \
|| die "could not read dockerd's cgroup membership"
eecho
mkdir -p " $ delegated_cgroup " \
|| die "could not create delegated cgroup directory: $ delegated_cgroup "
echo "+cpu +cpuset +io +memory +pids" \
| sudo tee " $ cgroup_root /cgroup.subtree_control" \
|| die "could not enable cgroup controllers in the root cgroup"
echo "+cpu +cpuset +io +memory +pids" \
| tee " $ session_cgroup /cgroup.subtree_control" \
|| die "could not enable cgroup controllers in the session cgroup"
echo " $ dockerpid " \
| tee -a " $ delegated_cgroup /cgroup.procs" \
|| die "could not move dockerd into the delegated cgroup"
eecho
eecho "Success."
eecho "Rootless dockerd cgroup is now:"
eecho
cat /proc/" $ dockerpid " /cgroup >&2 \
|| die "could not read dockerd's updated cgroup membership" The output should look something like this:
Rootless dockerd PID: 1443
Rootless dockerd cgroup:
1:name=elogind:/
0::/1/session
Password:
+cpu +cpuset +io +memory +pids
+cpu +cpuset +io +memory +pids
1443
Success.
Rootless dockerd cgroup is now:
1:name=elogind:/
0::/1/delegated/k8s-rootless-docker- You should now be able to start Minikube!
[0] maybetree @ orth: ~/proj/learn-k8s/helloworld
>>> minikube start --driver=docker --container-runtime=containerd
😄 minikube 1.27.0 on Void
❗ Kubernetes 1.25.0 has a known issue with resolv.conf. minikube is using a workaround that should work for most use cases.
❗ For more information, see: https://github.com/kubernetes/kubernetes/issues/112135
✨ Using the docker driver based on existing profile
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
🔄 Restarting existing docker container for "minikube" ...
📦 Preparing Kubernetes v1.25.0 on containerd 1.6.8 ...
🔗 Configuring CNI (Container Networking Interface) ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟 Enabled addons: storage-provisioner, default-storageclass
❗ /usr/bin/kubectl is version 1.36.2, which may have incompatibilites with Kubernetes 1.25.0.
▪ Want kubectl v1.25.0? Try 'minikube kubectl -- get pods -A'
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by defaultYou will need to run the script every time your start minikube.
Other quirks
Here I would like to mention two other quirks that I have run into when using minikube. I am not sure whether these are at all related to rootless Docker or elogind, but I think it might be useful to highlight my workarounds for them regardless.
minikube image build doesn't work
minikube image build is meant to allow building Docker images directly
inside Minikube.
For me, it seemed to work correctly -- even showing
the built image inside Minikube's
image store -- up until the point that a deployment tried pulling
the image.
The workaround for me is to build the image normally,
docker image save it into a tar archive,
and minikube image load the archive into the cluster.
Not very efficient, but hey -- your images are only
a few dozen MB in size, right? 😉
minikube service doesn't work either
minikube service $SERVICE --url=true is meant to expose
a service in the cluster to the outside network,
but this doesn't work for me either,
likely due to some rootless container networking gotchas.
I've found that kubectl port-forward svc/$SERVICE $HOST_PORT:$POD_PORT
does the job instead.
Closing thoughts
I am now probably more knowledgeable on cgroups controller delegation than 99% of the population, and so are you. I am sure that when life flashes before my eyes on my deathbed, this will be one of the moments that passes by and puts into perspective just how ephemeral our existence is and just how little time each one of us is allotted in this world. Perhaps in the future I will spend some more of that little precious time to put together that "true minimalist Void" setup I mentioned and ditch seat management completely -- after all, the BSD people seem mostly fine without it. But for now, I will simply enjoy my shiny updated elogind and continue with doing the local cluster experiments I have originally set out to do.