Safeguard
Containers

k8s fsGroup: How Kubernetes Sets Volume Ownership Securely

What k8s fsGroup actually does to volume permissions, why it can wreck pod start times, and how to configure it without opening a privilege gap.

Karan Patel
Platform Engineer
7 min read

The k8s fsGroup setting tells Kubernetes to change the group ownership of a mounted volume so every container in a pod can read and write it as a shared, non-root group — and getting it wrong either breaks your app with permission-denied errors or quietly hands a workload more filesystem access than it should have. fsGroup lives under a pod's securityContext, and while it looks like a one-line convenience, it changes how ownership, the setgid bit, and pod startup latency all behave.

This guide covers what fsGroup does mechanically, the fsGroupChangePolicy performance trap that bites teams with large volumes, and how to pair it with the rest of a pod security context so you get shared access without a privilege escalation path.

What does fsGroup actually change on a volume?

When you set fsGroup: 2000 in a pod's securityContext, Kubernetes does three things as the volume is mounted: it changes the group ownership of the volume's contents to GID 2000, it adds group read/write permissions, and it sets the setgid bit on directories so newly created files inherit the same group. The result is that any container running in that pod — regardless of the user it runs as — can collaborate on the same files through shared group membership.

A minimal example looks like this:

apiVersion: v1
kind: Pod
metadata:
  name: shared-data
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  containers:
    - name: app
      image: myapp:1.4.2
      volumeMounts:
        - name: data
          mountPath: /var/data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: app-data

Here the process runs as UID 1000 / GID 3000, but the volume at /var/data is chowned to GID 2000 and the process is added as a supplementary member of that group. This is the intended pattern for a container that runs as a non-root user but still needs to persist data.

Why does fsGroup make pods start slowly?

By default, Kubernetes recursively walks the entire volume and changes ownership and permissions on every file when the pod mounts. On a small config volume that is invisible. On a persistent volume holding tens of thousands of files — a database data directory, a media cache, an artifact store — it is brutal. A volume with 50,000 files can take two to three minutes to mount under the default policy, and the pod sits in ContainerCreating the whole time.

Kubernetes 1.20 introduced fsGroupChangePolicy to fix this, and it became stable in 1.23. It accepts two values:

  • Always — the default; chowns the whole tree recursively on every mount.
  • OnRootMismatch — only applies the ownership change if the top-level directory's owner and permissions don't already match the expected fsGroup. If the root looks right, Kubernetes assumes the rest of the volume is already correct and skips the recursive walk.
  securityContext:
    fsGroup: 2000
    fsGroupChangePolicy: "OnRootMismatch"

With OnRootMismatch, that same 50,000-file volume mounts in seconds on the second and subsequent pod starts instead of minutes. For most stateful workloads this is the setting you want. Be aware it has no effect on ephemeral volume types like secret, configMap, and emptyDir, and starting in Kubernetes 1.26, CSI drivers that advertise the VOLUME_MOUNT_GROUP capability handle group ownership at mount time, so kubelet skips fsGroupChangePolicy for those volumes entirely.

Is fsGroup a security control or a foot-gun?

It is both, depending on what you pair it with. On the safe side, fsGroup is what lets you run a container as a non-root user and still write to a persistent volume — which is the whole point of dropping root in the first place. Without it, a non-root process usually can't write to a freshly provisioned PV because the mount is owned by root:root.

The risk is scope creep. Because fsGroup grants group-level write access to the entire volume, an over-broad group can let sidecars, init containers, or a compromised process reach files they shouldn't. Two guardrails matter:

  • Use a dedicated, high-numbered GID per workload rather than reusing a shared group across unrelated pods. If three different apps all mount volumes as fsGroup: 1000, a container escape in one gives an attacker a familiar, predictable group ID to target.
  • Treat fsGroup as one line in a complete securityContext, not a standalone fix. It only governs volume ownership; it does nothing about capabilities, privilege escalation, or the root filesystem.

The broader discipline of shrinking what a workload can touch is the same one that governs container image hardening — least privilege at build time and least privilege at runtime reinforce each other.

How should fsGroup fit into a hardened pod spec?

A pod that uses fsGroup correctly usually sets a full complement of controls around it. A production-grade baseline looks like this:

spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
    fsGroup: 20001
    fsGroupChangePolicy: "OnRootMismatch"
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: myapp@sha256:...
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]

The fsGroup here does exactly one job — make the mounted PV writable by the non-root process — while readOnlyRootFilesystem, drop: ["ALL"], and allowPrivilegeEscalation: false handle the rest of the blast radius. This layering is what the Pod Security Standards "restricted" profile expects, and it's what admission controllers like Kyverno or the built-in Pod Security Admission will check for.

How do you enforce and audit fsGroup at scale?

Setting the right values on one pod is easy; keeping them right across hundreds of Helm charts and Kustomize overlays is the actual problem. A few practices keep it honest:

  • Enforce a policy that any volume-mounting pod running as non-root must declare an fsGroup, using Kyverno or Gatekeeper. This catches the pod that drops root but then can't write and gets "fixed" by someone adding runAsUser: 0 back in.
  • Flag reused or low-numbered GIDs in review. A policy that requires fsGroup to be above, say, 10000 keeps workloads out of the range where system groups live.
  • Scan manifests in CI, not just at admission. Catching a missing fsGroupChangePolicy or an over-broad group in a pull request is a one-line diff; catching it after a database pod takes four minutes to reschedule during an incident is not. Manifest and IaC scanning — the kind an SCA and posture tool such as Safeguard can run against your Kubernetes YAML — turns these into pre-merge findings.

FAQ

What is the difference between fsGroup and runAsGroup?

runAsGroup sets the primary GID of the container process itself. fsGroup sets a supplementary group that owns mounted volumes and is added to the process's group list. A process can run as runAsGroup: 3000 while still writing to a volume owned by fsGroup: 2000 because it's a member of both.

Does fsGroup work on every volume type?

No. It applies to volume types that support ownership management (most block and file PVs). It's ignored for secret, configMap, and emptyDir ephemeral volumes, and with CSI drivers that support VOLUME_MOUNT_GROUP (Kubernetes 1.26+), the driver handles it at mount instead of kubelet.

Why is my pod stuck in ContainerCreating after adding fsGroup?

Almost always the recursive chown on a large volume. Set fsGroupChangePolicy: "OnRootMismatch" so Kubernetes skips the recursive walk when the root directory already matches, and startup drops from minutes to seconds.

Can fsGroup replace running the container as root?

That's exactly what it's for. fsGroup is the mechanism that lets a non-root process own and write to persistent storage, so you can set runAsNonRoot: true without breaking data persistence.

Never miss an update

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