Safeguard
Containers

Kubernetes securityContext: fsGroup and the Fields People Skip

A field-by-field walkthrough of Kubernetes securityContext — fsGroup, runAsNonRoot, and the settings teams leave at their insecure defaults.

Safeguard Team
Product
Updated 5 min read

Security context in Kubernetes is the block of pod and container fields that control the Linux security settings a workload runs under — user and group IDs, filesystem ownership, capability sets, and whether privilege escalation is allowed. Most teams set one or two fields (usually runAsNonRoot) and stop there, leaving the rest — including Kubernetes fsGroup, the field that controls volume ownership — at Kubernetes' permissive defaults. That gap is where a surprising number of container escapes and privilege-escalation findings come from — not from exotic zero-days, but from a securityContext block that was never filled in past the first field a tutorial mentioned.

What does k8s securityContext actually control?

It controls two overlapping scopes: pod-level settings that apply to every container and volume in the pod, and container-level settings that override the pod defaults for a single container. Pod-level fields include fsGroup, runAsUser, runAsGroup, supplementalGroups, and seccompProfile. Container-level fields include runAsNonRoot, readOnlyRootFilesystem, allowPrivilegeEscalation, and capabilities. The split matters because a permissive pod-level default silently applies to every container that doesn't explicitly override it — which is exactly how a securityContext block that looks reasonable on the container you're staring at can still leave a sidecar running as root.

What does fsGroup kubernetes actually do?

fsGroup sets the group ownership on volumes mounted into the pod, so a non-root container process can still read and write files on a mounted volume without needing broad world-writable permissions. Without it, a container running as a non-root UID often can't write to a persistent volume at all, because the volume's directory is owned by root with restrictive group permissions — which is why teams frequently see runAsNonRoot: true paired with an unexplained permission-denied error on startup, and "fix" it by loosening volume permissions instead of setting fsGroup correctly. A minimal working example:

spec:
  securityContext:
    fsGroup: 2000
  containers:
    - name: app
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000

Kubernetes then changes group ownership (and, depending on fsGroupChangePolicy, permissions) on the volume's contents to match the specified GID when the pod starts, so the non-root process can read and write it.

Which securityContext fields do teams skip?

In rough order of how often they're left unset:

  • readOnlyRootFilesystem — without it, a container's root filesystem is writable, meaning a remote code execution bug can drop a malicious binary directly onto disk. Setting readOnlyRootFilesystem: true and mounting an explicit emptyDir for the few paths that genuinely need to write closes that off.
  • allowPrivilegeEscalation — defaults to allowing it, which lets a setuid binary or a process with CAP_SYS_ADMIN gain more privileges than it started with. Set it to false unless a specific workload genuinely requires escalation.
  • capabilities.drop: ["ALL"] — containers get a default Linux capability set (roughly a dozen capabilities) that most application workloads never use. Dropping all and adding back only what's required (rarely more than NET_BIND_SERVICE) shrinks the kernel-level attack surface significantly.
  • seccompProfile — as of Kubernetes 1.19+, setting type: RuntimeDefault restricts the syscalls a container can make to a curated allow-list, closing off obscure syscalls that legitimate application code almost never needs.
  • runAsGroup — often skipped even when runAsUser is set, leaving the process in an unintended default group.

Why does fsGroup matter more once you're using persistent storage?

Because volume permission errors are the single most common reason teams abandon runAsNonRoot hardening halfway through. A pod that worked fine with an emptyDir volume can break the moment it's switched to a PersistentVolumeClaim, since many storage provisioners create volumes owned by root with 0750 permissions. Without fsGroup set correctly, the fix under deadline pressure is often to add runAsUser: 0 back in, which undoes the entire hardening effort. Setting fsGroup up front — alongside fsGroupChangePolicy: OnRootMismatch to avoid a slow recursive chown on every pod restart for large volumes — avoids that regression entirely.

How do you check what's actually running in production?

Static YAML review catches misconfigured securityContext blocks before they're applied, but drift happens — a Helm chart update, a new sidecar, or a manually patched Deployment can silently reintroduce root or a writable filesystem. Pairing pre-deploy IaC scanning with periodic cluster audits is the only way to know your hardened defaults are still in force months after you set them, rather than assuming a config you wrote once is still what's running.

FAQ

What's the difference between runAsUser and fsGroup?

runAsUser sets the UID the container process runs as. fsGroup sets the group ownership applied to mounted volumes so that UID can actually read and write them. You typically need both for a non-root container using persistent storage.

Does setting fsGroup slow down pod startup?

It can, on volumes with a large number of files, because Kubernetes may need to recursively change ownership on mount. Setting fsGroupChangePolicy: OnRootMismatch avoids the recursive chown on every restart by skipping it when ownership is already correct.

Is runAsNonRoot enough by itself?

No. runAsNonRoot only guarantees the process doesn't run as UID 0 — it says nothing about writable filesystems, Linux capabilities, or privilege escalation. Those need their own explicit fields.

Can securityContext be enforced cluster-wide instead of per-pod?

Yes, via admission controllers like Pod Security Admission (built into Kubernetes) or policy engines like OPA Gatekeeper and Kyverno, which can reject or mutate pods that don't meet a baseline securityContext standard before they're scheduled.

Where does securityContext fsGroup get set — pod or container level?

Pod level. securityContext.fsGroup (sometimes searched as Kubernetes fsGroup or fsGroup Kubernetes) is only valid under the pod's top-level securityContext, not inside an individual container's securityContext block, because it changes ownership on volumes shared across the whole pod rather than a single container's process.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.