Embedded containerd and crun
By default KubeSolo runs its own container runtime, bundled in the binary: containerd 2.2.5 with crun 1.26 as the OCI runtime and the CNI plugins v1.9.0. It keeps its state under /var/lib/kubesolo/containerd, listens on /var/lib/kubesolo/containerd/containerd.sock, and links /run/containerd/containerd.sock to it so standard tools find it.
Any OCI image that runs under Docker or Podman runs here. Inspect it with crictl or ctr pointed at that socket.
kubesoloctl install stop if Docker is installed or running. For a laptop with Docker, use container mode.Attaching a host runtime
If the host already manages containerd or CRI-O, KubeSolo can attach to it instead of starting its own:
The value must be an absolute socket path or a unix:// URL. With an endpoint set, the host owns the runtime, the OCI runtime, the CNI plugin binaries in its own plugin directory, the sandbox image and any registry configuration. KubeSolo still writes its bridge CNI configuration to /etc/cni/net.d/10-bridge.conflist, and warns at startup if the runtime reports that its network is not ready.
/run/containerd/containerd.sock, which includes a host containerd. kubesoloctl install leaves a host-owned socket alone. Install, set runtime.endpoint, then restart KubeSolo.With an external runtime, containers keep running across a KubeSolo restart. That differs from the embedded runtime, where a restart recreates pods.
Container mode
KubeSolo can run inside a container instead of on the host. This is for development and CI. The simplest way is kubesoloctl install --run-mode=container, which also handles the kubeconfig and published ports.
Container mode turns on automatically when KubeSolo finds /.dockerenv, /run/.containerenv or a non-empty container environment variable. Force it on or off with runtime.containerMode. The published image sets --container-mode. In container mode KubeSolo:
- uses the
cgroupfsdriver and sets up cgroup controller delegation; - remounts
/asrsharedso volume mounts propagate into pods; - disables per-QoS cgroups and relaxes eviction and image garbage collection thresholds, so the host's disk usage does not evict pods;
- gives pods an empty node
resolv.conf, and CoreDNS forwards to1.1.1.1and8.8.8.8; - leaves conntrack sysctls alone, since
/proc/sysis often read-only; - refuses the static CPU manager policy.
Running the image directly
Release images are portainer/kubesolo:<version>, for example portainer/kubesolo:v1.2.1, built for amd64, arm64, arm and riscv64. There is no latest tag.
The kubeconfig inside the container points at the container's own address. Copy it out and point it at the published port:
Publish any NodePort or LoadBalancer ports your workloads need with extra -p options when you create the container.
CPU pinning
By default every pod shares every CPU. Latency-sensitive workloads such as audio processing, motion control and machine vision see that as jitter. The kubelet's static CPU manager policy gives qualifying pods exclusive cores that no other pod may use:
| Setting | Default | Meaning |
|---|---|---|
| kubernetes.kubelet.cpuManager.policy | none | static enables exclusive cores. |
| kubernetes.kubelet.cpuManager.reservedCPUs | "0" under static | Cpuset of CPU indexes held back for the host and KubeSolo. 0-1 reserves two CPUs. |
| kubernetes.kubelet.cpuManager.policyOptions | {} | full-pcpus-only, strict-cpu-reservation, distribute-cpus-across-numa, prefer-align-cpus-by-uncorecache. The last two cannot be combined. |
| kubernetes.kubelet.systemReserved | {} | Quantities instead of indexes, such as cpu=1. The kubelet then chooses which cores. If both are set, reservedCPUs wins and KubeSolo warns. |
A pod gets exclusive cores only if every container has Guaranteed QoS (CPU and memory requests equal to limits) and a whole-number CPU value. A pod that does not qualify still runs, silently in the shared pool, so verify from inside the container:
The static policy needs a host with at least two CPUs and is not supported in container mode. KubeSolo has no scheduler, so a pod that does not fit is rejected by the kubelet rather than rescheduled. Changing the policy only needs a restart; KubeSolo removes the kubelet's cpu_manager_state file for you.
Exclusive cores stop other pods interfering, not the kernel. For deterministic latency also isolate the cores (isolcpus, nohz_full, rcu_nocbs), steer IRQs away from them, and confine KubeSolo itself with CPUAffinity=. The full guide is in the repository: docs/configuration/cpu-pinning.md.
d2k: a Docker-compatible API
KubeSolo can embed d2k, Portainer's Docker-to-Kubernetes API translator. With it enabled, the node exposes a Docker-compatible API over mTLS on port 2376, and Docker calls become Kubernetes resources in one namespace. Existing Docker CLIs, scripts and CI jobs can target a KubeSolo node unchanged.
KubeSolo reuses its own CA to mint a d2k server and client certificate under /var/lib/kubesolo/pki/d2k/, deploys d2k 1.2.3 with its RBAC and a d2k-tls Secret, and exposes it through a LoadBalancer Service.
- Architectures: amd64 and arm64 only. On arm and riscv64, d2k disables itself with a warning.
- Needs the LoadBalancer:
d2k.enabledwithnetwork.loadBalancer.enabled: falseis rejected at startup. - Namespace is fixed after first start: the server certificate names the namespace. To change it, delete
/var/lib/kubesolo/pki/d2k/server.crtandserver.keybefore restarting.
Connecting from your machine
The files are root-owned on the node, so copy them as a user who can read them. In container mode, kubesoloctl d2k fetch does all of this for you. docker ps lists the pods in the d2k namespace as containers; the d2k README has the full translation table.