Skip to content

Security Contexts and Container Permissions

When building and deploying containers, a common misconception is that the environment inside a container is completely divorced from the host operating system. While the filesystem is isolated, the User IDs (UID) and Group IDs (GID) are not.

This nuance becomes critical when attaching persistent storage (like an NFS share) to your Kubernetes pods.

The Root Problem

By default, unless specified otherwise in the Dockerfile, a container process runs as the root user (UID 0).

If a pod running as root writes a file to an attached NFS Persistent Volume, that file will be created on the physical storage array with root:root ownership. This causes a massive problem in a shared media stack:

  1. Audiobookshelf (Running as Root) downloads a book and saves it to /data/media/books. The folder is owned by root.
  2. LazyLibrarian (Running as UID 1000) attempts to scan or rename that folder.
  3. The operating system blocks LazyLibrarian with a Permission Denied error because UID 1000 is not allowed to modify files owned by UID 0.

The LinuxServer.io Approach (PUID/PGID)

To solve this, many community images (specifically those published by linuxserver.io) use a clever workaround.

The container still starts as root, but the entrypoint script reads environment variables named PUID and PGID. Before launching the actual application (e.g., Radarr, Sonarr), the script uses a tool like s6-overlay to dynamically drop privileges down to the specified UID and GID.

env:
  - name: PUID
    value: "1000"
  - name: PGID
    value: "1000"

The Kubernetes Native Approach (securityContext)

The PUID/PGID approach is an image-specific hack. If an image (like advplyr/audiobookshelf) doesn't support those specific environment variables, it will ignore them and run as root.

The proper, Kubernetes-native way to handle this is using a Security Context.

Instead of relying on the container's entrypoint script to drop privileges, you instruct the Kubernetes kubelet to launch the entire container process as a specific, non-root user from the very beginning.

securityContext:
  runAsUser: 1000
  runAsGroup: 1000
  fsGroup: 1000
  • runAsUser: Forces the primary process to run as UID 1000.
  • runAsGroup: Forces the primary process to run as GID 1000.
  • fsGroup: A special supplemental group that applies to all containers in a Pod. Kubernetes will automatically ensure that the volume permissions match this group, granting the pod write access.

[!WARNING] While securityContext is safer and more standard, it can break containers that were strictly designed to start as root (e.g., containers that need to bind to privileged ports below 1024, or containers that hardcode chown commands in their startup scripts). Always check the container's documentation!

TrueNAS SMB vs Kubernetes Container Permissions

When integrating an enterprise storage array (like TrueNAS) with Kubernetes, you often encounter complex multi-protocol permission conflicts.

A common scenario is wanting to copy media files from your Mac to the NAS via SMB (Server Message Block), so that a Kubernetes pod (like Jellyfin) can read them via NFS (Network File System).

The Conflict

  1. You create an SMB user (truenas_smb_user) in TrueNAS and connect your Mac to the share.
  2. You try to paste a movie into the movies/ directory on the share, but macOS says Permission Denied.
  3. Why? Because the movies/ directory was originally created by the Jellyfin Kubernetes Pod over NFS.
  4. The Jellyfin pod runs as the abc user (UID 1000). When it created the movies/ directory, it applied standard Linux permissions (e.g., 755 - Owner can read/write, everyone else can only read).
  5. Your SMB user is a completely different entity on the TrueNAS server. It does not map to UID 1000. Therefore, it falls under the "everyone else" category and is denied write access by the Linux permissions set by the container.

The Escape Hatch

If you are just doing a one-off copy and do not want to wrestle with complex TrueNAS ACLs or ID mapping, you can use Kubernetes itself to unlock the folder dynamically.

Since the container created the folder and owns it, the container has the authority to change its permissions. You can use kubectl exec to break into the pod and forcefully change the directory permissions to 777 (Read/Write for everyone):

kubectl exec -n media deploy/jellyfin -- chmod -R 777 /data/media/movies

Now, your external SMB user will be able to write files directly into that folder!