Short answer:- Pod-level securityContext.runAsUser/runAsGroup set the UID/GID the container processes run as.- Pod-level fsGroup instructs Kubelet to ensure the mounted volume’s group ownership includes that GID (and on supported volume plugins to recursively chown/chmod) so processes can write even if file owner differs.- Container image file permissions still matter: if files/dirs are owned by a different UID and mode 000/400, process UID (or its groups) must have write permission.- Safe fixes: prefer fsGroup or per-pod runAsUser over running containers as root; use a one-time initContainer that adjusts permissions as root (only in the pod), avoiding host-level privileged mounts.Detailed explanation + examples:How they interact- runAsUser/runAsGroup: the Linux UID/GID used by the process inside the container. If you set runAsUser: 1000, processes run as UID 1000.- fsGroup: Kubelet will change the mounted volume’s group ownership to fsGroup (and on many volume types will recursively chown/chmod) so the group has access. The container process is member of that supplemental group, allowing writes if group write bit is set.- Filesystem ownership + mode: kernel enforces owner/group/other bits. If the directory is root:root with mode 0755 and your process UID is non-root and not in the group, writes fail.Safe manifest changes (preferred, non-privileged)1) Use fsGroup and set runAsUser to non-root:api example:yaml
apiVersion: v1
kind: Pod
metadata: {name: app}
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: app
image: myapp:latest
volumeMounts:
- mountPath: /data
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: pvc-data
Why: Kubelet ensures group ownership and the process runs as UID 1000 with GID 1000; group write allows writes without root.2) If fsGroup is not applied or volume plugin doesn’t support recursive chown (or you need finer control), use an initContainer to adjust permissions inside the pod rather than changing host:yaml
initContainers:
- name: fix-perms
image: busybox
securityContext:
runAsUser: 0 # root only inside the pod, not host-privileged
command: ["sh","-c","chown -R 1000:1000 /data && chmod -R g+rwX /data"]
volumeMounts:
- name: data
mountPath: /data
containers:
- name: app
image: myapp:latest
securityContext:
runAsUser: 1000
runAsGroup: 1000
volumeMounts:
- name: data
mountPath: /data
Notes: running an initContainer as root inside the pod is common and does not require privileged or hostPath privileges. It changes only the files within the volume for that pod lifecycle.Caveats and best practices- Some managed volume plugins (CSI, cloud disks) support fsGroup and will perform the chown as kubelet; check your environment. For NFS or custom CSI drivers, fsGroup may not be applied automatically.- Avoid making PVCs world-writable (chmod 777) unless you accept the security risk.- Avoid hostPath with privileged containers. If you must use hostPath, restrict scope and use PodSecurityPolicies/PSA to limit privileges.- Use immutable images that expect non-root, and bake ownership into the image where possible (chown in Dockerfile when using build-time UID mapping).- Audit: add SecurityContextConstraints/PodSecurityAdmission rules and RBAC to prevent unintended privilege escalation.In short: prefer fsGroup + non-root runAsUser; when fsGroup isn’t sufficient, use a short-lived initContainer that runs as root only inside the pod to chown/chmod the mounted path. This fixes permissions without granting broad host privileges.