Reviewing how SELinux secures containers: the container_t and spc_t domains, bind mount relabeling with the :Z and :z options, label security settings on Podman and Docker, and SELinux configuration in Kubernetes pod security context and its integration with seccomp and LSMs.

In episode 9, you protected web servers and databases with the right labels. Now it's time for the technology that changed how deployments run: containers. Namespaces and cgroups separate containers logically, but neither stops an escaped process from accessing host resources. This is where SELinux steps in as the second isolation layer — and that's why it plays a big role in runtimes like Podman, Docker, and CRI-O.
If SELinux is off on a container host, one compromised container can read another container's data on the same host. This episode explains how SELinux prevents that scenario.
The SELinux container policy introduces two important domains:
spc_t (Super Privileged Container) — the domain for runtime engines like dockerd, containerd, podman, and conmon. This engine is fully trusted: it's the one that builds and manages all containers.container_t — the domain for processes running inside a container. It's tightly locked and can't automatically read host files.Note
The analogy: spc_t is the building manager holding the master keys to all units, while container_t is the tenant who only has the key to their own unit. Separate the two firmly — when you execute something inside a container, you're in the container_t world, not the host world.
Because container processes are in a different domain from host processes, privileges that leak out of a container don't directly become full access to the host — they still have to pass through the SELinux type rules. This is why a container running processes as root is still not "host root" in SELinux's eyes.
The most common need is moving a host directory into a container with a bind mount: -v /data:/data. The problem is that the /data files are usually labeled for a specific domain (for example default_t or a user home), and container_t isn't allowed to read them.
The solution isn't changing the host label permanently, but relabeling at mount time. Two main options:
:Z — relabel the mount with a unique per-container label (full privacy). Each container gets its own MCS category, so other containers can't read those files.:z — relabel the mount with a label shared by all containers (shared). All containers using that volume can access each other.podman run -v /data:/data:Z nginxdocker run -v /data:/data:Z nginxImportant
Choose deliberately. :Z gives isolation between containers, :z allows sharing between all containers. If a volume is shared by several containers that genuinely need to read each other — like a cache volume — use :z. For data that belongs to only one container, always :Z. Using :z on sensitive data means opening a door between containers that should have been isolated.
Besides :Z and :z, both runtimes provide finer label control through --security-opt:
podman run --security-opt label=type:container_t nginx
podman run --security-opt label=level:s0:c123,c456 nginx
podman run --security-opt label=disable nginxAvailable options:
label=type:... — force a specific type domain.label=user:... and label=role:... — set the user and role on the label.label=level:... — set the MCS category, useful for tenant isolation.label=disable — turn off SELinux for that container.Warning
label=disable removes the SELinux layer entirely for that container. Use it only for short-term troubleshooting or genuinely special cases — and understand that it negates the protection we're building. The habit of "just add label=disable" when a denial appears is an expensive shortcut down the road.
Podman has an ergonomic advantage: it automatically relabels mounted volumes, so many :Z/:z cases don't need to be written manually. But it's still important to understand what happens behind the scenes.
In Kubernetes, the runtime (usually CRI-O or containerd) takes SELinux instructions from the pod security context. The configuration is made per pod, not per container:
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
securityContext:
seLinuxOptions:
user: system_u
role: system_r
type: container_t
level: "s0:c123,c456"
containers:
- name: app
image: nginxEach pod gets a unique MCS level. The consequence is very important: pod A with level s0:c123 can't read files belonging to pod B with level s0:c456 — even though both share the same container_t type. That's the isolation between workloads running on the same host, without the hassle of changing labels one by one.
Tip
On large clusters, don't manage MCS levels manually — let the runtime or tooling (for example via PSP/GAC or a policy engine like Kyverno) allocate a unique level per pod. Manual management is error-prone and creates isolation gaps. The only thing humans need to manage is choosing the correct type, user, and role for the workload.
Container runtimes use several security mechanisms at once, and it's important to tell them apart:
The ideal combination in Kubernetes: a seccomp profile restricts syscalls, SELinux restricts access based on labels, and cgroups restrict resources. Three layers working at different levels that complement each other — this is what defense in depth really means.
In this episode 10, you've seen how SELinux becomes the second isolation layer for containers: the spc_t domain for runtimes and container_t for container processes, bind mount relabeling with :Z and :z, fine label control through --security-opt, and seLinuxOptions configuration in Kubernetes with a unique MCS level per pod.
The key takeaways:
container_t domain can't automatically read the host — that's the protection.:Z for per-container privacy, :z for sharing between containers.So far all storage is still local to the host. In episode 11, we'll take SELinux into the distributed world: NFS, Samba & Network Services — how labeling works on NFS with mount options and noexec, the consequences of sharing storage between hosts, samba booleans for exporting directories and homes, and port labeling for non-standard services.