Getting Started
Install Falco on kind, trigger a sample alert, explore the rule language, try Tetragon's K8s-aware detection
Getting Started
This page installs Falco on a local cluster, triggers detections, writes a custom rule, then compares with Tetragon for the modern eBPF-first approach.
Prerequisites
kind create cluster --name runtime-sec
kubectl cluster-info --context kind-runtime-seckind runs containers, which means we can install Falco that watches the host kernel.
Install Falco
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
helm install falco falcosecurity/falco \
--namespace falco --create-namespace \
--set tty=true \
--set driver.kind=ebpfFalco runs as a DaemonSet — one pod per node. It uses eBPF probes by default (less invasive than the kernel module).
kubectl get pods -n falco
# Wait for Running
# Stream Falco's alerts (these appear in stdout)
kubectl logs -n falco -l app.kubernetes.io/name=falco -fTrigger an Alert
Falco ships with a baseline ruleset. Trigger one of its rules:
# Run a pod
kubectl run nginx --image=nginx
kubectl wait --for=condition=ready pod/nginx --timeout=30s
# Exec into it — Falco rule "Terminal shell in container" fires
kubectl exec -it nginx -- bash -c "ls /etc/shadow"In the Falco log:
22:34:12.123: Notice A shell was spawned in a container with an attached
terminal (user=root user_loginuid=-1 container_id=abc123
container_name=nginx image=nginx:latest shell=bash
parent=runc cmdline=bash -c ls /etc/shadow)
22:34:12.140: Warning Sensitive file opened for reading by non-trusted
program (file=/etc/shadow command=cat container_name=nginx
image=nginx)Two rules fired: shell-in-container and sensitive-file-read. The forensic signal is detailed: container, image, command, user, parent process.
Explore the Rule Language
Falco rules are YAML. Show the default rules:
kubectl exec -it -n falco $(kubectl get pod -n falco -o name | head -1) -- \
cat /etc/falco/falco_rules.yaml | head -100Each rule has:
- rule: Terminal shell in container
desc: A shell was spawned in a container with an attached terminal
condition: >
spawned_process and container
and shell_procs
and proc.tty != 0
and container_entrypoint
and not user_expected_terminal_shell_in_container_conditions
output: >
A shell was spawned in a container with an attached terminal
(user=%user.name user_loginuid=%user.loginuid %container.info
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline
terminal=%proc.tty container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [container, shell, mitre_execution, T1059]Fields:
- condition: a filter expression on events. Predefined macros like
shell_procs,container_entrypoint. - output: format string for the alert.
- priority: severity (DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL).
- tags: searchable categories, including MITRE ATT&CK mappings.
Write a Custom Rule
# custom-rules.yaml
- macro: trusted_internal_ranges
condition: (fd.cip in (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16))
- rule: Egress to External Network from Production
desc: A production pod made a connection to a non-internal IP
condition: >
outbound and container
and not trusted_internal_ranges
and k8s.ns.name = "production"
and not fd.sip in (allowed_external_destinations)
output: >
Production pod connected to external IP (pod=%k8s.pod.name
namespace=%k8s.ns.name image=%container.image.repository
destination_ip=%fd.sip destination_port=%fd.sport
command=%proc.cmdline)
priority: WARNING
tags: [network, exfiltration, production]Apply it via Helm or by mounting into the pod:
kubectl create configmap falco-custom-rules \
--from-file=custom-rules.yaml \
-n falco
helm upgrade falco falcosecurity/falco \
-n falco \
--set 'customRules.custom-rules\.yaml'="$(cat custom-rules.yaml)"Now any production pod connecting outside your internal ranges (and not in allowed_external_destinations) triggers an alert.
Route Alerts Somewhere Useful
Falco logs to stdout by default. For production:
# Falco's falcosidekick — fans out to many backends
helm upgrade falco falcosecurity/falco -n falco \
--set falcosidekick.enabled=true \
--set falcosidekick.config.slack.webhookurl="https://hooks.slack.com/..." \
--set falcosidekick.config.elasticsearch.hostport="http://es:9200" \
--set falcosidekick.config.kafka.hostport="kafka:9092"falcosidekick supports dozens of backends: Slack, PagerDuty, OpenSearch, Splunk, Kafka, AWS Security Hub, GCP Pub/Sub, Loki, etc. Route by severity: NOTICE to Slack, WARNING to PagerDuty, CRITICAL to incident response.
Try Tetragon (Different Approach)
Tetragon is Cilium-family, more K8s-aware, supports prevention (not just detection):
helm repo add cilium https://helm.cilium.io
helm install tetragon cilium/tetragon -n kube-systemWatch events:
kubectl exec -it -n kube-system ds/tetragon -c tetragon -- \
tetra getevents -o compactNow apply a Tetragon policy:
# tetragon-block-curl.yaml
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "block-curl"
spec:
kprobes:
- call: "sys_execve"
syscall: true
args:
- index: 0
type: "string"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values: ["/usr/bin/curl"]
matchActions:
- action: Sigkillkubectl apply -f tetragon-block-curl.yaml
# Try to curl
kubectl exec -it nginx -- curl http://example.com
# Process killedTetragon kills the process synchronously — detection and prevention in one. Powerful but careful: a wrong rule kills legitimate processes too.
For most teams: Falco for detection + Tetragon for selected high-risk events you want to actively block.
Compare
| Falco | Tetragon | |
|---|---|---|
| Approach | Userspace rule engine reading eBPF events | Kernel-side filtering with selective userspace |
| Default mode | Detect | Detect; can prevent |
| Rule language | YAML with macros | TracingPolicy CRDs (Kubernetes-native) |
| K8s integration | Good (labels, namespaces) | Excellent (pod identity from Cilium) |
| Maturity | Highest, large community | Newer but stable |
| Best for | Audit, SIEM feed, broad detection | Targeted prevention; Cilium users |
Both fine; Falco is the safer first install. Tetragon shines if you want kernel-side blocking.
Cleanup
kind delete cluster --name runtime-secWhat's Next
- Patterns — rule tuning, SIEM integration, MITRE mapping, blocking, multi-cluster
- Best Practices — alert fatigue, response runbooks, compliance, performance