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:

sh
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 unexpectedly

What 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:

  1. First, upgrade to an elogind version that supports delegation
  2. Second, enable delegation, since it is not on by default
  3. 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

  1. Make sure you are running elogind
    • If you are not using elogind, then this guide is unfortunately not for you.
  2. Make sure your elogind is at least version 257.15

Part two: enable elogind cgroup delegation

  1. Create a config file at /etc/elogind/logind.conf.d/20-delegate-session-cgroups.conf with the following content:
[Login]
DelegateSessionCgroups=yes
  1. 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:
elogind[699]: Config file reloaded.

Part three: create delegated cgroup and add rootless Docker to it

  1. Save this script and run it every time before starting the Minikube cluster:
sh
#!/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
  1. 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 default

You 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.