In this tutorial, we'll learn how to install Kubernetes on Debian 13 with Cilium (eBPF), secrets encryption, kubeadm and containerd. Complete production guide.
Introduction
We build a Kubernetes cluster on Debian 13 LTS using kubeadm and containerd, then enhance it with eBPF-based networking through Cilium. Instead of relying on traditional iptables-based networking, we use Cilium to leverage eBPF in the Linux kernel for improved performance, observability, and security.
We also implement foundational hardening measures, including Pod Security enforcement and encryption of Kubernetes Secrets at rest. These steps ensure that sensitive data is protected inside etcd and that workloads follow security best practices by default.
Prerequisites
Before we begin, ensure we have the following:
- An Debian 13 on dedicated server or KVM VPS.
- Basic Linux Command Line Knowledge.
Environment Requirements
Minimum per control plane node:
- 2 CPU
- 2GB RAM (4GB recommended)
- 20GB disk
Step 1: Prepare Debian 13
Update system:
sudo apt update && sudo apt upgrade -y
Disable swap (required):
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
Kubernetes scheduler assumes memory is not swapped. If swap is on, pod scheduling becomes unreliable.
Load required kernel modules:
sudo modprobe overlay
sudo modprobe br_netfilter
Persist them:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
Set sysctl values:
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
These settings allow Kubernetes to manage network traffic between pods correctly.
Step 2: Install containerd
Kubernetes does not run containers by itself. It needs a runtime.
We use containerd because:
- It is lightweight
- It is recommended for production
- It works perfectly with kubeadm
sudo apt install -y containerd
Generate default config:
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
Enable systemd cgroup driver:
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
Restart:
sudo systemctl restart containerd
sudo systemctl enable containerd
Why change cgroup driver?
Debian 13 uses systemd. Kubernetes must match the same cgroup driver or we get instability.
Step 3: Install Kubernetes Packages
Create keyring directory:
sudo mkdir -p /etc/apt/keyrings
Download signing key (adjust version if needed):
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.35/deb/Release.key \
| sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
Add repository:
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.35/deb/ /" \
| sudo tee /etc/apt/sources.list.d/kubernetes.list
Install:
sudo apt update
sudo apt install -y kubeadm kubelet kubectl
sudo apt-mark hold kubeadm kubelet kubectl
Because automatic upgrades can break clusters. We upgrade manually in production.
Note: Only above commands need to execute on both master node and worker node. After this all below commands need to execute on master node only.
Step 4: Initialize Control Plane
Run on master node:
sudo kubeadm init
At the end, it gives a kubeadm join command. Save it.
After completion, configure kubectl:
mkdir -p $HOME/.kube
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Verify:
kubectl get nodes
Node will show NotReady until networking is installed.
Step 5: Join Worker Nodes
On worker nodes, run the join command displayed by kubeadm.
sudo kubeadm join 192.168.1.10:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>
After joining:
kubectl get nodes
Step 6: Install Cilium (eBPF Networking)
We use Cilium because:
- It uses eBPF (modern Linux kernel technology)
- Better performance than traditional iptables
- Built-in network security
Download CLI:
curl -L --remote-name https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz
sudo tar xzvf cilium-linux-amd64.tar.gz -C /usr/local/bin
Install Cilium:
cilium install
Check status:
cilium status
Nodes should now become Ready.
Now check nodes:
kubectl get nodes
They should become Ready.
What is eBPF doing here?
Instead of using iptables for networking, Cilium programs the Linux kernel directly using eBPF. This makes networking faster, scalable, and more secure.
Step 7: Configure crictl
Let’s configure it crictl:
Create config file:
sudo nano /etc/crictl.yaml
Add following content:
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
Save and exit the file.
Now run:
sudo crictl ps -a
Step 8: Enable Secrets Encryption at Rest
By default, Kubernetes stores secrets unencrypted in etcd. We now enable encryption properly.
Generate Encryption Key
head -c 32 /dev/urandom | base64
Copy the output. Create Encryption Configuration File
sudo nano /etc/kubernetes/encryption.yaml
Add following content:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: PASTE_BASE64_KEY_HERE
- identity: {}
Save and exit the file.
Set permissions:
sudo chmod 600 /etc/kubernetes/encryption.yaml
sudo chown root:root /etc/kubernetes/encryption.yaml
Mount Encryption File in API Server Static Pod
Edit:
sudo nano /etc/kubernetes/manifests/kube-apiserver.yaml
Add flag under command section:
- --encryption-provider-config=/etc/kubernetes/encryption.yaml
Add under volumeMounts:
volumeMounts:
- mountPath: /etc/kubernetes/encryption.yaml
name: encryption-config
readOnly: true
Add under volumes:
volumes:
- name: encryption-config
hostPath:
path: /etc/kubernetes/encryption.yaml
type: File
Save file.
Kubelet automatically restarts the API server.
Re-encrypt Existing Secrets
Run:
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
Now secrets are encrypted at rest.
Step 9: Apply Basic Security Hardening
Enable Pod Security (restricted mode):
kubectl label ns default \
pod-security.kubernetes.io/enforce=restricted
This prevents:
- Privileged containers
- Root containers
- Dangerous capabilities
Create default deny network policy:
sudo nano deny.yaml
Add following content:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Apply:
kubectl apply -f deny.yaml
Verify Cluster Health
Check:
kubectl get nodes
kubectl get pods -A
cilium status
Conclusion
We have successfully seen how to install Kubernetes on Debian 13 with Cilium. Modern networking and essential security controls enabled from the start. The cluster uses containerd for runtime stability, Cilium for high-performance eBPF networking, and encryption at rest to protect sensitive data stored in etcd.
More importantly, we have established a security-first baseline. Pod Security standards prevent insecure workloads from running. Network isolation policies restrict unnecessary communication. Secrets are no longer stored in plaintext. These are not optional enhancements; they are foundational practices for responsible infrastructure design.
