Helm charts declared in k0s config: Flannel, Traefik, cert-manager, Prometheus

Published: 2026-04-10

One of k0s's underused features is declaring Helm chart installations directly in /etc/k0s/k0s.yaml. Instead of running helm install after the cluster starts, you list the charts under spec.extensions.helm and k0s reconciles them automatically on startup.


How it works

The k0s controller includes a Helm controller. When it reads the cluster config, it creates Chart custom resources in kube-system for each entry under extensions.helm.charts. The Helm controller installs or upgrades the chart — same as FluxCD's HelmRelease controller, but without needing FluxCD.

yamlspec:
  extensions:
    helm:
      concurrencyLevel: 5
      repositories:
        - name: flannel
          url: https://flannel-io.github.io/flannel
        - name: traefik
          url: https://traefik.github.io/charts
        - name: jetstack
          url: https://charts.jetstack.io
        - name: prometheus-community
          url: https://prometheus-community.github.io/helm-charts
      charts:
        - name: flannel
          chartname: flannel/flannel
          namespace: kube-flannel
          order: 1
        - name: traefik
          chartname: traefik/traefik
          namespace: traefik
          order: 2
        - name: cert-manager
          chartname: jetstack/cert-manager
          namespace: cert-manager
          order: 3
        - name: kube-prometheus-stack
          chartname: prometheus-community/kube-prometheus-stack
          namespace: monitoring
          order: 4

order controls installation sequence:

  • 1 — Flannel (CNI). Nothing reaches Running without a working network.
  • 2 — Traefik. Needs network.
  • 3 — cert-manager. Its webhook must be ready before any Certificate objects are created.
  • 4 — Prometheus. Needs everything above.

Flannel

Flannel is the CNI. Backend is vxlan — wraps pod traffic in UDP datagrams, works without special kernel routing. Pod CIDR: 10.244.0.0/16.

yamlvalues: |
  podCidr: "10.244.0.0/16"
  flannel_backend_type: "vxlan"

No Calico or Cilium. Single node, no node-to-node routing to manage.


Traefik

yamlvalues: |
  hostNetwork: true
  service:
    type: ClusterIP
  ports:
    web:
      redirectTo:
        port: websecure
  ingressRoute:
    dashboard:
      enabled: false
  providers:
    kubernetesCRD:
      enabled: true
    kubernetesIngress:
      enabled: true

hostNetwork: true lets Traefik bind directly to host ports 80 and 443 — no cloud LoadBalancer needed. ClusterIP means no NodePort. HTTP immediately redirects to HTTPS.

Dashboard is disabled — it's not needed and exposes internal routing information.


cert-manager

yamlvalues: |
  installCRDs: true
  global:
    leaderElection:
      namespace: cert-manager

installCRDs: true — Certificate, ClusterIssuer, and related CRDs are installed as part of the Helm chart. Without this, you'd need to apply CRDs separately before the chart, breaking the single-config approach.

After cert-manager is ready, create a ClusterIssuer for Let's Encrypt:

yamlapiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            class: traefik

Prometheus (kube-prometheus-stack)

yamlvalues: |
  prometheus:
    prometheusSpec:
      retention: 7d
      storageSpec:
        emptyDir:
          medium: ""
          sizeLimit: 5Gi
  alertmanager:
    enabled: false
  prometheus-pushgateway:
    enabled: false
  grafana:
    enabled: true

persistentVolume: false / emptyDir — on a VPS with limited disk, a PVC for Prometheus isn't worth it. If the pod restarts, you lose recent history. Acceptable for personal monitoring.

alertmanager: false and pushgateway: false save memory. kube-state-metrics and node-exporter are enabled — pod/node metrics visible in Grafana and Beszel.


Checking chart status

bashk0s kubectl get charts.helm.k0sproject.io -A

Example output:

NAMESPACE    NAME                   VERSION   STATUS    ...
kube-system  flannel                0.26.0    deployed
kube-system  traefik                32.1.0    deployed
kube-system  cert-manager           v1.16.0   deployed
kube-system  kube-prometheus-stack  68.0.0    deployed

Status: deployed, pending-install, failed. First place to look when a chart fails.

Get events for a specific chart:

bashk0s kubectl describe chart.helm.k0sproject.io cert-manager -n kube-system

Upgrading charts

Change the version: field in /etc/k0s/k0s.yaml:

yaml- name: traefik
  chartname: traefik/traefik
  version: "33.0.0"

Then restart k0scontroller:

bashk0s stop
k0s start

Or reload the config without full restart:

bashk0s config update /etc/k0s/k0s.yaml

Limitations

  • No helm diff before apply — k0s applies blindly on startup
  • No rollback UI — manual helm rollback in the chart's namespace
  • No support for OCI registry charts (as of k0s v1.30)
  • Values must be inline YAML in k0s.yaml — no separate values files

For complex charts with many values, managing inline YAML gets unwieldy. At that point, move to FluxCD HelmReleases.


What can go wrong

Chart fails to install and k0s keeps retrying. k0s applies chart specs on every startup. If a chart errors, check logs: journalctl -u k0scontroller | grep -i helm. Fix the values in k0s.yaml and restart: systemctl restart k0scontroller.

Values YAML in k0s.yaml breaks the entire config. k0s.yaml is parsed as YAML; an indentation error in the inline values block will prevent k0s from starting. Always validate with k0s config validate before restarting the controller.

Chart version drift. k0s installs charts from a HelmRepository on first start. If you don't pin the version: field, a cluster rebuild gets a different chart version than the original. Always pin chart versions.


Summary

  • Charts declared in spec.extensions.helm in k0s.yaml are installed automatically on cluster startup — no separate helm install step
  • This replaces the need for FluxCD for the initial cluster bootstrap layer (Flannel, Traefik, cert-manager, Prometheus)
  • Limitations: no helm diff, no rollback UI, no OCI registry support (as of k0s v1.30)
  • Pin chart versions explicitly; leaving version unpinned means a cluster rebuild gets whatever is current in the repo at that moment
  • For charts with many values or complex per-environment overrides, move to FluxCD HelmReleases instead