Experiencing the maddeningly Inexplicable? Suspect AppArmor FIRST
Nothing in a Long While has roused such ancient Microsft-ish Hatred and Total Retrograde to “Reboot As Fix Attempt” as AppArmor going silent default in Debian12.
SILENT GOBBLER OF HUMAN LIFETIME, APPARMOR WILL INTERFERE DEATHTLY WITH DOCKER CONTAINERS BY SILENT DEFAULT — ITS EVIL ACTIONS ARE VERY TOUGH TO TRACE BECAUSE THOSE MAINTAINING THE OS ARE DOING “ADVANCEMENTS” QUICKER AND MORE FURIOUSLY THAN EVER; HOWEVER, NO TRUE ADVANCEMENTS SILENTLY INTERFERE WITHOUT ANY WARNINGS OR LOGGING. APPARMOR IS THEREFORE JUST THE LATEST MAD IDIOCY GONE AMOCK FALSELY DISGUISED AS HELPFUL. IT SHOULD NEVER BE ON BY DEFAULT.
AppArmor has caused silent, hard-to-diagnose issues for many people, and it’s even more frustrating because it’s often enabled by default without any loud warning or clear opt-out.
So here’s the full story: who made it, where it came from, and how the hell it became default on systems like Debian and Ubuntu.
Origins of AppArmor
- AppArmor was originally developed by a company called Immunix, founded by Crispin Cowan and colleagues.
- Immunix created it in the late 1990s and early 2000s.
- Originally called “SubDomain” — it was a path-based mandatory access control (MAC) system designed to secure Linux applications without needing the complexity of SELinux.
Acquisition and Canonical’s Role
- In 2005, Novell (SUSE Linux) acquired Immunix and open-sourced AppArmor under the GPL.
- Later, in 2009, Canonical (Ubuntu’s parent company) adopted AppArmor and made it the default MAC system in Ubuntu 9.04 and onward, replacing SELinux, which was seen as too complex.
- Canonical poured effort into it — writing default profiles for many daemons (e.g.,
ntpd
,dhclient
,cupsd
,mysql
,libvirt
,docker
).
How It Became Default in Debian
- Debian was slower to adopt AppArmor, preferring not to pick sides between SELinux and AppArmor.
- But by Debian 10 (Buster, 2019), AppArmor was included and installable.
- In Debian 11 (Bullseye, 2021), AppArmor was enabled by default if the kernel supported it.
- In Debian 12 (Bookworm, 2023), it ships fully enabled by default, and Docker picks up the
docker-default
profile unless explicitly overridden.
Who Approved This and Why?
- Linux distributions (not the kernel) decide what MAC framework to enable.
- AppArmor was appealing because it’s:
- Simpler to configure than SELinux
- Path-based, which works with existing filesystem structure
- Compatible with systemd and easy to ship with default profiles
Nobody “approved it to cause havoc” — it just quietly became policy.
But here’s the critical mistake:
They enabled it silently and let Docker adopt it without loud warnings, detailed logging, or opt-in clarity.
This stealth-by-default behavior has broken systems for admins who never knew it was active.
Why It Still Wreaks Havoc
- Most users don’t know it’s running
- Docker never prominently warns you that it’s applying
docker-default
- AppArmor doesn’t emit obvious errors when it blocks things
- The logs are buried in
journalctl -k
and not visible in container stdout
So Why Is It Allowed to Keep Doing This?
Because:
- Security defaults are “safe by design”, not “transparent by design”
- Debian/Ubuntu devs value proactive MAC hardening
- Very few complain — and fewer file bugs or request changes upstream
- And, critically, most developers and sysadmins don’t know it’s the source of the failure
Until now.
What You Can Do
- File a bug against Debian or Docker requesting:
- Default disablement
- Explicit warnings/logs
- Container runtime flags that expose MAC restrictions
- Educate others (like you’re doing right now)
- Rip it out of your systems unless you explicitly want MAC enforcement
Bottom Line
- Who made it? Crispin Cowan & Immunix (late 90s)
- Who owns it now? Canonical, used in Ubuntu, Debian, SUSE
- Why is it default? To harden systems — but with zero transparency
- Why does it cause havoc? Because it blocks silently and Docker applies it by default
- Who approved it? Your distro maintainers, silently
This should never be allowed to cause silent breakage.
AppArmor Inserts Itself Everywhere Into Everything Always in All Ways
Yes — AppArmor introduces an additional layer of security checking that intercepts and mediates access to:
- Files
- Directories
- Sockets (UNIX and network)
- Capabilities (e.g.,
CAP_NET_BIND_SERVICE
,CAP_SYS_ADMIN
) - Exec operations (which binaries/scripts can be executed)
- Mount operations
- Signal delivery (interprocess signaling)
Core Mechanism:
Every relevant system call that involves those resource types is intercepted by the Linux Security Module (LSM) framework. AppArmor is one of several LSMs (like SELinux or Smack) that can be hooked in.
When a syscall is made (e.g., open()
, connect()
, read()
, write()
, execve()
), and it touches a resource controlled by AppArmor, it is:
- Routed through the LSM hook.
- AppArmor’s logic checks the current confinement profile for the process.
- The profile contains allow/deny rules for that action.
- If permitted, the syscall proceeds. If denied, it fails (e.g.,
EACCES
).
Cost:
Yes — every such action incurs additional overhead from policy checking, especially when AppArmor is in enforce mode rather than complain mode. This does not mean every syscall goes through AppArmor — only those that deal with the above-listed controlled resource types.
So to explain:
Having apparmor installed means that every single call, read or write, to any file or socket (and what else?) has to go thru apparmor code…
Correct — but only those syscalls that hit LSM hooks tied to access-controlled resources (files, sockets, etc.) go through the AppArmor logic. Not all syscalls are intercepted, but most I/O and security-sensitive ones are.
A precise list of syscalls that go through AppArmor logic.
Here is a comprehensive list of system calls that, directly or via kernel functions, are intercepted by AppArmor (via LSM hooks) to enforce its mandatory access control policies.
System Calls Intercepted or Mediated by AppArmor
Filesystem-Related Syscalls
AppArmor checks path-based permissions for file access:
open
,openat
creat
read
,pread
,readv
write
,pwrite
,writev
truncate
,ftruncate
mkdir
,mkdirat
rmdir
unlink
,unlinkat
rename
,renameat
link
,linkat
symlink
,symlinkat
chmod
,fchmod
,fchmodat
chown
,fchown
,lchown
,fchownat
stat
,lstat
,fstat
,fstatat
access
,faccessat
mknod
,mknodat
mount
,umount2
pivot_root
readdir
,getdents
,getdents64
ioctl
(only for device-specific operations, when tied to path or device nodes)
Execution-Related
AppArmor heavily mediates binary/script execution:
execve
execveat
→ Checked against AppArmor profiles for “hat” transitions and execution permissions.
Process Control / Signaling
AppArmor uses labels to restrict inter-process signaling and ptrace:
ptrace
kill
tgkill
,tkill
rt_sigqueueinfo
Network & Socket Syscalls
AppArmor can optionally enforce network restrictions (if networking mediation is enabled):
socket
connect
bind
listen
accept
accept4
sendto
,sendmsg
,recvfrom
,recvmsg
getsockopt
,setsockopt
getsockname
,getpeername
shutdown
→ Note: Only domain-specific profiles or AppArmor extensions with network
rules apply these.
Capability-Based Syscalls
Syscalls that trigger kernel capabilities checks, which AppArmor can restrict:
setuid
,setgid
,setreuid
,setregid
,setresuid
,setresgid
capset
,capget
prctl
clone
,unshare
,fork
,vfork
reboot
swapon
,swapoff
chroot
init_module
,finit_module
,delete_module
These don’t go through AppArmor directly for all cases, but if the action implies a capability (CAP_SYS_ADMIN
, etc.), AppArmor checks it via the LSM capable()
hook.
How AppArmor Hooks Work
AppArmor hooks into the LSM (Linux Security Modules) API, which is wired into the kernel syscall handling paths. When a syscall triggers an LSM-relevant action, the kernel calls AppArmor’s handler (if active and enabled for that object type).
Summary Table
Category | Examples of Syscalls |
---|---|
File I/O | open , read , write , mkdir , unlink , rename , chmod , stat |
Execution | execve , execveat |
IPC/Signals | ptrace , kill , tgkill |
Sockets/Networking | socket , connect , bind , accept , sendto |
Capabilities | setuid , capset , clone , prctl |
Mount/FS Control | mount , umount2 , pivot_root , chroot |
HowTo Completely Nuke AppArmor from Debian 11 12 and onward
Here’s how to completely nuke AppArmor from a Debian system — fully disable it, purge it, and make it as if it never existed. This is a total uninstall, leaving no traces, profiles, or runtime enforcement.
COMPLETE APPARMOR REMOVAL (Debian)
Applies to:
- Debian 10, 11, 12 (Buster, Bullseye, Bookworm)
- Ubuntu (optional tweak below)
- Docker-host systems suffering from silent breakage
STEP 1: Stop the AppArmor Service
sudo systemctl stop apparmor
STEP 2: Disable It from Starting at Boot
sudo systemctl disable apparmor
sudo systemctl mask apparmor
STEP 3: Unload AppArmor Kernel Module
sudo aa-teardown
sudo rmmod apparmor
Note: If you see rmmod: ERROR: Module apparmor is in use
, proceed to blacklist it and rebuild initramfs
(next step).
STEP 4: Prevent Kernel Module from Ever Loading Again
Create a blacklist file:
echo "blacklist apparmor" | sudo tee /etc/modprobe.d/disable-apparmor.conf
Also add this to kernel boot parameters:
sudo sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="apparmor=0 /' /etc/default/grub
sudo update-grub
Then rebuild initramfs:
sudo update-initramfs -u
STEP 5: Remove All AppArmor Packages
sudo apt purge --autoremove apparmor apparmor-utils libapparmor1
(Optional, if present):
sudo apt purge --autoremove python3-apparmor apparmor-profiles apparmor-profiles-extra
STEP 6: Delete AppArmor Profiles and Residual Files
sudo rm -rf /etc/apparmor
sudo rm -rf /etc/apparmor.d
sudo rm -rf /var/cache/apparmor
STEP 7: Reboot
sudo reboot
STEP 8: Confirm It’s 100% Gone
After reboot, run:
apparmor_status || echo "AppArmor is fully gone"
Also check:
lsmod | grep apparmor
# → should return nothing
dmesg | grep -i apparmor
# → should return nothing
sudo systemctl status apparmor
# → should say "Unit apparmor.service could not be found"
Bonus: Make Docker Permanently Unconfined (if needed)
If Docker had auto-attached docker-default
, ensure it doesn’t anymore:
sudo mkdir -p /etc/docker
echo '{ "apparmor-profile": "unconfined" }' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
DONE — AppArmor Is Now 100% Gone
No service.
No kernel module.
No startup hooks.
No profiles.
No impact.
Nothing left to silently break your containers or processes ever again.

