KhueApps
Home/DevOps/Fix AppArmor or SELinux denials that block Docker containers

Fix AppArmor or SELinux denials that block Docker containers

Last updated: October 07, 2025

Overview

AppArmor (Ubuntu/Debian) and SELinux (Fedora/RHEL/CentOS) can block Docker containers from reading files, mounting paths, or performing syscalls. Typical errors:

  • AppArmor: apparmor="DENIED" operation="..." profile="docker-default" ...
  • SELinux: AVC denial messages in audit logs; apps see EACCES or 13 Permission denied.

This guide shows how to quickly diagnose and fix denials safely, focusing on Docker.

Quickstart (TL;DR)

  • Identify the active LSM:
    • SELinux: getenforce => Enforcing/Permissive/Disabled
    • AppArmor: aa-status or cat /sys/module/apparmor/parameters/enabled
  • Check logs:
    • SELinux: journalctl -t setroubleshoot -b or ausearch -m avc -ts recent
    • AppArmor: journalctl -k | grep DENIED or dmesg | grep DENIED
  • Common SELinux fix for bind mounts: add :Z or :z to Docker volume options.
  • Common AppArmor fix in dev: run with --security-opt apparmor=unconfined or temporarily set docker-default to complain mode; then refine a profile.
  • Prefer targeted fixes (labels/profiles) over disabling enforcement.

Minimal working example (SELinux host)

This example shows a denial from a bind mount and fixes it with :Z labeling.

# 1) Reproduce: bind-mount a host dir into nginx
mkdir -p "$PWD/html" && echo "hello" > "$PWD/html/index.html"
# On SELinux Enforcing hosts, this may trigger AVC denials without labels
docker run --rm -p 8080:80 \
  -v "$PWD/html":/usr/share/nginx/html:ro \
  nginx:alpine

# In another shell, check for SELinux AVC denials
ausearch -m avc -ts recent | tail -n 20 || true

# 2) Fix: relabel the mount for container use
# :Z gives a private label for this container
docker run --rm -p 8080:80 \
  -v "$PWD/html":/usr/share/nginx/html:ro,Z \
  nginx:alpine

# Now nginx can read the files; AVC denials should stop.

Notes:

  • Use :Z for a private label (safer isolation). Use :z to share the label with multiple containers that mount the same path.
  • Alternative: chcon -Rt container_file_t ./html to persistently relabel the directory.

Step-by-step diagnosis

  1. Determine the platform and LSM
  • SELinux: getenforce and sestatus
  • AppArmor: aa-status and ls -l /etc/apparmor.d
  1. Capture the denial
  • Re-run the container and immediately inspect logs:
# SELinux
ausearch -m avc -ts recent | audit2why
journalctl -t setroubleshoot -b | tail -n 50 || true

# AppArmor
dmesg | grep -i apparmor | tail -n 50
journalctl -k | grep -i "apparmor=\"DENIED\"" | tail -n 50
  1. Identify the resource
  • Look for the path, capability, or syscall in the denial (e.g., denied r access to /host/path, operation="mount", cap=sys_admin).
  1. Apply the least-privilege fix
  • SELinux: label volumes (:z/:Z) or relabel paths; only if needed, adjust SELinux policy.
  • AppArmor: adjust the profile or use an appropriate security-opt override.
  1. Verify
  • Re-run the container; confirm no new denials in logs.

Fixes on SELinux hosts

Common container issues and fixes:

  • Bind mount denials (read/write)

    • Preferred: use volume flags
      # private label (container-specific)
      docker run ... -v /path:/container/path:Z ...
      
      # shared label (multiple containers share)
      docker run ... -v /path:/container/path:z ...
      
    • Persistent relabel (affects the host path):
      # Temporary change (does not survive restorecon)
      chcon -Rt container_file_t /path
      
      # Persistent mapping (survives relabels)
      semanage fcontext -a -t container_file_t "/path(/.*)?"
      restorecon -Rv /path
      
  • Capability or syscall denials

    • If the app needs extra capabilities, add only what’s required:
      docker run ... --cap-add SYS_ADMIN ... # narrow if possible
      
    • For rare cases where SELinux blocks specific actions, derive a minimal policy from audit logs:
      # Generate a type enforcement module from recent AVCs
      ausearch -m avc -ts recent | audit2allow -M mycontainer
      semodule -i mycontainer.pp
      
      Caution: review the generated rules; avoid broad allows.
  • Last-resort development overrides (avoid in production)

    # Disable SELinux separation for the container (Docker-specific)
    docker run ... --security-opt label=disable ...
    
    # Temporarily set host to permissive (global)
    sudo setenforce 0  # setenforce 1 to re-enable Enforcing
    

Fixes on AppArmor hosts

Docker uses the docker-default AppArmor profile by default.

  • Quick dev workaround

    # Unconfine a single container (dev/testing only)
    docker run ... --security-opt apparmor=unconfined ...
    
  • Temporarily collect denials and update the profile

    # Put docker-default into complain mode to log rather than block
    sudo aa-complain docker-default
    
    # Reproduce the issue, then update the profile interactively
    sudo aa-logprof
    
    # Return to enforcing
    sudo aa-enforce docker-default
    
  • Use a custom profile per container

    1. Create /etc/apparmor.d/docker-myapp with the minimal needed rules.
    2. Load it: sudo apparmor_parser -r /etc/apparmor.d/docker-myapp
    3. Run: docker run ... --security-opt apparmor=docker-myapp ...
  • If a capability is blocked (e.g., mount), consider using a precise capability add and profile rule rather than unconfined.

Docker Compose equivalents

  • Volume labels
    services:
      web:
        image: nginx:alpine
        ports: ["8080:80"]
        volumes:
          - ./html:/usr/share/nginx/html:Z  # or :z
    
  • Security options
    services:
      app:
        image: myimage
        security_opt:
          - label=disable            # SELinux (dev-only)
          - apparmor:unconfined      # AppArmor (dev-only)
    

Pitfalls

  • Forgetting :z/:Z on SELinux bind mounts; the container may start but fail at runtime.
  • Using chcon without semanage: labels may revert after restorecon or relabel operations.
  • Overbroad policy generated by audit2allow; always review and minimize.
  • Running unconfined or label=disable in production increases risk surface.
  • Mixing shared (:z) and private (:Z) labels inconsistently across containers can cause access conflicts.

Performance notes

  • Relabeling large directory trees with :Z or chcon can be slow; prefer targeting only required paths.
  • Frequent SELinux AVCs or AppArmor denials increase audit log volume; fix root causes to avoid log overhead.
  • Granting extra capabilities (e.g., SYS_ADMIN) can enable expensive operations inside containers—measure impact.
  • Custom AppArmor profiles have negligible runtime overhead; keep them minimal to reduce parsing/load time.

FAQ

  • How do I know which module blocked me?

    • Check getenforce (SELinux) and aa-status (AppArmor). Review kernel/audit logs for the denial source.
  • What’s the difference between :z and :Z in Docker volumes?

    • :z applies a shared label so multiple containers can access the path; :Z applies a private label for one container.
  • Is it safe to run apparmor=unconfined or label=disable?

    • Use only for development or short-term debugging. Prefer targeted labels/profiles in production.
  • Do rootless Docker setups change this?

    • Rootless reduces privileges but SELinux/AppArmor still apply on the host. Labeling and profiles remain relevant.
  • Can I fix this from inside the container?

    • No. Labels and profiles are managed on the host. Apply fixes on the host side and restart the container.

Series: Docker

DevOps