This guide explains how to install and configure MicroShift 4.19 on a Hetzner Cloud server running AlmaLinux 9.5. It includes system setup, container runtime configuration, networking, TLS setup with Certbot, and sample application deployments.
🖥️ 1. Server & System Preparation
1.1 System Details
Verify that you’re running on the correct architecture and OS version.
uname -m && cat /etc/os-release
Expected output:
x86_64
NAME="AlmaLinux"
VERSION="9.5 (Teal Serval)"
...
✅ Confirms your server is running AlmaLinux 9.5 on
x86_64
, which is required for MicroShift 4.19.
1.2 Cgroups and Kernel Settings
Check if cgroup2
is mounted and required subsystems are enabled.
mount | grep cgroup2
cat /proc/cgroups
🛠️
cgroup2
must be active. Ensurememory
andcpuset
controllers are listed. If not, you may need to appendsystemd.unified_cgroup_hierarchy=1
to the GRUB kernel parameters and reboot.
1.3 Disable Swap
Disable swap immediately and permanently.
swapoff -a
sed -i.bak '/swap/d' /etc/fstab
⚠️ Kubernetes-based systems require swap to be disabled for proper memory management. This change also removes swap entries from
/etc/fstab
to persist across reboots.
1.4 Enable Kernel Modules and Sysctl Parameters
Load required modules now and configure them to auto-load at boot.
modprobe overlay
modprobe br_netfilter
modprobe openvswitch
echo -e "overlay\nbr_netfilter\nopenvswitch" > /etc/modules-load.d/microshift.conf
📦 These modules are essential for container networking and Open vSwitch integration.
Now apply necessary sysctl settings and make them persistent:
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.bridge.bridge-nf-call-iptables=1
cat > /etc/sysctl.d/99-microshift.conf <<EOF
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
🌐 Enables IP forwarding and bridge network filtering — required for Kubernetes networking components.
1.5 NetworkManager and Firewall
Install and configure NetworkManager
with OVS plugin, and disable firewalld
.
dnf install -y NetworkManager NetworkManager-ovs
systemctl enable --now NetworkManager
systemctl disable --now firewalld
🔌
NetworkManager
is used for consistent container networking. ❌firewalld
is disabled to prevent it from interfering with Kubernetes-managed iptables rules.
🛠️ 2. Installing MicroShift and TLS
2.1 Add Repositories
Add the necessary repositories for MicroShift and its dependencies.
sudo tee /etc/yum.repos.d/ocp-deps-4.19.repo > /dev/null <<EOF
[openshift-4.19-dependencies]
name=OpenShift 4.19 BETA
baseurl=https://mirror.openshift.com/pub/openshift-v4/x86_64/dependencies/rpms/4.19-el9-beta/
enabled=1
gpgcheck=0
EOF
sudo tee /etc/yum.repos.d/microshift-4.19.repo > /dev/null <<EOF
[microshift-4.19]
name=MicroShift 4.19 BETA RPMs
baseurl=https://mirror.openshift.com/pub/openshift-v4/x86_64/microshift/ocp-dev-preview/latest-4.19/el9/os/
enabled=1
gpgcheck=0
EOF
dnf clean all && dnf makecache
📦 These custom repositories provide the pre-release packages required to install MicroShift 4.19 and its OpenShift dependencies.
2.2 Install MicroShift & Dependencies
Install MicroShift, CRI-O (container runtime), and networking plugins.
dnf install -y microshift cri-o cri-tools containernetworking-plugins
rpm -q microshift microshift-networking cri-o openvswitch3.4
🛠️ These packages form the MicroShift stack. The
rpm -q
lines verify the correct versions were installed.
2.3 Configure Pull Secret
MicroShift uses a pull secret to fetch container images from Red Hat registries.
mkdir -p /etc/crio/
echo '<YOUR_PULL_SECRET_JSON>' > /etc/crio/openshift-pull-secret
chmod 600 /etc/crio/openshift-pull-secret
🔐 Replace
<YOUR_PULL_SECRET_JSON>
with your Red Hat pull secret. This file must have secure permissions to prevent unauthorized access.
2.4 Start Services & Setup kubeconfig
Start the core services and configure access to the cluster via kubectl
.
systemctl enable --now crio
systemctl enable --now microshift
curl -k https://127.0.0.1:6443/healthz
🚀 This starts the container runtime (
crio
) and MicroShift. Thecurl
command checks if the Kubernetes API is reachable.
Now configure your local kubectl
access:
mkdir -p ~/.kube
sudo cp /var/lib/microshift/resources/kubeadmin/kubeconfig ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
kubectl get nodes
kubectl get pods -n kube-system -o wide
📋 These commands copy the
kubeconfig
for the cluster and verify that the node and core pods are running.
2.5 Expose Router via NodePort
Create a NodePort service to expose the OpenShift router to external traffic.
oc create -n openshift-ingress -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: router
namespace: openshift-ingress
spec:
type: NodePort
selector:
ingresscontroller.operator.openshift.io/deployment-ingresscontroller: default
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30080
- name: https
port: 443
targetPort: 443
nodePort: 30443
EOF
🌍 This exposes the ingress router on ports
30080
and30443
, making your apps reachable over HTTP/HTTPS from outside the server.
🔐 2.6 TLS Setup with Certbot
Install Certbot & Cloudflare Plugin
dnf install -y epel-release
dnf install -y snapd
systemctl enable --now snapd.socket
ln -s /var/lib/snapd/snap /snap
snap install --classic certbot
snap set certbot trust-plugin-with-root=ok
snap install certbot-dns-cloudflare
Cloudflare API Credentials
echo "dns_cloudflare_api_token = YOUR_API_TOKEN" > /root/.cloudflare.ini
chmod 600 /root/.cloudflare.ini
Issue Certificate
export PATH=$PATH:/snap/bin
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.cloudflare.ini --dns-cloudflare-propagation-seconds 60 -d "microshift.josephvillalba.com" -d "*.microshift.josephvillalba.com" --agree-tos -m "[email protected]" --no-eff-email
🌐 2.7 NGINX Reverse Proxy
dnf install -y nginx
/etc/nginx/conf.d/microshift.conf
:
upstream microshift_router {
server 127.0.0.1:30080;
}
server {
listen 80;
server_name microshift.josephvillalba.com *.microshift.josephvillalba.com;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name microshift.josephvillalba.com *.microshift.josephvillalba.com;
ssl_certificate /etc/letsencrypt/live/microshift.josephvillalba.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/microshift.josephvillalba.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:30080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}
systemctl enable --now nginx
setsebool -P httpd_can_network_connect 1
chcon -Rt httpd_cert_t /etc/letsencrypt/live
🧩 2.8 Configure MicroShift TLS Integration
kubectl create namespace demo
oc project demo
kubectl -n openshift-ingress delete secret wildcard-microshift --ignore-not-found
kubectl -n openshift-ingress create secret tls wildcard-microshift --cert=/etc/letsencrypt/live/microshift.josephvillalba.com/fullchain.pem --key=/etc/letsencrypt/live/microshift.josephvillalba.com/privkey.pem
Edit /etc/microshift/config.yaml
:
apiServer:
advertiseAddress: "95.217.163.238"
namedCertificates:
- certPath: "/etc/letsencrypt/live/microshift.josephvillalba.com/fullchain.pem"
keyPath: "/etc/letsencrypt/live/microshift.josephvillalba.com/privkey.pem"
names:
- "microshift.josephvillalba.com"
- "*.microshift.josephvillalba.com"
ingress:
certificateSecret: wildcard-microshift
systemctl restart microshift
🌈 3. Deploy Hello World App
nginx-hello.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: hello-index
namespace: demo
data:
index.html: |
<html><body style="font-family:sans-serif;text-align:center;">
<h1>Hello from <b>Nginx</b> on MicroShift 4.19!</h1>
</body></html>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-hello
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: nginx-hello
template:
metadata:
labels:
app: nginx-hello
spec:
containers:
- name: nginx
image: nginxinc/nginx-unprivileged:stable-alpine
ports:
- containerPort: 8080
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
- name: cache
mountPath: /var/cache/nginx
volumes:
- name: html
configMap:
name: hello-index
- name: cache
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: nginx-hello-svc
namespace: demo
spec:
selector:
app: nginx-hello
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: nginx-hello-route
namespace: demo
spec:
host: hello.microshift.josephvillalba.com
to:
kind: Service
name: nginx-hello-svc
port:
targetPort: http
tls:
termination: edge
kubectl apply -f nginx-hello.yaml
Visit: https://hello.microshift.josephvillalba.com
🧪 3.1 Optional: Root-Powered NGINX Pod
apiVersion: v1
kind: ConfigMap
metadata:
name: hello-root-index
namespace: demo
data:
index.html: |
<html><body style="font-family:sans-serif;text-align:center;">
<h1>Hello from <b>Nginx (root)</b> on MicroShift 4.19!</h1>
</body></html>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-root
namespace: demo
spec:
replicas: 1
selector:
matchLabels: { app: nginx-root }
template:
metadata:
labels: { app: nginx-root }
spec:
serviceAccountName: nginx-root # ← uses SA with anyuid
containers:
- name: nginx
image: nginx:stable-alpine # classic image with UID 0
securityContext:
runAsUser: 0 # ← runs as root
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: hello-root-index
---
apiVersion: v1
kind: Service
metadata:
name: nginx-root-svc
namespace: demo
spec:
selector: { app: nginx-root }
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: nginx-root-route
namespace: demo
spec:
host: hello-root.microshift.josephvillalba.com
to:
kind: Service
name: nginx-root-svc
port:
targetPort: http
tls:
termination: edge
kubectl apply -f nginx-root.yaml
kubectl rollout status deploy/nginx-root -n demo
kubectl get pod -n demo -l app=nginx-root
kubectl get route nginx-root-route -n demo -o wide
Visit: https://hello-root.microshift.josephvillalba.com