This episode covers privilege minimization via lxc.cap.drop and lxc.cap.keep, lxc.aa_profile configuration, restricting device access with cgroup devices, and GPU and USB passthrough into containers.

In episode 13 we set up MAC and seccomp fences. Now we tighten further: capabilities — the specific abilities of root processes inside the container — and device control — which devices are allowed. The principle is one: least privilege. The fewer abilities a process has, the less damage it can do when compromised.
In modern Linux, "root" is no longer all-or-nothing. Root rights are split into individual capabilities: CAP_NET_ADMIN (network management), CAP_SYS_ADMIN (mount/namespace operations), CAP_DAC_OVERRIDE (bypass file permissions), and dozens more. A container can be given a subset of these capabilities — root inside the container is only as powerful as the allowed capability list.
The safe default approach: drop all capabilities the workload doesn't need.
lxc.cap.drop = sys_admin
lxc.cap.drop = mknod
lxc.cap.drop = sys_moduleEach line removes one capability from the set allowed inside the container. For simple workloads (web server, database, app server), most capabilities can be dropped.
The opposite, stricter approach: explicitly specify what to keep; everything else is removed automatically.
lxc.cap.keep = net_bind_service
lxc.cap.keep = net_raw
lxc.cap.keep = dac_overrideWith keep, the final list is exactly what's written — nothing is silently left over. This is the most auditable pattern.
Tip
Recommended workflow: start from a running container, audit the capabilities its processes use (lxc-attach -n c1 -- capsh --print), then drop everything unused. Using drop is friendlier for unknown workloads; using keep is safer for workloads you're certain about.
From inside the container, view the current process capability set:
lxc-attach -n c1 -- capsh --print | grep CurrentCompare with the default (broad) set to see the effect of drop/keep.
The lxc.aa_profile config determines the AppArmor profile covering the container processes — complementing capabilities. This is how you select the per-container profile from episode 13:
lxc.aa_profile = generatedThe combination of lxc.cap.drop/keep (capabilities) + lxc.aa_profile (MAC) + seccomp (syscalls) is three interlocking layers. Capabilities limit rights, AppArmor limits file access, seccomp limits syscalls.
Even though unprivileged containers are already restricted, it's still worth controlling devices explicitly via cgroup devices — the list of devices container processes may access. LXC provides the lxc.cgroup2.devices.allow and lxc.cgroup2.devices.deny keys.
# Format: <type> <major>:<minor> <perm>
lxc.cgroup2.devices.deny = a
lxc.cgroup2.devices.allow = c 10:200 rwmdeny a — deny all devices (reset permissions to empty).allow c 10:200 rwm — allow char device major 10 minor 200 (e.g. /dev/net/tun) with read/write/mknod permissions.After deny a, the container can only access devices explicitly allowed. This prevents the container from creating or using unwanted devices.
Imagine a hijacked container: without device control, it could try to access /dev/mem or disk devices. With deny a plus a narrow allow list, that attempt fails at the kernel layer — even before AppArmor speaks.
For workloads that need a GPU (rendering, inference), the host's GPU devices need to be mounted into the container. For NVIDIA, the common approach:
lxc.mount.entry = /dev/nvidia0 c1/dev/nvidia0 none bind,create=file 0 0
lxc.mount.entry = /usr/lib/nvidia c1/usr/lib/nvidia none bind,ro 0 0USB passthrough: find the device on the host, then bind-mount its node into the container.
lsusb
ls -l /dev/bus/usb/001/002lxc.mount.entry = /dev/bus/usb/001/002 c1/dev/bus/usb/001/002 none bind,create=file 0 0Warning
GPU/USB passthrough deliberately weakens isolation — processes inside the container can now program host hardware. Make sure that container doesn't belong to an untrusted tenant, and keep all other layers (unprivileged, MAC, seccomp) active. Passthrough is an architectural decision, not a trick.
lxc.cap.drop for what's certainly unused, lxc.cap.keep for what must be audited.lxc.cgroup2.devices.deny = a then allow only what's needed.Key takeaways:
drop removes them, keep leaves only the listed ones.lxc.aa_profile connects a container to an AppArmor profile.lxc.cgroup2.devices.allow/deny) restrict device access.In the next episode 15 we'll cover user namespaces & ID mapping — /etc/subuid and /etc/subgid, lxc.idmap for UID/GID mapping, lxc-usernet for unprivileged network quotas, and best practices for 0-65536 mappings.