Every eBPF runtime security product prompts the same question from evaluating teams: must deployment changes reach our container images? The answer rests on the tool's deployment topology. The two options are sidecars and DaemonSets, and they involve different trade-offs. Documentation does not always make the better choice clear.
This post examines both patterns step by step, shows why eBPF's kernel-level design favors DaemonSets for efficiency, and explains when sidecars remain appropriate.
The Sidecar Model
In a sidecar setup, a security agent container shares a Kubernetes pod with the application container. By default, both containers use the pod's network namespace. For process visibility, the sidecar can also use the pod's PID namespace, which requires shareProcessNamespace: true in the pod spec.
Adding sidecars to existing workloads without changing Dockerfiles requires a Kubernetes admission webhook. A MutatingAdmissionWebhook intercepts pod creation requests and adds the sidecar container spec before scheduling. Service mesh proxies such as Envoy and Linkerd use this mechanism, as do some security tools for agent deployment.
Sidecars offer meaningful benefits in certain scenarios. If each service needs distinct per-pod policy settings, the sidecar keeps that configuration explicit and portable within the pod spec. If Kubernetes spans clusters with different kernel versions, sidecars reduce exposure to kernel compatibility issues. Some compliance frameworks also favor sidecars because instrumentation is declared in each workload's manifest.
The trade-offs are significant. Every sidecar adds CPU and memory use to its pod. With 40 pods on a node, the sidecar design creates 40 agent processes. If the admission webhook is unavailable when a pod is created, that pod can run without instrumentation, leaving coverage gaps that may be hard to spot. Aggregating logs is also more involved when every application pod has a companion container with its own output stream.
The DaemonSet Model
A DaemonSet places one pod on each node. Oligo runs as a DaemonSet in the oligo-system namespace. Its agent pod has host PID namespace access, so it can enumerate every node process through /proc and receive eBPF events keyed by PID.
kubectl apply -f https://install.oligosec.org/daemonset.yaml
# Resources created:
# - DaemonSet: oligo-agent (oligo-system namespace)
# - ClusterRole: oligo-agent (get/list nodes, pods, namespaces)
# - ServiceAccount: oligo-agent
# Kernel requirements:
# - Linux 4.14+ for kprobe support
# - Linux 5.8+ for BPF ring buffer (preferred)
The DaemonSet agent attaches eBPF programs to kprobes, tracepoints, and LSM hooks once for each kernel. Those programs run for every node process, independent of its container or pod. A BPF ring buffer collects events from all processes. The user-space agent reads that stream and adds container identity by looking up the PID's cgroup path in a pre-populated BPF map.
Resolving container identity is straightforward because each containerized process has a cgroup path containing the pod, namespace, and container names. In Kubernetes using containerd or CRI-O, the hierarchy resembles /system.slice/containerd.service/kubepods/besteffort/pod<uid>/<container-id>. At process creation, the DaemonSet agent reads this path and records the mapping in a BPF map keyed by PID. Later events from that PID automatically include the complete workload identity.
Why DaemonSets Fit eBPF Better
The central point is that eBPF operates at the kernel level. Each node has one kernel, and kprobe attachment occurs at a kernel function rather than at a process. A kprobe on sys_enter_execve captures every execve syscall on the node, from any process, through one BPF program attached once.
A sidecar design does not alter that reality. Even with 40 sidecar agents on one node, each remains a userspace process receiving kernel events through its own perf buffer or ring buffer file descriptor. That does not create 40 observation points. It creates 40 consumers of the same kernel event stream, each handling some node PIDs and adding the resulting coordination overhead.
The resource comparison is simple. A DaemonSet agent on a node with 50 pods uses resources once for that node. A sidecar agent on the same node uses resources 50 times. For teams tracking pod density and node costs, the distinction is substantial.
Coverage Guarantees Differ
The DaemonSet model delivers unconditional coverage for every process on a node. A pod cannot lose coverage because someone omitted a sidecar injection annotation or because the webhook was unavailable during deployment. When a pod lands on a node running the Oligo DaemonSet, it is instrumented.
That distinction is especially important for security tools. A control with gaps caused by deployment configuration mistakes offers weaker guarantees than one whose coverage follows node scheduling. Runtime detection is intended to show what is actually running, rather than only what someone remembered to configure for monitoring.
When Sidecars Still Fit
The sidecar model is not inherently wrong. Sidecars are established, well-supported choices for application performance monitoring, distributed tracing, and service mesh scenarios. When compliance requires per-workload agent attestation, the sidecar's explicit entry in the pod spec can be an advantage.
For runtime security, DaemonSets are the appropriate default. The kernel is the observation layer, so attaching to it once per node instead of N times per pod is more than an optimization. It matches eBPF's actual architecture.
If your Kubernetes environment has unusual constraints, evaluate sidecars: multi-tenant clusters where the DaemonSet cannot access the host PID namespace, highly varied kernel versions across node pools, or strict network policies blocking agent egress. For most teams running standard Kubernetes workloads on Linux 4.14+, the DaemonSet route provides complete coverage with zero Dockerfile changes and one deployment manifest.