AppArmor
AppArmor confines programs to a declared set of capabilities: which files they may read or write, which network operations they may perform, which capabilities they may use. Like SELinux it is mandatory access control, applying regardless of what Unix permissions would allow.
The difference is what it identifies objects by. AppArmor uses paths; SELinux uses labels attached to inodes. That single choice accounts for most of the practical difference between them.
What path-based buys and costs
A profile reads like a list of rules about files:
/var/log/myapp/*.log rw,
/etc/myapp/config.yaml r,
Anyone can read that and say what the program is allowed to do. An SELinux policy requires understanding the label taxonomy first. For a team where not everyone is a security specialist, this difference is decisive — a profile that gets reviewed is worth more than a stricter one nobody reads.
The cost is that paths are not identities. Hard links, bind mounts, and symlinks can produce a second path to the same file, and a profile written for one path does not constrain access through another. SELinux labels travel with the inode and have no equivalent gap. This is a real weakness rather than a theoretical one, though exploiting it generally requires capabilities an attacker would need to have obtained already.
In practice
Profiles run in enforce or complain mode, and complain mode is the sane way to develop one — run the application, collect what it actually did with aa-logprof, and turn the result into rules. Building a profile by reasoning about what a program should need is much slower and less accurate than watching what it does.
The distribution split matters for support: AppArmor is the default on Debian and Ubuntu and SUSE, SELinux on Red Hat derivatives. Whichever your distribution ships will have the better-maintained profiles and the better answers when something breaks, and that is a stronger argument than the technical comparison for most systems.
Container runtimes apply AppArmor profiles to containers, which is a useful default layer that most people never notice.
See also: SELinux for the comparison, host hardening patterns, and Linux administration in datacenters.