You are viewing the development docs which are in progress. There is no guarantee that the development documentation will be accurate, including instructions, links, and other information. For the latest stable documentation, click here.
A Minimal Single-Node Kubernetes with Kubeadm
Kairos is all about giving you the power to customize your operating system just the way you need it—declaratively, reproducibly, and predictably. Today, we’re walking through how to build and boot a Kairos image using the provider-kubeadm to set up a Kubernetes cluster with kubeadm
.
This guide is focused on a simple use case: booting a single-node Kubernetes cluster with role init
, version v1.30.0
.
🧱 What Is provider-kubeadm
?
The provider-kubeadm is a binary for Kairos that integrates with Kubernetes’ kubeadm
bootstrap process. It translates the familiar kubeadm
configuration into a Kairos-compatible cloud-init YAML, wrapping everything in a reproducible and declarative boot process.
With this provider, we can fully define Kubernetes cluster parameters—including API server args, scheduler, networking, and etcd configuration—right inside a Kairos #cloud-config
block.
🔧 Building the Image
We start with a Kairos base image—here, Ubuntu 24.04 Core—and layer on everything kubeadm
needs: containerd, kubelet, kubectl, and the kubeadm binary. Also we will download and set the agent-provide-kubeadm to handle the kubeadm
configuration.
Here’s the Dockerfile used to construct the image:
FROM quay.io/kairos/ubuntu:24.04-core-amd64-generic-v3.4.2
# Add Kubernetes apt repository
RUN curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
RUN echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
# Install required packages
RUN apt-get update && apt-get install -y --no-install-recommends \
socat \
conntrack \
containerd \
runc \
kubelet \
kubeadm \
kubectl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Copy the provider into place
RUN mkdir -p /system/providers && curl -L https://github.com/kairos-io/provider-kubeadm/releases/download/v4.7.0-rc.4/agent-provider-kubeadm-v4.7.0-rc.4-linux-amd64.tar.gz | tar -xz -C /system/providers/
Or with the modern Kairos Factory method:
FROM quay.io/kairos/kairos-init:v0.5.1 AS kairos-init
FROM ubuntu:24.04
ARG VERSION=1.0.0
RUN --mount=type=bind,from=kairos-init,src=/kairos-init,dst=/kairos-init /kairos-init --version "${VERSION}"
RUN curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
RUN echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
# Install required packages
RUN apt-get update && apt-get install -y --no-install-recommends \
socat \
conntrack \
containerd \
runc \
kubelet \
kubeadm \
kubectl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /system/providers && curl -L https://github.com/kairos-io/provider-kubeadm/releases/download/v4.7.0-rc.4/agent-provider-kubeadm-v4.7.0-rc.4-linux-amd64.tar.gz | tar -xz -C /system/providers/
☁️ The Cloud-Config
Here’s the heart of the system—a Kairos-compatible #cloud-config
YAML. This config installs the OS, sets up kernel and containerd parameters, and passes the full kubeadm
configuration block to the provider-kubeadm
.
#cloud-config
install:
device: auto
auto: true
reboot: true
cluster:
cluster_token: "random_token"
control_plane_host: "1.1.1.1"
role: init
config: |
clusterConfiguration:
apiServer:
extraArgs:
advertise-address: 0.0.0.0
anonymous-auth: "true"
audit-log-maxage: "30"
audit-log-maxbackup: "10"
audit-log-maxsize: "100"
audit-log-path: /var/log/apiserver/audit.log
authorization-mode: RBAC,Node
default-not-ready-toleration-seconds: "60"
default-unreachable-toleration-seconds: "60"
disable-admission-plugins: AlwaysAdmit
enable-admission-plugins: AlwaysPullImages,NamespaceLifecycle,ServiceAccount,NodeRestriction
profiling: "false"
secure-port: "6443"
tls-cipher-suites: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,...
extraVolumes:
- hostPath: /var/log/apiserver
mountPath: /var/log/apiserver
name: audit-log
pathType: DirectoryOrCreate
timeoutForControlPlane: 10m0s
controllerManager:
extraArgs:
feature-gates: RotateKubeletServerCertificate=true
profiling: "false"
terminated-pod-gc-threshold: "25"
use-service-account-credentials: "true"
etcd:
local:
dataDir: /etc/kubernetes/etcd
extraArgs:
listen-client-urls: https://0.0.0.0:2379
kubernetesVersion: v1.30.0
networking:
podSubnet: 192.168.0.0/16
serviceSubnet: 192.169.0.0/16
initConfiguration:
nodeRegistration:
kubeletExtraArgs:
event-qps: "0"
feature-gates: RotateKubeletServerCertificate=true
protect-kernel-defaults: "true"
read-only-port: "0"
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
event-qps: "0"
feature-gates: RotateKubeletServerCertificate=true
protect-kernel-defaults: "true"
read-only-port: "0"
kubeletConfiguration:
authentication:
anonymous: {}
webhook: { cacheTTL: 0s }
x509: {}
authorization:
webhook:
cacheAuthorizedTTL: 0s
cacheUnauthorizedTTL: 0s
cpuManagerReconcilePeriod: 0s
logging:
flushFrequency: 0
options:
json:
infoBufferSize: "0"
verbosity: 0
stages:
initramfs:
- name: pre-kubeadm
sysctl:
net.ipv4.conf.default.rp_filter: 0
net.ipv4.conf.all.rp_filter: 0
net.bridge.bridge-nf-call-ip6tables: 1
net.bridge.bridge-nf-call-iptables: 1
net.ipv4.ip_forward: 1
kernel.panic: "10"
kernel.panic_on_oops: "1"
vm.overcommit_memory: "1"
modules:
- br_netfilter
- overlay
files:
- path: /etc/containerd/config.toml
permissions: "0644"
content: |
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.k8s.io/pause:3.8"
- path: /etc/hosts
permissions: "0644"
content: |
127.0.0.1 localhost
users:
kairos:
passwd: kairos
groups:
- sudo
commands:
- ln -s /etc/kubernetes/admin.conf /run/kubeconfig
- mkdir -p /etc/kubernetes/manifests
cluster.role
is set toinit
, so this node bootstraps the control plane. For a multi-node setup, change this tojoin
and provide discovery options.
🔄 What’s Next?
This is a minimal setup, but it lays the groundwork for more advanced clusters. From here, you can:
- Customize the CNI (via the containerd config or an additional stage),
- Inject manifests into
/etc/kubernetes/manifests
, - Scale to multiple nodes with
join
configurations and token-based discovery.
And, of course, all of this benefits from the immutability and reproducibility that Kairos brings to the OS layer.
If you want to see more examples or contribute to the provider-kubeadm
, check out the GitHub repo or hop into our community channels.
Let us know how you’re bootstrapping Kubernetes with Kairos—we’d love to feature your use case!