Container runtimes provide process isolation through namespaces and cgroups, but these mechanisms alone do not restrict what a container process can do on the host kernel. AppArmor (Application Armor) is a Linux Security Module (LSM) that enforces mandatory access control policies, restricting container processes to only the system resources they explicitly need.

This guide compares three approaches to managing AppArmor profiles for containers: standalone AppArmor utilities (apparmor-utils), Docker’s built-in AppArmor integration, and containerd’s native AppArmor profile support. We cover profile creation, deployment, and best practices for securing containerized workloads in self-hosted environments.

Understanding AppArmor for Containers

AppArmor works by attaching security profiles to processes via the Linux Security Module (LSM) framework. Each profile defines which files, network ports, and capabilities a process can access. For containers, AppArmor profiles are applied when the container runtime starts the container process.

Without AppArmor, a root process inside a container can potentially:

  • Access sensitive host files through mount namespaces
  • Load kernel modules if capabilities are not properly restricted
  • Access raw sockets or perform network operations beyond what the container needs
  • Modify system-wide settings via procfs or sysfs

AppArmor profiles restrict these operations at the kernel level, providing defense-in-depth beyond container runtime security settings.

FeatureAppArmor UtilsDocker AppArmorContainerd AppArmor
Packageapparmor-utilsdocker-ce (built-in)containerd (built-in)
Profile Location/etc/apparmor.d//etc/apparmor.d/ (or inline)/etc/apparmor.d/ (or inline)
Profile SyntaxAppArmor DSLAppArmor DSLAppArmor DSL
Management CLIaa-genprof, aa-enforce, aa-complaindocker run –security-optcontainerd CRI config
Default ProfileNo (manual)docker-default (included)cri-default (CRI)
Custom ProfilesYesYesYes
Profile EnforcementKernel LSMRuntime-applied at container startRuntime-applied at container start
Profile Switchingaa-enforce/aa-complainContainer restart requiredContainer restart required
Kubernetes SupportManualVia runtime configNative (via CRI)
Best ForHost-level securityDocker environmentsKubernetes/containerd environments

AppArmor Utils: Standalone Profile Management

apparmor-utils provides command-line tools for creating, managing, and enforcing AppArmor profiles. This approach is ideal for host-level container security management.

Installation

1
2
3
4
5
6
7
8
# Debian/Ubuntu
apt-get update && apt-get install -y apparmor apparmor-utils

# Verify AppArmor is active
aa-status

# Check kernel module
systemctl status apparmor

Generating Profiles with aa-genprof

aa-genprof interactively generates profiles by monitoring an application’s behavior:

1
2
3
4
5
6
7
8
# Start profiling a container runtime
aa-genprof /usr/bin/containerd-shim

# Or profile a specific application
aa-genprof /usr/sbin/nginx

# The tool monitors the application and prompts for each access request
# Answer Allow/Deny for each operation, then save the profile

Writing Custom AppArmor Profiles

Create a profile in /etc/apparmor.d/:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
cat > /etc/apparmor.d/docker-restricted << 'EOF'
#include <tunables/global>

