Docker Error Guide: 'pthread_create failed: Operation not permitted' — Fix seccomp Syscall Blocks
Fix Docker 'pthread_create failed: Operation not permitted' and runc EPERM caused by an old seccomp profile blocking clone3. Update Docker/runc or loosen the seccomp profile.
- #docker
- #troubleshooting
- #errors
- #seccomp
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
A container built on a newer base image fails to start, or its process crashes at launch, because a syscall it needs is denied by Docker’s default seccomp profile. The two faces of this problem look like a runtime/exec permission failure:
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "runc": permission denied: unknown.
runtime/cgo: pthread_create failed: Operation not permitted
SIGABRT: abort
The pthread_create failed: Operation not permitted line is the tell: the container’s glibc tried a newer syscall — most often clone3 — and an outdated seccomp allowlist returned EPERM instead of permitting it. Newer glibc (Ubuntu 22.04+, Fedora, recent Alpine builds) uses clone3, which older Docker/runc seccomp profiles did not include.
Symptoms
- Containers based on recent images (glibc 2.34+) crash immediately with
pthread_create failed: Operation not permitted. - Go/CGO programs abort with
SIGABRTright after start. - The same image runs fine on a newer Docker host but fails on an old one.
runccreate fails withpermission denied/operation not permittedon an aging engine.- Running with
--security-opt seccomp=unconfinedmakes the container start (a diagnostic clue, not a fix).
Common Root Causes
- Old Docker/runc default seccomp profile — its allowlist predates
clone3, so newer glibc’s threading path is blocked. - New-glibc image on an old engine — modern base images call syscalls the host’s seccomp profile does not know about.
- Outdated
libseccompon the host — the daemon’s seccomp library is too old to translate newer syscall numbers, so they fall through to the default deny action. - A custom seccomp profile with a restrictive default action — a hand-written profile that denies (rather than allows/logs) unknown syscalls, missing
clone3/faccessat2/rseq. - Kernel/userspace skew — a recent kernel exposing new syscalls paired with an old container stack that filters them.
Diagnostic Workflow
First, confirm versions — the fix hinges on how old Docker, runc, and libseccomp are:
docker version --format 'Engine {{.Server.Version}}'
runc --version
dpkg -l libseccomp2 2>/dev/null || rpm -q libseccomp
Prove seccomp is the cause by running the exact image with seccomp disabled. If it now works, seccomp was the culprit:
docker run --rm --security-opt seccomp=unconfined <image> true
Identify the specific blocked syscall using strace inside a privileged debug run (look for EPERM on clone3):
docker run --rm --privileged <image> sh -c 'command -v strace' 2>/dev/null
strace -f -e trace=clone,clone3 <program> # on the host binary, if reproducible
Read the daemon log around the failure:
journalctl -u docker --since '15 min ago' | grep -i 'seccomp\|operation not permitted\|clone3'
Check what seccomp mode the daemon applies by default:
docker info --format '{{.SecurityOptions}}'
Example Root Cause Analysis
A team upgraded their application base image from Ubuntu 20.04 to 22.04. On developer laptops (recent Docker) everything worked, but a long-lived production host running an old engine crashed every container:
docker run --rm registry.internal/api:2.0 /api
# runtime/cgo: pthread_create failed: Operation not permitted
# SIGABRT: abort
Running the same image with --security-opt seccomp=unconfined started cleanly, which pointed straight at seccomp. docker version showed an engine whose default seccomp profile predated clone3 support, and glibc 2.35 in Ubuntu 22.04 uses clone3 for thread creation — so the syscall was silently denied and glibc aborted.
The correct fix was to update the container stack, which ships a current seccomp profile that allows clone3:
sudo apt-get update
sudo apt-get install --only-upgrade docker-ce docker-ce-cli containerd.io libseccomp2
sudo systemctl restart docker
docker run --rm registry.internal/api:2.0 /api # starts cleanly
Where the engine could not be upgraded immediately, a stop-gap was a custom seccomp profile based on the default with clone3 added — much safer than unconfined:
# Start from Docker's default profile, add the missing syscall names, then:
docker run --rm --security-opt seccomp=/etc/docker/seccomp-clone3.json <image> /api
Using --security-opt seccomp=unconfined also works but disables syscall filtering entirely, weakening container isolation — acceptable only as a temporary diagnostic, not a production setting.
Prevention Best Practices
- Keep Docker Engine,
runc/containerd, andlibseccompcurrent so the default seccomp profile knows about new syscalls likeclone3. - Test images built on new base OSes against your oldest production Docker host before rolling out.
- Prefer updating the engine over disabling seccomp; never ship
seccomp=unconfinedto production — it removes a key isolation layer. - If you must customize, base your profile on Docker’s default and add only the specific syscalls you need, keeping a permissive default action for forward compatibility.
- Pin and track
libseccompin your host provisioning so it is not left behind during partial upgrades. - Standardize the Docker/containerd version across the fleet to avoid works-here/fails-there skew.
Quick Command Reference
# Versions that determine the fix
docker version --format '{{.Server.Version}}' && runc --version
# Confirm seccomp is the cause (diagnostic only)
docker run --rm --security-opt seccomp=unconfined <image> true
# Update the whole container stack (the real fix)
sudo apt-get install --only-upgrade docker-ce docker-ce-cli containerd.io libseccomp2
sudo systemctl restart docker
# Run with a custom profile that adds clone3 (stop-gap)
docker run --rm --security-opt seccomp=/etc/docker/seccomp-clone3.json <image>
# Inspect the blocked syscall
journalctl -u docker --since '15 min ago' | grep -i 'clone3\|operation not permitted'
Conclusion
pthread_create failed: Operation not permitted is almost always a seccomp problem: a newer image’s glibc calls clone3, and an old Docker/runc/libseccomp stack denies it. Confirm by running with seccomp=unconfined — if that works, seccomp is the cause. The right fix is to update Docker, runc, and libseccomp so the default profile permits the newer syscalls. A custom profile that adds clone3 is a reasonable stop-gap; unconfined should stay a temporary diagnostic because it strips syscall filtering and weakens isolation.
For related runtime failures, see the OCI runtime create failed guide, the failed to create shim task guide, and more fixes in the Docker guides.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.