AppArmor 454,622 confines a process to the files, mounts and capabilities its profile allows; on Ubuntu 225 hosts, Docker 514 applies a generated docker-default profile to every container, and --security-opt apparmor=<profile> selects another (SELinux 1,634 plays the same role on Fedora 1,480 and RHEL 664 ). The WSL2 6 kernel of this book ships with AppArmor disabled (/sys/module/apparmor/parameters/enabled is N, and docker info lists only seccomp and cgroupns), so custom profiles are not run here. A read-only root filesystem works everywhere and stops an attacker from writing tools or changing code. BookNest's production file (Per-Environment Files) gets it for the API, with the other two hardening flags of this section:
read_only: true
tmpfs: [/tmp]
cap_drop: [ALL]
security_opt: [no-new-privileges:true]P="-p l3-booknest-prod -f compose.yaml -f compose.prod.yaml"
docker compose $P up -d --build --wait 2>&1 | grep Healthy | sort -u
docker compose $P exec api touch /home/node/probe
docker compose $P exec api grep -E 'CapBnd|NoNewPrivs' /proc/1/status
docker compose $P down -v >/dev/null 2>&1
git commit -qam "Harden the API container in production" && git log --oneline -1Container l3-booknest-prod-api-1 Healthy Container l3-booknest-prod-db-1 Healthy Container l3-booknest-prod-web-1 Healthy touch: cannot touch '/home/node/probe': Read-only file system CapBnd: 0000000000000000 NoNewPrivs: 1 8bf0906 Harden the API container in production
All three services came up healthy; the API could not write even to its home directory, its bounding set is empty and no-new-privileges stops setuid binaries from raising privileges. tmpfs: [/tmp] gives it the one writable directory programs expect. Nginx 75 and PostgreSQL 1,289 can be made read-only too, but need tmpfs or volumes for their cache, run and data directories, so test each image before adding the flag.