AppArmor and SELinux restrict what a container may do at node level, even if a process in the container tries to use additional permissions. This provides an additional layer of defence alongside RBAC, container image hardening, and NetworkPolicies.
Which one you use mainly depends on your nodes' operating system. AppArmor is most common on Ubuntu and Debian nodes, which we also use on the TransIP Kubernetes platform, while SELinux is common on AlmaLinux, Rocky Linux, RHEL, and CentOS Stream.
- Use AppArmor or SELinux for workloads with sensitive data, additional privileges, or strict compliance requirements.
- Start with runtime defaults and create stricter custom profiles only when necessary.
- Custom AppArmor profiles using `Localhost` and custom SELinux policies require node management. This is not always possible on managed clusters.
Before you begin
First determine which Linux security module your nodes use. Use kubectl to check your nodes' operating system:
kubectl get nodes -o wideIn practice, use:
- AppArmor: on Ubuntu- or Debian-based nodes.
- SELinux: on RHEL-, AlmaLinux-, Rocky Linux-, or CentOS Stream-based nodes.
Start with AppArmor RuntimeDefault on Ubuntu and Debian nodes
Step 1
Create a .yaml file for a pod using AppArmor `RuntimeDefault`, for example:
nano apparmor-demo.yamlAdd the following configuration to the file:
apiVersion: v1
kind: Namespace
metadata:
name: kb-mac-test
---
apiVersion: v1
kind: Pod
metadata:
name: apparmor-runtime-default
namespace: kb-mac-test
spec:
restartPolicy: Never
containers:
- name: busybox
image: busybox:1.36
command: ["/bin/sh", "-c", "sleep 3600"]
securityContext:
appArmorProfile:
type: RuntimeDefault
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 65532
seccompProfile:
type: RuntimeDefaultSave the changes and close the file (ctrl + x > y > enter).
Step 2
Create the resources and wait for the pod to run:
kubectl apply -f apparmor-demo.yaml
kubectl -n kb-mac-test wait --for=condition=Ready pod/apparmor-runtime-default --timeout=180s
Step 3
Check whether the container uses an AppArmor profile:
kubectl -n kb-mac-test exec apparmor-runtime-default -- cat /proc/1/attr/currentYou should see a profile name instead of `unconfined`. This confirms that AppArmor is active for the workload.
Use SELinux on RHEL-based nodes
If your nodes use SELinux, replace appArmorProfile from the previous section with a `seLinuxOptions` block. A simple example looks like this:
securityContext:
seLinuxOptions:
level: "s0:c123,c456"
allowPrivilegeEscalation: false
runAsNonRoot: true
seccompProfile:
type: RuntimeDefaultUse `seLinuxOptions` only if you understand which labels and policies your nodes already use. On many platforms, the runtime default is sufficiently secure, and an incorrect label may prevent a workload from opening volumes or sockets.
When do you use RuntimeDefault or a custom profile?
- RuntimeDefault: choose this as a secure default for almost all workloads.
- Custom AppArmor Localhost profile: use this only if you manage the nodes yourself and can load the same profile on every node.
- Custom SELinux policy: use this only if you know exactly which SELinux contexts and type enforcement you need.
For many environments, `RuntimeDefault` provides an immediate security improvement without significant management overhead.
AppArmor and SELinux do not replace other security measures, but they provide an important additional layer. Start with runtime defaults, combine them with limited privileges, and use custom profiles only when there is a clear reason to do so.