ivinco
Kubernetes Runtime Security: Seccomp, AppArmor and Falco

Kubernetes Runtime Security: Seccomp, AppArmor and Falco

Stanislav LevchenkoStanislav Levchenko··Updated

Kubernetes runtime security protects workloads while they run. It does three jobs:

  • it limits what a container's processes can do;
  • it detects suspicious behavior, such as a shell opened inside a production container;
  • it stops pods that break your rules from being admitted at all.

It complements build-time checks like image scanning, which can only find known problems before deployment.

This guide covers three areas:

  • built-in controls: seccomp and AppArmor, which come with Linux and Kubernetes;
  • runtime detection: Falco and Tetragon;
  • policy enforcement: Pod Security Admission, ValidatingAdmissionPolicy, OPA Gatekeeper and Kyverno.

For cluster-wide hardening of the API server, RBAC, network and secrets, see our Kubernetes security best practices checklist.

What is runtime security?

Runtime security covers a workload from admission until it stops. Image scanning checks what you ship; runtime controls decide whether it may start, then watch what it does:

  • which system calls it makes;
  • which files it reads;
  • which processes it spawns;
  • where it connects.

Some threats only show up at runtime: a compromised dependency that opens a reverse shell, a cryptominer dropped into a running container, an attacker trying to break out to the node.

In the 4C's model, this is the Container layer. The usual runtime threats:

  • Container escape: a process uses a kernel bug or a privileged setting to reach the host.
  • Privilege escalation inside the container: setuid binaries or extra Linux capabilities.
  • Unexpected processes: shells, package managers or miners in a container that should run one binary.
  • Credential theft: reading service account tokens, cloud metadata or sensitive files.
  • Lateral movement: a compromised pod scanning and connecting to other services.

Seccomp: limit system calls

Seccomp (secure computing mode) is a Linux kernel feature that filters the system calls a process can make. Containers share the host's kernel, so every allowed system call is a door into it. Fewer calls mean a smaller attack surface.

A seccomp profile is a JSON file with a default action and a list of system calls with their own actions:

  • Allowlist: with SCMP_ACT_ERRNO as the default, any call not on the list fails with an error.
  • Denylist: with SCMP_ACT_ALLOW as the default, only the listed calls are blocked.
  • Other actions: a blocked call can also kill the process or only be logged, depending on the action you choose.

