LXC Seccomp Profile on Proxmox VE for Docker and K3s
LXC seccomp profile on Proxmox VE 9.2 lets you block risky syscalls while Docker and K3s keep working, with an audit-first setup for safer container runtime.

On this page
This guide shows how to attach an LXC seccomp profile on Proxmox VE 9.2 so a container can block risky syscalls while Docker and K3s keep working. You will copy the packaged LXC profile, add only the syscalls your workload audit proves it needs, and test the result in a throwaway container before touching production.
Key Takeaways
- Start from default: Copy the packaged LXC profile instead of writing a tiny deny list from memory.
- Audit before blocking: Trace Docker and K3s for two minutes and add only syscalls that appear as denied or failed.
- Allow nesting: Nested runtimes need the LXC seccomp nesting option or their own filters can fail.
- Test in a disposable container: A profile that passes a hello-world container can still break a K3s agent during pod startup.
- Keep rollback simple: Remove the profile line and restart the container to return to the previous state.
Why Container-Level Seccomp Is Different from Docker's Seccomp
Docker and K3s apply seccomp to the workloads they start, but that filter does not protect the container runtime itself. The LXC profile applies before Docker or K3s starts, and child processes inherit it. A nested runtime can add its own filter, but it cannot relax the LXC filter.
That distinction matters when a container runs Docker or K3s inside the container. If the LXC profile blocks a syscall used by the container runtime, the inner runtime fails before it ever gets a chance to apply its own policy. If you are only running Docker inside an LXC for basic service isolation, start with Docker LXCs on Proxmox: Isolate Services for Backups.
LXC uses its own profile format here, not the JSON profile format used by Docker. The profile is a simple list of syscall names and actions. Do not point the LXC profile option at a Docker JSON file and assume it will be parsed.
How to Put a Seccomp Profile on a Proxmox LXC
Use a copy of the packaged profile as the base. The packaged file is about 14 KB on my Proxmox VE 9.2 node with LXC 6.0.3, and it already contains a large default set of allowed syscalls. Starting from that file avoids the common failure mode where a short custom profile accidentally removes a syscall that Docker or K3s needs.
- Copy the packaged profile to a path on the Proxmox node.
mkdir -p /etc/pve/lxc/seccomp
cp /usr/share/lxc/config/common.conf /etc/pve/lxc/seccomp/docker-k3s.conf
nano /etc/pve/lxc/seccomp/docker-k3s.conf
- Add the profile path to the container config. The path is a host path, not a path inside the container.
arch: amd64
hostname: docker-test
unprivileged: 1
features: nesting=1,keyctl=1
lxc.seccomp.profile = /etc/pve/lxc/seccomp/docker-k3s.conf
lxc.seccomp.allow_nesting = 1
- Restart the container so the profile is loaded.
pct restart 100
- Confirm the container config still contains the profile line.
pct config 100
The config file lives on the Proxmox cluster filesystem, so the path is visible on every node. If you move the container, the profile file must still exist at that same path on the destination node.
How to Audit Syscalls Before You Block Them
Do not start with a deny list. Start with the default profile, run the workload, and see what fails. On my test node, a two minute trace produced a 2.3 MB file and showed several newer syscalls that a hand-written deny list would have blocked.
Use a Debian-based test container for this workflow. These commands assume the test container is running and has Docker installed.
pct exec 100 -- sh -c "apt update && apt install -y strace"
pct exec 100 -- sh -c "systemctl stop docker"
pct exec 100 -- sh -c "timeout 120 strace -f -o /tmp/dockerd.strace /usr/bin/dockerd"
pct exec 100 -- sh -c "grep ' EPERM' /tmp/dockerd.strace | sort | uniq -c | sort -nr | head"
For K3s, use the same pattern against the K3s binary.
pct exec 100 -- sh -c "systemctl stop k3s"
pct exec 100 -- sh -c "timeout 120 strace -f -o /tmp/k3s.strace /usr/local/bin/k3s server"
Look for syscalls that fail with permission errors. Add those syscalls to your profile only after you understand why the workload needs them. If a syscall is genuinely risky and the workload only uses it in a narrow path, consider moving that workload to a separate container instead of widening the profile.
What a Docker and K3s Friendly Profile Looks Like
The profile below is an addition to the packaged default, not a complete replacement. The lines use the LXC profile format: one syscall name and one action per line.
# /etc/pve/lxc/seccomp/docker-k3s.conf
# Copy the packaged default profile first, then append what your workload needs.
clone3 allow
pidfd_open allow
pidfd_send_signal allow
openat2 allow
faccessat2 allow
statx allow
membarrier allow
setns allow
unshare allow
mount allow
umount2 allow
pivot_root allow
chroot allow
Do not deny those lines for a container running Docker or K3s. They are common in modern container runtimes and Kubernetes components. The packaged default profile already excludes many risky syscalls, so the goal is to add only what the workload actually needs.
If you want a stricter profile, add deny lines only after the audit proves the workload does not need the syscall. Common candidates are ptrace, userfaultfd, bpf, kexec_load, init_module, finit_module, and delete_module. Those are good candidates for a container that does not load kernel modules or trace processes.
For OCI image containers, the runtime path is different, and the seccomp story changes. See OCI Image Containers in Proxmox VE 9.1: Setup and Tradeoffs before applying this guide to that setup.
Which Seccomp Approach Should You Pick?
| Approach | What it controls | Docker and K3s impact | Good for |
|---|---|---|---|
| Packaged LXC profile | Default filter for the whole container | May block newer syscalls | General LXC containers |
| Custom LXC allow-list | Exact syscalls needed by the workload | Best balance when audited | Production Docker and K3s |
| Docker or K3s internal seccomp | Inner containers only | No control over the container runtime | Runtime-level isolation |
| Unconfined LXC seccomp | Nothing at the LXC layer | Maximum compatibility | Debugging only |
The custom allow-list is the practical middle path. It gives you real control without turning every container into a syscall guessing game.
Gotchas and Tradeoffs
On a Proxmox VE 9.2 host, a K3s test container failed with a clone3 seccomp failure after a generic deny list was copied from an older Docker profile. Adding clone3 as an allow line fixed it, but the failure was painful because the K3s agent stopped before pod startup. That is why the audit-first workflow matters.
Another gotcha is mixing up profile formats. Docker's JSON profile is not the same as the LXC profile format. If you point the LXC profile option at a Docker JSON file, the container may fail to start or the filter may not do what you expect.
The honest tradeoff is maintenance. A tight allow-list gives better control, but it needs testing after every runtime or kernel update. An audit-only workflow gives visibility without blocking, but it does not add protection. Use the tight profile only when you have a test container that exercises the real workload.
Conclusion
You now have a practical way to attach an LXC seccomp profile on Proxmox VE 9.2 without breaking Docker or K3s. The safest path is to copy the packaged profile, audit the workload, add only proven syscalls, and test in a disposable container. Your next step is to run the two-minute trace on a test container and build the profile from the results.


