CoursesAdvanced container securityHardening the container

seccomp: filter syscalls

RuntimeDefault and a curated custom profile.

Advanced14 min · lesson 13 of 25

A container talks to the host kernel through syscalls, and a kernel bug reachable through an exotic syscall is a direct route out. seccomp (secure computing mode) filters which syscalls a process may make. Docker ships a default profile that already blocks dozens of dangerous calls (mount, ptrace, kernel module loading, and more) — but the critical caveat is that --privileged and some runtimes run unconfined, so “the default is on” is an assumption to verify.

terminal
# confirm the default seccomp profile is actually applied (Docker’s default: on)
$ docker run --rm alpine grep Seccomp /proc/self/status
Seccomp: 2 # 2 = filter mode active. 0 = UNCONFINED (bad)
# --privileged disables it entirely:
$ docker run --rm --privileged alpine grep Seccomp /proc/self/status
Seccomp: 0 # no syscall filtering at all

A custom profile for a tight allowlist

To go beyond the default, ship a JSON profile that denies by default and allows only the syscalls your app makes. You do not guess the list — you observe it: run under an audit (log-only) profile, exercise every code path, and read which syscalls were actually used, then allow exactly those. The result is a container that can make a few dozen syscalls instead of the ~300 the kernel exposes.

terminal
# 1) discover the syscalls the app really uses
$ strace -f -qcf -o trace.txt myapp # or run under an SCMP_ACT_LOG profile
# 2) apply a curated profile that allows only those
$ docker run --rm --security-opt seccomp=./app-seccomp.json myapp:1.0
app-seccomp.json
{
"defaultAction": "SCMP_ACT_ERRNO", // deny everything...
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [{
"names": ["read","write","openat","close","futex","epoll_wait","accept4"],
"action": "SCMP_ACT_ALLOW" // ...except this observed allowlist
}]
}
Never run --privileged; it turns seccomp (and everything) off
The single most common way seccomp ends up disabled is --privileged, which drops the default profile, restores all capabilities, and exposes host devices at once. If a workload “needs” privileged, it almost always needs one capability or one device mount instead. Verify Seccomp: 2 in /proc/self/status on your production containers — 0 means you are running unconfined.