Applying seccomp in Kubernetes via the seccompProfile field on securityContext, its evolution from alpha annotations, and its relation to Pod Security Standards. Including custom profile distribution across nodes and the seccomp-profiles-operator.

In episode 9 you locked down services directly on the host. In a Kubernetes cluster the challenge is bigger: many nodes, many pods, and different runtimes. But the principle is the same — a pod runs containers that share the node's kernel, and dangerous syscalls must be pruned. The difference is that Kubernetes adds one layer of abstraction: seccompProfile in securityContext.
Episode 10 covers how to apply seccomp in Kubernetes: the seccompProfile field, its evolution from alpha annotations, Pod Security Standards, distributing custom profiles across nodes, and seccomp-profiles-operator.
Kubernetes has a long history with seccomp. Originally (long before version 1.19), the only way was an alpha annotation:
apiVersion: v1
kind: Pod
metadata:
name: legacy-app
annotations:
seccomp.security.alpha.kubernetes.io/pod: "runtime/default"
spec:
containers:
- name: app
image: busybox
command: ["sleep", "3600"]This annotation was fragile: no schema validation, hard to apply per-container, and its alpha status meant it could change at any time. That's why Kubernetes introduced a proper seccompProfile field in securityContext (beta since 1.19, GA since 1.25). Since then, seccomp has been treated as infrastructure — not an experiment.
Note
If you find old documentation or Helm charts still using the seccomp.security.alpha.kubernetes.io/pod annotation, plan a migration to the seccompProfile field. The old annotation still reads for compatibility, but it's not the recommended way for new configuration.
The securityContext.seccompProfile.type field accepts three values:
Unconfined — no seccomp filter. This is the historical Kubernetes default; avoid it unless you're truly confident.RuntimeDefault — the container runtime's built-in profile. With Docker/containerd that's the docker-default from episode 8: a deny-list of dangerous syscalls maintained by the community.Localhost — a custom profile as a JSON file present on every node, referenced via localhostProfile.The precedence rules are simple: a pod-level seccompProfile acts as the default for all containers, and a container can override it with its own seccompProfile. The most specific level wins.
Here's the most common pattern — a pod using the runtime's built-in profile:
apiVersion: v1
kind: Pod
metadata:
name: webapp
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginx:1.27For full control, point to a custom profile stored on the node:
apiVersion: v1
kind: Pod
metadata:
name: webapp-custom
spec:
containers:
- name: nginx
image: nginx:1.27
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/webapp-allowlist.jsonlocalhostProfile is relative to /var/lib/kubelet/seccomp/ on the node. The profile file must exist on all nodes where the pod could be scheduled — this is the source of an operational problem we'll discuss in a moment.
Tip
Start with RuntimeDefault for all workloads. It gives you a solid seccomp baseline at zero operational cost. After that, graduate gradually to Localhost with a purpose-built allow-list for workloads you understand (following the strace baseline flow from episodes 7 and 8).
Kubernetes doesn't stop at the API level — it also standardizes policy. The Pod Security Standards define two levels related to seccomp:
seccompProfile.type: RuntimeDefault for all pods.allowPrivilegeEscalation: false and dropping capabilities.These policies are enforced per-namespace via the pod-security.kubernetes.io/enforce label:
apiVersion: v1
kind: Namespace
metadata:
name: secure-ns
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restrictedPods that don't meet the Restricted level are rejected (because of enforce) or reported in the audit log (because of audit) — the right label pair for a gradual rollout. This is the fastest way to normalize seccomp across a cluster: not relying on individual discipline, but enforcing through the platform.
Here's the real challenge of Localhost: profiles are files on the node filesystem, and those files must be identical on all nodes — including nodes added later. Managing this manually is a recipe for disaster. Three common approaches:
The third approach fits clusters that keep changing. With the operator, profiles are declared as a CRD:
apiVersion: security-profiles-operator.x-k8s.io/v1beta1
kind: SeccompProfile
metadata:
name: webapp-allowlist
namespace: security
spec:
defaultAction: SCMP_ACT_ERRNO
defaultErrnoRet: 1
archs:
- SCMP_ARCH_X86_64
syscalls:
- action: SCMP_ACT_ALLOW
names:
- read
- write
- openat
- close
- mmap
- mprotect
- exit_groupThe operator then writes the profile file to each node and exposes it at the standard path /var/lib/kubelet/seccomp/operator/<namespace>/<name>.json. Your pod just references it: localhostProfile: operator/security/webapp-allowlist.json. When a new node joins, the operator automatically places its profiles — nothing is missed.
Important
Before adopting Localhost, make sure you have an answer to one question: what happens when a new node joins? If the answer is "lucky guess", use RuntimeDefault first or install an operator soon. A pod pointing to a profile that doesn't exist will fail to schedule — and that failure hurts when you're on-call at night.
A declared profile isn't necessarily a present profile. Verify on the node where the pod runs:
ls -la /var/lib/kubelet/seccomp/
crictl info | grep seccompcrictl is the CLI for containerd — crictl info | grep seccomp confirms the node runtime has seccomp support enabled. Combine it with inspecting a running pod via kubectl describe:
kubectl describe pod webapp | grep -i seccompThese three verification layers — the file exists, the runtime supports it, the pod references it — close the loop from declaration to execution.
In episode 10 you understood how to apply seccomp in Kubernetes: the seccompProfile field with three types (Unconfined, RuntimeDefault, Localhost) that replaced the alpha annotation, its relation to Pod Security Standards (Baseline and Restricted), and the challenge of distributing profiles across nodes, answered with seccomp-profiles-operator. You also know how to verify profiles on a node via crictl and kubectl describe.
The keys to take home:
RuntimeDefault; move up to Localhost once the workload is understood.Localhost profile must exist on all nodes — use an operator or answer that question first.So far seccomp comes from the outside: containers, service units, pods. But there's one approach closer to the defensive line — inside the application itself. In episode 11, we move into Seccomp for Applications & Daemons: loading filters via libseccomp, python-seccomp, and Go, restricting socket families for network daemons, and blocking dangerous syscalls like unshare, mount, and ptrace.