This allowlist lets a process write, exit and nothing else:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],
  "syscalls": [
    {
      "names": ["write", "exit", "exit_group"],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

A container with this profile won't even start, because the runtime itself needs execve and more. Real applications need far more calls than that. A practical approach is to start from your container runtime's default profile and tighten it for sensitive workloads.

In Kubernetes, seccomp has been stable since 1.19 and is set in the security context. There are three profile types:

TypeWhat it does
RuntimeDefaultThe container runtime's default profile, a good baseline
LocalhostA profile file stored on the node
UnconfinedNo filtering; this is the default unless you change it
apiVersion: v1
kind: Pod
metadata:
  name: seccomp-demo
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: registry.example.com/app:1.4.2
    securityContext:
      seccompProfile:
        type: Localhost
        localhostProfile: profiles/app.json

To make RuntimeDefault the default for every pod on a node, start the kubelet with --seccomp-default, which has been stable since 1.27. Test it first: some workloads need system calls that the default profile blocks.

AppArmor: limit files, capabilities and network access

AppArmor is a Linux security module that confines a program to the files, capabilities and network access listed in its profile. It works on distributions that ship it, such as Ubuntu and Debian. Systems that use SELinux instead set it through the seLinuxOptions field of the security context.

This profile, from the Kubernetes documentation, denies all file writes:

#include <tunables/global>

profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
  #include <abstractions/base>

  file,

  # Deny all file writes.
  deny /** w,
}

AppArmor support has been stable since Kubernetes 1.31, and since 1.30 it is set with the appArmorProfile field. The old container.apparmor.security.beta.kubernetes.io/<container> annotation is deprecated; move it to this field.

apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor
spec:
  securityContext:
    appArmorProfile:
      type: Localhost
      localhostProfile: k8s-apparmor-example-deny-write
  containers:
  - name: hello
    image: busybox:1.28
    command: ["sh", "-c", "echo 'Hello AppArmor!' && sleep 1h"]

The profile has to be loaded on every node where the pod can run. If it isn't, the pod is created but stays pending.

Kubernetes runtime security tools: Falco and Tetragon

Seccomp and AppArmor block behavior you have ruled out in advance. Detection tools tell you when something unusual happens anyway.

Falco graduated in the CNCF in February 2024. It watches system calls and checks them against rules. Its default driver is a modern eBPF probe built into Falco, so on a recent kernel there is nothing to compile on the node. It runs as a DaemonSet, one pod per node, and installs with Helm:

helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
helm install --replace falco --namespace falco --create-namespace --set tty=true falcosecurity/falco

Among others, the default rules flag a shell opened in a container ("Terminal shell in container", which fires on an interactive kubectl exec -it) and reads of sensitive files by untrusted programs. Tune the rules to your workloads, and send alerts to a system someone watches rather than a log nobody reads.

Tetragon, part of the Cilium project, also uses eBPF. It can enforce as well as observe. When a policy matches, it can make the call fail or send the process a SIGKILL, inline in the kernel.

Policy enforcement: stop risky pods at admission

Admission control decides which pods may run at all. Use the built-in controls first, then add custom rules.

Pod Security Admission has been stable since 1.25. It applies the privileged, baseline or restricted standard per namespace. Our security checklist shows how to roll it out.

ValidatingAdmissionPolicy has been stable since 1.30. You write custom rules in CEL, and they run inside the API server, with no webhook to operate. This policy rejects pods whose containers come from any registry but yours:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: trusted-registry
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE", "UPDATE"]
      resources: ["pods"]
  validations:
  - expression: "object.spec.containers.all(c, c.image.startsWith('registry.example.com/'))"
    message: "Images must come from registry.example.com"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: trusted-registry
spec:
  policyName: trusted-registry
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        registry-policy: enforced

The binding applies the policy only to namespaces labeled registry-policy: enforced, so system namespaces keep working. Start with validationActions: [Warn, Audit] to see what would be rejected. A complete policy would also check initContainers and ephemeralContainers, and match the pods/ephemeralcontainers subresource.

OPA Gatekeeper runs Open Policy Agent, a CNCF project that graduated in 2021, as an admission webhook. Policies are written in Rego. Since OPA 1.0, Rego requires the if and contains keywords. A rule for plain OPA as an admission controller, requiring every namespace to have labels, looks like this:

package kubernetes.admission

deny contains msg if {
	input.request.kind.kind == "Namespace"
	not input.request.object.metadata.labels
	msg := "every namespace must have labels"
}

Gatekeeper wraps rules like this in ConstraintTemplates, so one template can be reused with different parameters.

Kyverno graduated in the CNCF in March 2026. Policies are Kubernetes resources written in YAML or CEL. Besides validating, Kyverno can mutate resources, generate defaults and verify image signatures.

A sensible order: Pod Security Admission, ValidatingAdmissionPolicy and MutatingAdmissionPolicy first. Add Kyverno or Gatekeeper when you need image verification, resource generation or a ready-made policy library.

Isolate untrusted workloads

Some workloads run code nobody on your team wrote: customer plugins, CI jobs, AI-generated scripts. A RuntimeClass can send them to a sandboxed runtime:

  • gVisor intercepts system calls in a user-space kernel;
  • Kata Containers runs each pod in a lightweight virtual machine.

Both add a second boundary between the workload and the host.

Runtime security tools at a glance

ToolBlocks or detectsRuns as
SeccompBlocks system calls outside the profileKernel feature, set in the security context
AppArmor, SELinuxBlocks file, capability and network access outside the profileKernel module, profile loaded on each node
FalcoDetects suspicious system calls and alertsDaemonSet with an eBPF probe
TetragonDetects and can block calls inline in the kernelDaemonSet with eBPF, part of Cilium
Pod Security Admission, ValidatingAdmissionPolicyBlocks non-compliant pods at admissionBuilt into the API server
Kyverno, OPA GatekeeperBlocks or changes resources at admissionAdmission webhook in the cluster
gVisor, Kata ContainersIsolates untrusted workloads from the host kernelSandboxed runtime selected by RuntimeClass

Kubernetes runtime security checklist

  1. Enforce the baseline or restricted Pod Security Standard on every application namespace.
  2. Run containers as non-root, drop all capabilities and block privilege escalation.
  3. Use the RuntimeDefault seccomp profile everywhere, and custom profiles for sensitive workloads.
  4. Load AppArmor or SELinux profiles on the nodes and reference them in pods.
  5. Run Falco or Tetragon on every node, and route alerts to someone who acts on them.
  6. Block images from unknown registries and verify signatures at admission.
  7. Sandbox untrusted code with gVisor or Kata Containers.
  8. Correlate runtime alerts with the Kubernetes audit log to see who did what.
  9. Deny pod traffic by default with NetworkPolicy, so a compromised pod can't reach the rest of the cluster.

Need runtime controls set up on your clusters? Our Kubernetes consulting team starts with read-only access and a written list of what to fix first. We harden the clusters; for a SOC 2 or ISO 27001 audit you still need an auditor.

Frequently Asked Questions

What is runtime security in Kubernetes?

Runtime security protects containers while they run. It limits what their processes can do with seccomp and AppArmor. It detects suspicious behavior, such as unexpected shells or file reads, with tools like Falco. It also blocks non-compliant pods at admission. It catches threats that image scanning can't see before deployment.

How is runtime security different from image scanning?

Image scanning checks what you ship, before deployment, for known vulnerabilities and misconfigurations. Runtime security watches what the container does after it starts: system calls, processes, file access and network connections. You need both, because a clean image can still be exploited through a new bug or a stolen credential.

Do runtime security tools need an agent on every node?

Most do. Falco and Tetragon run as a DaemonSet, one pod per node, because they read system calls from each node's kernel through eBPF. Some managed and serverless node types limit what you can install, so check what your provider allows before you pick a tool.

What is the difference between seccomp and AppArmor?

Seccomp filters which system calls a process may make. AppArmor restricts which files, capabilities and network operations a program may use, based on a profile loaded on the node. They work at different layers and are often used together. Kubernetes sets both in the pod's security context.

What is the difference between OPA Gatekeeper and Kyverno?

Both are admission policy engines. Gatekeeper runs Open Policy Agent with the Rego language, which suits teams that already use OPA elsewhere. Kyverno writes policies as Kubernetes resources in YAML or CEL. Both can validate and mutate; Kyverno adds resource generation and image verification. For simple rules, the built-in ValidatingAdmissionPolicy may be enough.