profile docker-restricted flags=(attach_disconnected) {
  #include <abstractions/base>
  #include <abstractions/nameservice>

  # Allow reading standard library files
  /lib/** mr,
  /usr/lib/** mr,

  # Allow network access (TCP/UDP)
  network inet tcp,
  network inet udp,
  network inet6 tcp,
  network inet6 udp,

  # Restrict file system access
  deny /etc/shadow r,
  deny /etc/gshadow r,
  deny /etc/passwd w,
  deny /proc/sys/** w,
  deny /sys/** w,

  # Allow working directory
  /var/lib/docker/containers/** rw,

  # Deny kernel module loading
  deny capability sys_module,
  deny capability sys_admin,
}
EOF

# Load and enforce the profile
apparmor_parser -r /etc/apparmor.d/docker-restricted
aa-enforce docker-restricted

Managing Profile States

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# List all loaded profiles
aa-status

# Switch a profile to complain mode (log violations, don't block)
aa-complain docker-restricted

# Switch to enforce mode (block and log violations)
aa-enforce docker-restricted

# Disable a profile
aa-disable docker-restricted

# Remove a profile
apparmor_parser -R /etc/apparmor.d/docker-restricted

Docker AppArmor Integration

Docker includes a default AppArmor profile (docker-default) that provides a reasonable baseline for container security. Custom profiles can be applied per-container.

Default Docker AppArmor Profile

Docker ships with a default profile that:

  • Allows basic system calls required by most containers
  • Blocks access to /proc/sys, /sys/fs, and other sensitive paths
  • Restricts kernel module loading
  • Allows network access (TCP/UDP)
  • Denies raw socket access

To check the default profile:

1
2
3
4
5
# View Docker's default AppArmor profile
docker info | grep -i apparmor

# Or inspect the profile file
cat /etc/apparmor.d/docker

Applying Custom AppArmor Profiles to Docker Containers

1
2
3
4
5
6
7
8
# Run a container with a custom AppArmor profile
docker run --security-opt "apparmor=docker-restricted"   --name my-app   -d nginx:latest

# Run with no AppArmor profile (not recommended)
docker run --security-opt "apparmor=unconfined"   --name my-debug   -d ubuntu:latest

# Verify AppArmor profile is applied
docker inspect my-app --format '{{.AppArmorProfile}}'

Docker Compose with AppArmor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
version: "3.8"
services:
  web-app:
    image: nginx:latest
    security_opt:
      - apparmor=docker-restricted
    ports:
      - "80:80"
    restart: unless-stopped

  database:
    image: postgres:16
    security_opt:
      - apparmor=database-restricted
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  pgdata:

Generating Docker-Specific Profiles

1
2
3
4
5
# Use aa-logprof to refine a profile based on actual container activity
aa-logprof

# This analyzes AppArmor log entries and suggests profile updates
# Review each suggestion and accept or deny

Containerd AppArmor Support

containerd supports AppArmor profiles through its CRI (Container Runtime Interface) implementation. This is the primary mechanism for applying AppArmor in Kubernetes environments.

Configuring Default AppArmor in containerd

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Edit containerd configuration
cat > /etc/containerd/config.toml << 'EOF'
version = 2

[plugins."io.containerd.grpc.v1.cri".containerd]
  default_runtime_name = "runc"

[plugins."io.containerd.grpc.v1.cri"]
  # Set default AppArmor profile for all containers
  apparmor_profile = "cri-default"
EOF

# Restart containerd
systemctl restart containerd

Applying AppArmor Profiles via CRI

When using containerd directly (not through Kubernetes):

1
2
# Use ctr with AppArmor profile
ctr run --with-ns="network:/var/run/netns/myns"   --apparmor-profile=cri-default   docker.io/library/nginx:latest my-nginx

Kubernetes AppArmor Integration

Kubernetes supports AppArmor through annotations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
  name: secured-pod
  annotations:
    # Apply AppArmor profile to all containers in the pod
    container.apparmor.security.beta.kubernetes.io/web: runtime/default
    container.apparmor.security.beta.kubernetes.io/sidecar: localhost/custom-profile
spec:
  containers:
    - name: web
      image: nginx:latest
    - name: sidecar
      image: busybox:latest

The runtime/default annotation applies the container runtime’s default profile (containerd’s cri-default or Docker’s docker-default). The localhost/profile-name annotation applies a custom profile from /etc/apparmor.d/.

Container AppArmor Profile for Database Services

Here’s a production-ready AppArmor profile for a database container:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
cat > /etc/apparmor.d/database-restricted << 'EOF'
#include <tunables/global>

profile database-restricted flags=(attach_disconnected) {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  #include <abstractions/openssl>

  # Database data directory
  /var/lib/postgresql/** rw,
  /var/lib/mysql/** rw,

  # Configuration files (read-only)
  /etc/postgresql/** r,
  /etc/mysql/** r,

  # Log files
  /var/log/postgresql/** rw,
  /var/log/mysql/** rw,

  # Network access for database connections
  network inet tcp,
  network inet6 tcp,

  # Deny dangerous capabilities
  deny capability sys_admin,
  deny capability sys_module,
  deny capability sys_rawio,
  deny capability sys_ptrace,

  # Deny access to sensitive host paths
  deny /proc/sys/** w,
  deny /sys/** w,
  deny /dev/** rw,
  deny /etc/shadow r,
  deny /root/** r,

  # Allow standard libraries
  /lib/** mr,
  /usr/lib/** mr,
}
EOF

apparmor_parser -r /etc/apparmor.d/database-restricted
aa-enforce database-restricted

Docker Compose Full Stack with AppArmor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
version: "3.8"
services:
  web:
    image: nginx:latest
    security_opt:
      - apparmor=web-restricted
    ports:
      - "80:80"
      - "443:443"
    restart: unless-stopped

  app:
    image: node:20-alpine
    security_opt:
      - apparmor=app-restricted
    working_dir: /app
    command: ["node", "server.js"]
    restart: unless-stopped

  db:
    image: postgres:16
    security_opt:
      - apparmor=database-restricted
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    restart: unless-stopped

volumes:
  pgdata:

Choosing the Right AppArmor Management Approach

ScenarioRecommended ApproachReason
Docker-only environmentDocker built-in profilesSeamless integration, default profile included
Kubernetes clustersContainerd AppArmor via annotationsNative CRI support, per-pod granularity
Mixed runtime environmentAppArmor utils (standalone)Runtime-agnostic, works with any container engine
Custom security requirementsCustom profiles + aa-genprofFine-grained control over each service
Compliance (PCI, HIPAA)Custom enforced profilesAudit-ready, verifiable security policies

For broader container sandboxing, see our container sandboxing comparison covering gVisor, Kata, and Firecracker. For container security hardening beyond access control, our Docker Bench vs Trivy vs Checkov guide covers automated security auditing. For seccomp-based syscall filtering, check our seccomp profile management article.

FAQ

What is AppArmor and why should I use it with containers?

AppArmor is a Linux Security Module that provides mandatory access control (MAC). It restricts what files, network ports, and capabilities a process can access. While container runtimes provide namespace and cgroup isolation, AppArmor adds kernel-level enforcement that prevents container escape even if a process gains root privileges inside the container. It is defense-in-depth for containerized workloads.

How does AppArmor differ from seccomp for container security?

AppArmor and seccomp serve different purposes. AppArmor controls what resources a process can access (files, network, capabilities). Seccomp controls which system calls a process can make. They are complementary: seccomp blocks dangerous syscalls (like mount, ptrace), while AppArmor restricts file and network access. Use both for comprehensive container security.

Can I use AppArmor profiles with Docker Compose?

Yes. Add security_opt: - apparmor=profile-name to each service in your docker-compose.yml. The profile must be loaded on the host system before starting the containers. Docker automatically applies the profile when creating the container.

How do I create an AppArmor profile for my application?

Use aa-genprof /path/to/binary to start interactive profile generation. Run your application normally, and aa-genprof will monitor all access attempts. When the application tries to access a resource, aa-genprof prompts you to allow or deny the access. After completing the session, save the profile and switch to enforce mode.

What happens if an AppArmor profile blocks a legitimate container operation?

Switch the profile to complain mode with aa-complain profile-name. In complain mode, violations are logged but not blocked. Review the logs with aa-logprof to identify which rules need adjustment. Once the profile is correct, switch back to enforce mode with aa-enforce profile-name.

Are AppArmor profiles shared across containers?

Yes. AppArmor profiles are defined at the host level and can be applied to any container. Multiple containers can use the same profile, or each container can have its own profile. Profiles are loaded into the kernel once and reused.

How do I verify AppArmor is active on my system?

Run aa-status to see loaded profiles and their enforcement state. Check cat /sys/kernel/security/lsm to see if AppArmor is in the active LSM list. Also check dmesg | grep apparmor for kernel-level AppArmor messages.

Why Self-Host Container Security?

Self-hosted container environments require security controls that match or exceed managed cloud offerings. AppArmor provides enterprise-grade mandatory access control without additional infrastructure or licensing costs. Combined with seccomp profiles, read-only filesystems, and dropped capabilities, AppArmor forms a critical layer in container security hardening.

For organizations running self-hosted Kubernetes, AppArmor profiles complement Kubernetes Pod Security Standards by providing host-level enforcement that survives container restarts and runtime changes. For Docker-based deployments, AppArmor prevents container escape even if application vulnerabilities are exploited.

For Linux kernel security auditing, see our kernel security guide. For Linux-level sandboxing frameworks, our landlock and firejail comparison covers alternative approaches.