Blog · August 19, 2025 · Updated on September 7, 2026 · 11 min read

Kubernetes DaemonSet Explained

Close-up of a pulled-out server module in front of a row of identical racks in a data center
Photo: panumas nikhomkhai / Pexels

A Kubernetes DaemonSet makes sure that exactly one Pod from a given template runs on every node in your cluster. When a new node joins, Kubernetes starts the Pod there automatically. When a node leaves, its Pod disappears with it. You don't specify a number of replicas, the nodes themselves determine the count.

You need this whenever an application should not run somewhere in the cluster, but everywhere: log shippers, monitoring agents, network components, security scanners, storage drivers. Anything that watches or extends the state of the individual machine belongs in a DaemonSet.

I run Kubernetes clusters in production and wrote the Kubernetes Practical Guide for Rheinwerk Computing (2024). In my clusters, DaemonSets run for metrics, logs, runtime security and storage, and most of the mistakes in this article are ones I have run into in my own operations or in client projects. You can find all my Kubernetes articles on the Kubernetes page.

What a DaemonSet in Kubernetes does

The DaemonSet controller looks at every node in the cluster and checks each one: is a Pod from my template already running there? If not, it creates one. If a Pod is running that should not be, it deletes it. That's the same reconciliation between desired and actual state you know from the ReplicaSet, except the target count doesn't come from a replicas field, it follows from the list of matching nodes.

The manifest looks nearly identical to a Deployment: apiVersion: apps/v1, a selector, a Pod template. The difference is kind: DaemonSet and the missing replicas field. That similarity is exactly what makes the manifests so easy to read. If you can write a Deployment, you can write a DaemonSet.

A familiar example from the cluster itself: in many setups, kube-proxy runs as a DaemonSet, and so does the network plugin. Without those Pods, the node would have no connection to the rest of the cluster. That already shows the kind of program that belongs in a DaemonSet: services that matter to the node, not to individual users. The official description is in the Kubernetes documentation on DaemonSets.

Kubernetes DaemonSet vs Deployment: which one to use

This is the question I get most often on the topic. The short answer: a Deployment answers "how many?", a DaemonSet answers "where?". With a Deployment you say "three Pods, wherever," and the scheduler places them based on free resources. With a DaemonSet you say "one Pod per node," and the distribution is fixed by that.

Deployment DaemonSet
Number of Pods replicas, set by you one per matching node, set by the cluster
Scaling dial replicas up or down only via the node count or a node filter
New node joins nothing happens until the scheduler places something there the Pod starts there immediately
Typical use web servers, APIs, workers log shippers, monitoring agents, CNI, CSI, security agents
Access to the node rarely needed often needed: hostPath, hostNetwork, hostPort
Default update rolling update across replicas rolling update node by node

A Deployment is the right choice for anything your users interact with. A DaemonSet is the right choice for anything the cluster needs to know about itself or do on every machine. If you're considering running a normal application as a DaemonSet just so it runs on every node, that's almost always a detour. A Deployment with Pod anti-affinity spreads Pods too, without tying you to the node count.

In my clusters on Hetzner Cloud, the DaemonSets are always the same four candidates: the Node Exporter for Prometheus, the log shipper for Loki, Falco for runtime monitoring, and the node part of the CSI driver that attaches volumes to the machine. None of them would make sense as a Deployment, because each one looks after exactly one machine.

A DaemonSet as YAML: example with the Node Exporter

The following manifest rolls out the Prometheus Node Exporter to every node. It reads CPU, memory, disk and network of the machine and exposes the values as metrics. For that it needs access to the node's file system, hence the hostPath mount, and it should be reachable under the node's own IP address, hence hostNetwork.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
  labels:
    app: node-exporter
spec:
  selector:
    matchLabels:
      app: node-exporter
  template:
    metadata:
      labels:
        app: node-exporter
    spec:
      hostNetwork: true
      hostPID: true
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
      containers:
        - name: node-exporter
          image: quay.io/prometheus/node-exporter:latest
          args:
            - --path.rootfs=/host
          ports:
            - containerPort: 9100
              name: metrics
          volumeMounts:
            - name: root
              mountPath: /host
              readOnly: true
      volumes:
        - name: root
          hostPath:
            path: /

The latest tag is only there to keep the example timeless. In a real cluster, pin a fixed version, or you might get a different version after the next node restart than on the other nodes. The tolerations block makes sure the Pod also lands on control-plane nodes, more on that in a moment.

After kubectl apply -f node-exporter.yaml, check the result with a couple of commands:

kubectl get daemonset -n monitoring
kubectl get pods -n monitoring -o wide
kubectl rollout status daemonset/node-exporter -n monitoring

The first output shows the columns DESIRED, CURRENT, READY and NODE SELECTOR. If DESIRED is lower than your number of nodes, a taint or a node filter is excluding some. The second output with -o wide shows which node each Pod landed on, the fastest way to spot a missing node.

Tolerations: getting a DaemonSet onto control-plane nodes

Before rolling out a DaemonSet, ask yourself which nodes the Pod should actually run on. In most cluster setups, control-plane nodes carry a taint that keeps normal Pods away. A monitoring agent or log shipper should run there anyway, since without it you see nothing from your most important machines.

The fix is tolerations in the Pod template. They tell the scheduler: the Pod is allowed to ignore this taint. For the control plane, it looks like this, with the older master key as a second entry in case your cluster still uses it:

tolerations:
  - key: node-role.kubernetes.io/control-plane
    operator: Exists
    effect: NoSchedule
  - key: node-role.kubernetes.io/master
    operator: Exists
    effect: NoSchedule