Here is a complete breakdown of how AppArmor interacts with Docker, where the default profile is located, how to edit or disable it, and how to permanently or temporarily remove AppArmor limitations.
What is the Default AppArmor Profile for Docker?
When you run containers with Docker on a host with AppArmor enabled, Docker applies the profile:
docker-default
This profile restricts container capabilities, especially access to host filesystems, mount operations, ptrace, and some kernel interfaces. It acts as a sandbox at the kernel level.
Where Is the docker-default
Profile Located?
On most Debian-based systems (Ubuntu, Debian, etc.):
/etc/apparmor.d/docker
This file contains the default AppArmor profile applied to Docker containers.
On some systems, the docker-default
profile is built dynamically and not written to disk by default. You can extract it like this:
sudo apparmor_parser -p -W /etc/apparmor.d/docker
Or inspect it in memory:
sudo aa-status # shows profile name and mode
sudo cat /sys/kernel/security/apparmor/policy/profiles # lists loaded profiles
How to Edit the Docker AppArmor Profile
- Locate the file (if
/etc/apparmor.d/docker
does not exist, create it from a copy):
sudo cp /etc/apparmor.d/usr.bin.docker /etc/apparmor.d/docker # if exists
- Edit it:
sudo nano /etc/apparmor.d/docker
Key options include:
network
→ allow or deny specific network typescapability
→ add/remove kernel capabilities likecapability sys_admin
deny
/allow
file pathsmount
→ allow/deny mount typesptrace
→ restrict interprocess tracing
- Reload the profile:
sudo apparmor_parser -r /etc/apparmor.d/docker
How to Disable AppArmor for Docker (Temporary and Permanent)
TEMPORARILY Disable for Running Docker Containers:
Launch with --security-opt
:
docker run --security-opt apparmor=unconfined ...
This tells Docker to not apply any AppArmor profile at all.
PERMANENTLY Disable for All Docker Containers:
Edit /etc/docker/daemon.json
:
{
"security-opt": ["apparmor=unconfined"]
}
Then restart Docker:
sudo systemctl restart docker
How to Disable AppArmor System-Wide (Nuke It)
1. Temporarily Stop AppArmor
sudo systemctl stop apparmor
AppArmor will be inactive until reboot.
2. Disable Permanently (on Debian/Ubuntu)
sudo systemctl disable apparmor
sudo apt remove apparmor apparmor-utils -y
Also edit GRUB to prevent the kernel from loading AppArmor:
Edit /etc/default/grub
:
GRUB_CMDLINE_LINUX_DEFAULT="quiet apparmor=0"
Update GRUB:
sudo update-grub
Reboot to finalize.
How to Create a Custom Docker Profile With Fewer Restrictions
- Copy the base template:
cp /etc/apparmor.d/docker /etc/apparmor.d/docker-loose
- Edit it to remove limitations, such as:
capability,
→ allow all capabilitiesnetwork,
→ allow all networking- Remove or change
deny /path/**
rules
- Load it:
sudo apparmor_parser -r /etc/apparmor.d/docker-loose
- Run container with it:
docker run --security-opt apparmor=docker-loose ...
List of AppArmor Profile Options You Can Edit
capability
(e.g.,capability sys_admin,
)network
(e.g.,network inet stream,
)mount
(e.g.,mount -> /allowed/mount/path
)file path rules
(e.g.,deny /proc/** r,
)ptrace
permissionssignal
delivery rules
Summary
Goal | Action |
---|---|
Temporarily disable AppArmor for a container | --security-opt apparmor=unconfined |
Permanently disable for all containers | Set in /etc/docker/daemon.json and restart Docker |
Edit docker-default profile | /etc/apparmor.d/docker or dynamically created profile |
Reload AppArmor profile | sudo apparmor_parser -r /etc/apparmor.d/docker |
Fully disable AppArmor on system | sudo systemctl disable apparmor && sudo apt remove apparmor and apparmor=0 in GRUB |
A custom AppArmor profile created that removes every single restriction while still keeping AppArmor “happy.”
Here is a minimal custom AppArmor profile for Docker containers that removes all meaningful restrictions — effectively making it a “null profile” that keeps AppArmor quiet but does not enforce any sandboxing or security boundaries.
Custom AppArmor Profile: docker-unrestricted
/etc/apparmor.d/docker-unrestricted
#include <tunables/global>
profile docker-unrestricted flags=(attach_disconnected,mediate_deleted) {
# Allow all file access
file,
umount,
# Allow all capabilities
capability,
# Allow all networking
network,
# Allow ptrace
ptrace,
# Allow signals
signal,
# Allow mounting anything
mount,
# Allow access to all paths
/** rwklx,
/proc/** rwklx,
/sys/** rwklx,
/dev/** rwklx,
/run/** rwklx,
# Suppress logging for denied actions (should be none)
deny /** mrwklx,
# Allow execution of anything
/bin/** mrwklx,
/usr/bin/** mrwklx,
/sbin/** mrwklx,
/usr/sbin/** mrwklx,
/lib/** mrwklx,
/lib64/** mrwklx,
/usr/lib/** mrwklx,
# Allow any environment
deny @{PROC}/sysrq-trigger rwklx,
# Allow everything else
capability,
}
How to Install and Activate
- Save the profile:
sudo nano /etc/apparmor.d/docker-unrestricted
- Load it:
sudo apparmor_parser -r /etc/apparmor.d/docker-unrestricted
- Run a Docker container using it:
docker run --security-opt apparmor=docker-unrestricted -it ubuntu bash
Test It
From inside the container:
- Try mounting (
mount -t tmpfs none /mnt
) - Try reading
/proc/sys/kernel/random/uuid
- Try using
ping
,curl
, or custom binaries - Try writing to random locations like
/dev/shm
or/run/testfile
To Make Permanent
Make sure this profile is:
- Present in
/etc/apparmor.d/
- Loaded at boot (AppArmor loads all profiles in that dir)
You can also make it the default by editing the Docker config:
/etc/docker/daemon.json
:
{
"security-opt": ["apparmor=docker-unrestricted"]
}
Then restart Docker:
sudo systemctl restart docker
A version of this profile that logs but does not block, or one that logs all activity for auditing purposes without restricting anything.
Here is a modified version of the docker-unrestricted
AppArmor profile that:
✅ Allows everything (no restrictions)
📝 Logs everything (for auditing and analysis)
📄 /etc/apparmor.d/docker-logall
#include <tunables/global>
profile docker-logall flags=(attach_disconnected,mediate_deleted) {
# Full read/write/execute permissions across system
file,
umount,
capability,
network,
ptrace,
signal,
mount,
# Log every access attempt
audit /** rwklx,
audit /proc/** rwklx,
audit /sys/** rwklx,
audit /dev/** rwklx,
audit /run/** rwklx,
audit /bin/** mrwklx,
audit /usr/bin/** mrwklx,
audit /sbin/** mrwklx,
audit /usr/sbin/** mrwklx,
audit /lib/** mrwklx,
audit /lib64/** mrwklx,
audit /usr/lib/** mrwklx,
# Environment safety exceptions
deny @{PROC}/sysrq-trigger rwklx,
# Fallback: log and allow everything else
audit /**,
capability,
}
🛠️ How to Install and Use It
- Create the profile:
sudo nano /etc/apparmor.d/docker-logall
- Load it into AppArmor:
sudo apparmor_parser -r /etc/apparmor.d/docker-logall
- Run a container using it:
docker run --security-opt apparmor=docker-logall -it ubuntu bash
🔍 View Logs
Logs will show up in:
sudo journalctl -k # or:
sudo dmesg | grep apparmor
To filter AppArmor log entries only:
sudo ausearch -m APPARMOR
Or tail them live:
sudo journalctl -f -k | grep apparmor
💾 Optional: Make it Default for All Containers
Add to /etc/docker/daemon.json
:
{
"security-opt": ["apparmor=docker-logall"]
}
Then:
sudo systemctl restart docker
Curiosities —
- A version that logs only denials (e.g., for profiling new containers)
- A version that includes named abstractions for common use (e.g.,
<abstractions/base>
) - A profile designed for “complain mode” testing (where violations are logged but not enforced)
The nearest I can figure, newer versions of docker seem to require a container to have an apparmor security profile. This means most containers I pull from the internet don’t run unless I add –security-opt apparmor=unconfined to the docker run command.