Policy Engine
    Kubernetes

    Deploy Kyverno on a RamNode VPS

    A complete single-node Kyverno 1.18 deployment on k3s — Pod Security Standards baseline, custom policies, Policy Reporter UI, and a tested webhook-lockout recovery path.

    At a Glance

    ProjectKyverno 1.18.0 (Helm chart 3.7.0)
    Distributionk3s v1.32.4+k3s1 (single node)
    LicenseApache 2.0
    Recommended Plan2 vCPU / 4 GB / 40 GB NVMe
    OSUbuntu 24.04 LTS / Debian 12
    Estimated Setup Time45–60 minutes

    Why Kyverno on a Budget VPS

    • Image registry enforcement — block docker.io if you only allow private/ghcr.io
    • Required labels and annotations (app, owner, cost-center)
    • Auto-generate NetworkPolicy, LimitRange, ResourceQuota per namespace
    • Cluster-wide Pod Security Standards without writing admission controllers
    • Mutation — inject sidecars, default resource requests, or imagePullSecrets automatically
    1

    Prepare the VPS

    Patch + base utilities
    apt update && apt upgrade -y
    apt install -y curl wget gnupg ca-certificates apt-transport-https
    hostnamectl set-hostname k8s-policy-01
    Open firewall (if ufw active)
    ufw allow 22/tcp
    ufw allow 6443/tcp     # k3s API server
    ufw allow 80,443/tcp   # ingress
    ufw allow 10250/tcp    # kubelet metrics
    ufw --force enable
    Sysctls — bump inotify for pod density
    cat <<EOF > /etc/sysctl.d/99-kubernetes.conf
    fs.inotify.max_user_instances = 1024
    fs.inotify.max_user_watches = 524288
    net.ipv4.ip_forward = 1
    net.bridge.bridge-nf-call-iptables = 1
    EOF
    
    modprobe br_netfilter
    echo "br_netfilter" > /etc/modules-load.d/br_netfilter.conf
    sysctl --system

    Default Ubuntu inotify values run out fast once Kyverno's controllers start watching CRDs.

    2

    Install k3s

    Install with reserved resources
    curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.32.4+k3s1 sh -s - \
      --write-kubeconfig-mode=644 \
      --disable-cloud-controller \
      --kubelet-arg=eviction-hard=memory.available<200Mi,nodefs.available<10% \
      --kubelet-arg=system-reserved=memory=300Mi,cpu=200m \
      --kubelet-arg=kube-reserved=memory=300Mi,cpu=200m

    The reserved/eviction flags are critical on 4 GB. Without them the kubelet has no headroom and will start killing system pods first.

    Verify + configure kubectl
    systemctl status k3s
    k3s kubectl get nodes
    k3s kubectl get pods -A
    
    mkdir -p ~/.kube
    cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
    chown $(id -u):$(id -g) ~/.kube/config
    chmod 600 ~/.kube/config
    3

    Install Helm

    Helm 3.x from upstream repo
    curl https://baltocdn.com/helm/signing.asc | gpg --dearmor -o /usr/share/keyrings/helm.gpg
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" \
      > /etc/apt/sources.list.d/helm-stable-debian.list
    apt update
    apt install -y helm
    helm version

    The raw YAML manifest works for testing, but the chart is the only path that exposes the configuration knobs you actually need.

    4

    Install Kyverno (Standalone Profile)

    Add repo
    helm repo add kyverno https://kyverno.github.io/kyverno/
    helm repo update
    helm search repo kyverno/kyverno --versions | head -5
    ~/kyverno/values.yaml
    # Standalone single-node profile for a budget VPS
    
    admissionController:
      replicas: 1
      resources:
        requests: { cpu: 100m, memory: 128Mi }
        limits:   { memory: 384Mi }
    
    backgroundController:
      replicas: 1
      resources:
        requests: { cpu: 50m, memory: 64Mi }
        limits:   { memory: 256Mi }
    
    reportsController:
      replicas: 1
      resources:
        requests: { cpu: 50m, memory: 64Mi }
        limits:   { memory: 256Mi }
    
    cleanupController:
      replicas: 1
      resources:
        requests: { cpu: 50m, memory: 64Mi }
        limits:   { memory: 128Mi }
    
    # Exclude system namespaces so a webhook outage doesn't lock you out
    config:
      resourceFiltersExcludeNamespaces:
        - kube-system
        - kube-public
        - kube-node-lease
        - kyverno
        - cert-manager
    
    features:
      admissionReports: { enabled: true }
      policyReports:    { enabled: true }
    
    webhooksCleanup:
      enable: true
    Install + watch rollout
    helm install kyverno kyverno/kyverno \
      --namespace kyverno --create-namespace \
      --version 3.7.0 \
      -f values.yaml
    
    kubectl -n kyverno rollout status deployment/kyverno-admission-controller
    kubectl -n kyverno rollout status deployment/kyverno-background-controller
    kubectl -n kyverno rollout status deployment/kyverno-reports-controller
    kubectl -n kyverno rollout status deployment/kyverno-cleanup-controller

    If the admission controller crashes on first start with a TLS error, give it 60s — the chart bootstraps its own CA on the first pass.

    5

    Apply Your First Policy

    require-labels.yaml
    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata:
      name: require-labels
    spec:
      validationFailureAction: Enforce
      background: true
      rules:
        - name: check-team-label
          match:
            any:
              - resources:
                  kinds:
                    - Pod
          validate:
            message: "Every pod must carry an 'app.kubernetes.io/managed-by' label."
            pattern:
              metadata:
                labels:
                  app.kubernetes.io/managed-by: "?*"
    Apply + test rejection
    kubectl apply -f require-labels.yaml
    
    # Should be rejected
    kubectl run test --image=nginx
    
    # Should be admitted
    kubectl run test --image=nginx \
      --labels=app.kubernetes.io/managed-by=manual
    
    kubectl delete pod test
    kubectl delete clusterpolicy require-labels
    6

    Pod Security Standards via the Policies Chart

    Install in audit mode first so you can see what would break before turning enforcement on.

    Audit mode (safe)
    helm install kyverno-policies kyverno/kyverno-policies \
      --namespace kyverno \
      --set podSecurityStandard=baseline \
      --set validationFailureAction=Audit
    
    kubectl get clusterpolicyreports
    kubectl get policyreports -A
    Promote to Enforce once clean
    helm upgrade kyverno-policies kyverno/kyverno-policies \
      --namespace kyverno \
      --set podSecurityStandard=baseline \
      --set validationFailureAction=Enforce

    The restricted profile is significantly stricter and will reject most off-the-shelf Helm charts unmodified. Stay on baseline until you have time to deal with the fallout.

    7

    Policy Reporter UI (Optional)

    Install via Helm
    helm repo add policy-reporter https://kyverno.github.io/policy-reporter
    helm repo update
    
    helm install policy-reporter policy-reporter/policy-reporter \
      --namespace policy-reporter --create-namespace \
      --set kyvernoPlugin.enabled=true \
      --set ui.enabled=true \
      --set ui.plugins.kyverno=true
    policy-reporter-ingress.yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: policy-reporter-ui
      namespace: policy-reporter
      annotations:
        traefik.ingress.kubernetes.io/router.entrypoints: websecure
        traefik.ingress.kubernetes.io/router.tls: "true"
    spec:
      rules:
        - host: policy.example.com
          http:
            paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: policy-reporter-ui
                    port:
                      number: 8080
    8

    Tuning & Upgrades

    • Drop the cleanup controller if you never use cleanup policies — saves ~50 MB. Set cleanupController.enabled=false.
    • Watch the reports controller — on busy clusters with ephemeral pods, policyreports accumulate quickly. Prune via cron.
    • Tune webhook timeouts — default 10s. Lower to 5s via admissionController.tuning.webhookTimeoutSeconds if the API server complains about latency.
    • Pin image versions in your values file before any production cutover.
    Standard upgrade procedure
    helm repo update
    helm upgrade kyverno kyverno/kyverno \
      --namespace kyverno \
      --version <new-version> \
      -f values.yaml
    
    helm upgrade kyverno-policies kyverno/kyverno-policies \
      --namespace kyverno \
      --version <new-version>

    CVE-2026-4789 disabled HTTP by default in namespaced policies. Upgrading from 1.16 or earlier? Audit any namespaced Policy that fetches data over HTTP — those calls now require explicit opt-in.

    9

    Webhook Lockout Recovery

    Every Kyverno operator should rehearse this. If all Kyverno pods are unavailable (OOM, node reboot, image pull failure), the webhooks will reject every admission request — including the ones needed to bring Kyverno back up.

    Delete webhook configs to bypass
    kubectl delete validatingwebhookconfiguration \
      kyverno-policy-validating-webhook-cfg \
      kyverno-resource-validating-webhook-cfg \
      kyverno-cleanup-validating-webhook-cfg \
      kyverno-exception-validating-webhook-cfg \
      kyverno-global-context-validating-webhook-cfg \
      kyverno-ttl-validating-webhook-cfg
    
    kubectl delete mutatingwebhookconfiguration \
      kyverno-policy-mutating-webhook-cfg \
      kyverno-resource-mutating-webhook-cfg \
      kyverno-verify-mutating-webhook-cfg

    Once the admission controller is healthy again it re-registers the webhooks automatically. Excluding kube-system and the kyverno namespace (in your values file) prevents this in most realistic failure modes.

    10

    Troubleshooting Checklist

    1. kubectl -n kyverno get pods — are all four controllers running?
    2. kubectl -n kyverno logs deploy/kyverno-admission-controller --tail=200 — TLS or webhook registration errors?
    3. kubectl get validatingwebhookconfigurations -o yaml | grep -A2 failurePolicy — on a single-node setup, Fail will lock you out during restarts.
    4. kubectl describe clusterpolicy <name> — Kyverno reports rule compilation failures here.
    5. kubectl get events -A --sort-by='.lastTimestamp' | tail -50 — admission rejections show up here with policy name and resource.

    If the admission controller pod will not start at all, 90% of the time it is a memory limit set too low. Bump it to 512Mi and try again.

    Backup Considerations

    • k3s state — embedded SQLite at /var/lib/rancher/k3s/server/db/. Stop k3s, snapshot, restart.
    • Policy YAMLs — should live in git. If not: kubectl get clusterpolicies,policies -A -o yaml > policies-backup.yaml.
    • Helm values files — should also live in git.