A detail many people don't know: the DaemonSet controller adds several tolerations to every Pod on its own, including the taints for not-ready, unreachable, disk-pressure and memory-pressure. The reason is logical: a Pod that watches the node must not be evicted from it just because the node is under load right now, or you'd lose visibility at exactly the moment you need it. The full list is in the section on taints and tolerations for DaemonSets in the documentation.

Take a look yourself: kubectl get pod <name> -o yaml shows more entries under tolerations than you wrote in the manifest. If you don't know about this, the first comparison between manifest and running Pod is a surprise.

nodeSelector: DaemonSet on specific nodes only

A DaemonSet doesn't have to run on every node. With a nodeSelector in the Pod template, you restrict it to nodes carrying certain labels. Typical cases: a GPU driver only on nodes with a graphics card, a storage agent only on nodes with local disks, an agent only on Linux nodes in a mixed cluster.

spec:
  template:
    spec:
      nodeSelector:
        kubernetes.io/os: linux
        node-role.example.com/storage: "true"

The controller factors the selector into its count: DESIRED then equals the number of nodes carrying all the labels. Label another node to match later, and the Pod starts there without any further action from you. Remove the label, and the Pod terminates. For more complex rules, such as "on every node except those with label X," use a nodeAffinity with requiredDuringSchedulingIgnoredDuringExecution instead of nodeSelector, the syntax is the same as for any other Pod.

From practice: I rarely use the selector, because my clusters are homogeneous. Where I do use it is the CSI driver, which should only run on nodes that actually get volumes attached. Everything else is free to run on any node.

Updates, priority and reachability in operation

A DaemonSet updates itself by rolling update by default: change the image in the template, and the controller swaps the Pods node by node. How many nodes may be without a Pod at once is controlled by maxUnavailable under updateStrategy.rollingUpdate, with a default of one node. The alternative, OnDelete, only swaps Pods once you delete them by hand, useful for components you want to verify node by node, such as a network plugin.

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1

Since DaemonSet Pods matter more to the node than normal application Pods, give them a higher priorityClassName. Under memory pressure, Kubernetes then evicts the less important Pods first, and your log shipper stays up. For node-critical components there's the built-in class system-node-critical; for your own agents, a dedicated PriorityClass above the application Pods is worth setting up.

One point that is easy to miss with log shippers and metrics agents: the DaemonSet gives you one Pod per node, but it does not limit what that Pod collects. If the agent's configuration discovers targets cluster-wide, every instance discovers the same targets, and you end up with the same logs or metrics as many times as you have nodes. Restricting an agent to its own node belongs in the agent's configuration, usually as a filter on the node name, which you pass into the Pod from spec.nodeName as an environment variable via the Downward API.

That leaves the question of how other services reach a DaemonSet Pod. Four patterns come up. Push: the Pod ships its own data out, like a log shipper delivering to Loki, and isn't reachable from outside at all. Node IP with hostPort or hostNetwork: the Pod is reachable at the node's address, which is how Prometheus scrapes the Node Exporter. Service: works like for any application, though the Service spreads requests across Pods, so you don't reach a specific node's Pod on purpose. Headless Service: gives you the addresses of all Pods via DNS if you want to address them individually.

There's an honest alternative too: you could install the agents directly on the nodes via systemd. But then you lose logs, metrics, rolling updates and the YAML manifest the cluster uses to re-establish the state itself. In my projects, the DaemonSet has won every time because of that, even for software that was originally built for the host.

Frequently asked questions

What is the difference between a DaemonSet and a Deployment?

A Deployment runs a number of Pods you specify, somewhere in the cluster. A DaemonSet runs exactly one Pod on every matching node, and the count follows from the number of nodes. Deployments are for applications, DaemonSets for services that look after each machine individually.

How do I restrict a DaemonSet to specific nodes?

With a nodeSelector or a nodeAffinity in the Pod template. The controller then only starts Pods on nodes whose labels match, and adjusts the Pod count automatically as you add or remove labels.

Why doesn't my DaemonSet run on the control-plane nodes?

Because in most setups those nodes carry a taint with the NoSchedule effect. Add a toleration for node-role.kubernetes.io/control-plane to the Pod template, and the Pod lands there too. Check with kubectl describe node which taints your nodes actually have.

Can I scale a DaemonSet?

Not through a replica count. A DaemonSet has at most one Pod per node. More Pods only come from more nodes, fewer from a tighter node filter. If you need several Pods per node, the DaemonSet is the wrong object.

How do I update a DaemonSet without downtime?

With the default strategy RollingUpdate and maxUnavailable: 1, Kubernetes swaps the Pods node by node. Each node is missing its agent only for the duration of one Pod restart, nothing more. Watch progress with kubectl rollout status daemonset/<name>, and roll back with kubectl rollout undo.

Where to go next

The two biggest use cases for DaemonSets are monitoring and logging. How the Node Exporter from the example above becomes a complete monitoring setup together with Prometheus and Grafana is covered in Kubernetes Monitoring with Prometheus. How a log shipper as a DaemonSet collects every node's container logs and ships them to Loki is covered in Kubernetes Logging with Loki and Grafana. And if the difference to a Deployment still isn't quite clear, read Kubernetes Deployment: Rollouts Explained first.

The practical next step: take the Node Exporter manifest above, roll it out in your cluster, and compare kubectl get nodes with kubectl get daemonset. If the numbers don't match, you now know where to look for taints and labels.

In full detail, from your first DaemonSet to the PriorityClass, this is covered in chapter 5 of my Kubernetes Practical Guide (Rheinwerk Computing).

Kevin Welter

Kevin Welter

Developer, IT architect, author of technical books (Kubernetes, cloud infrastructures) and speaker. Runs his business with an AI workforce of eight AI employees and shows solo business owners in his community how to hire their first AI employee.

More about AI employees

Kubernetes from the basics to a production-ready cluster

Get the book