Quick answer

What this guide helps you do

Fix Docker file permission problems by understanding UID, GID, umask, PUID/PGID, shared groups, ACLs and bind mounts. Includes Jellyfin, Sonarr and Radarr examples.

Jellyfin beginner path

New to Jellyfin? Follow this order.

These guides form the SmallGrid Jellyfin path: install it, fix folder access, solve empty libraries, reduce unnecessary transcoding, then choose the right mini PC.

  1. How to Reduce Jellyfin Server Power Use on Ubuntu
  2. Give Jellyfin Access to Media Folders on Ubuntu
  3. Jellyfin Library Not Showing Files? 8 Checks That Fix It
  4. Jellyfin Direct Play vs Transcoding: What Is the Difference?
  5. Best Mini PC for Jellyfin: CPU, RAM and Quick Sync Specs

Quick answer

Docker does not invent a separate permission system for bind-mounted files. On a Linux host, access still comes down to the numeric user ID (UID), group ID (GID), Unix permission bits, ACLs, mount options and the process umask.

A common media-server failure looks like this:

qBittorrent creates file
→ file belongs to one UID/GID
→ Sonarr or Radarr moves it
→ Jellyfin runs as another UID/GID
→ new file is not group-readable
→ Jellyfin cannot scan or play it

The correct fix is usually to make the services share a deliberate access model, not to use:

chmod -R 777 /srv/media

For a shared Docker media stack, aim for:

known service UIDs
+
one shared media GID where appropriate
+
group-readable files
+
group-traversable directories
+
a suitable umask
+
consistent bind-mount paths
+
read-only media mounts for consumers that do not need write access

Docker Engine 29.8.0, released on 3 September 2026, adds a --umask option to docker run and docker create. It can set the umask for the container’s main process, execs and healthchecks. That is useful, but it does not replace understanding the UID, GID and host filesystem that ultimately own the files.


What UID and GID actually mean

Linux permissions are based primarily on numeric identities.

Check your host user:

id

Example:

uid=1000(sean) gid=1000(sean) groups=1000(sean),1001(media)

The important values are:

UID 1000
GID 1000
supplementary GID 1001

A filename may display a friendly owner and group:

ls -l /srv/media/movies

but Linux ultimately checks the numeric IDs.

Show them explicitly:

ls -ln /srv/media/movies

or:

stat -c 'uid=%u gid=%g mode=%a path=%n' /srv/media/movies

This matters because the username inside a container can differ from the username on the host while still using the same numeric UID.


Host identity vs container identity

A bind mount exposes host files inside the container.

Example:

volumes:
  - /srv/media:/media

The file:

/srv/media/movies/Film.mkv

on the host may appear as:

/media/movies/Film.mkv

inside the container.

It is still the same underlying host file.

If the host says the file belongs to:

UID 1000
GID 1001

then a container process needs permission as that owner, that group, another supplementary group, an ACL entry, or through the other permission bits.

Docker does not magically translate a denied host filesystem permission into an allowed container permission.


Check the identity inside a container

Do not assume what account an image uses.

Inspect the configured Docker user:

docker inspect YOUR_CONTAINER --format 'ConfiguredUser={{json .Config.User}}'

Then check the effective runtime identity:

docker exec YOUR_CONTAINER id

Example:

uid=1000(abc) gid=1000(abc) groups=1000(abc),1001

The numeric values matter more than the displayed names.

An empty Docker .Config.User does not necessarily mean your application process runs as the host root user. Images can select their own runtime account internally.

Always check the running container.


What PUID and PGID mean

Some container images, especially LinuxServer-style images, support environment variables such as:

environment:
  - PUID=1000
  - PGID=1001

These are image conventions, not universal Docker features.

They tell that particular image how to run its application process.

Other images use Docker’s standard Compose user field:

services:
  app:
    user: "1000:1001"

Do not copy PUID and PGID into an image that does not support them.

Check the image documentation, then verify the result with:

docker exec YOUR_CONTAINER id

Unix permission bits in one minute

A typical mode might be:

-rw-r-----

That means:

owner: read + write
group: read
other: no access

Numeric form:

640

A directory might use:

drwxr-x---

or:

750

Directories need execute permission to be traversed.

That distinction is important for media libraries.

Jellyfin may have read permission on:

/srv/media/tv/show/episode.mkv

but still fail if it cannot traverse:

/srv
/srv/media
/srv/media/tv
/srv/media/tv/show

Inspect the complete path with:

namei -l /srv/media/tv/show/episode.mkv

What umask does

A process normally requests a default mode when creating a file or directory.

The umask removes permission bits from that requested mode.

Check your shell’s current umask:

umask

A common value is:

0022

For a normal requested file mode of 666:

666
-022
----
644

For a requested directory mode of 777:

777
-022
----
755

So a 022 umask normally creates files that are not group-writable.

A cooperative media stack often uses a more group-friendly policy such as 002, where appropriate:

file:      664
directory: 775

Do not change umask blindly. First decide whether the group is actually the security boundary you want.


Docker 29.8: the new —umask option

Docker Engine 29.8.0 adds:

--umask <octal>

to docker run and docker create.

Docker documents that this sets the umask for:

  • the container’s main process
  • exec processes
  • healthchecks

Example:

docker run --umask 0002 ...

The Engine API also exposes the corresponding HostConfig.Umask option.

This is useful when you control containers directly and want an explicit container-wide umask.

But remember:

--umask controls which permissions a process removes when creating new objects. It does not change existing file ownership, fix an incorrect UID/GID, repair a missing bind mount or override filesystem mount restrictions.

After changing a creation policy, test a newly created file. Existing files will not automatically change.


Does Docker Compose support the new umask setting?

Do not assume a new docker run flag has an equivalent Compose key until the Compose specification and your installed Compose version document it.

Check:

docker version
docker compose version

For existing Compose stacks, image-supported settings, service startup wrappers, shared groups and ACLs may remain the practical approach until the setting you want is explicitly supported by your Compose toolchain.

Avoid adding invented Compose keys from forum examples.


A good shared-media permission model

For a home media stack, a common design is:

media group GID: 1001

Jellyfin:
  read media

Sonarr:
  read/write media

Radarr:
  read/write media

qBittorrent:
  write downloads
  read/write where required for imports/seeding

The host directories can use the shared group:

sudo chgrp -R media /srv/media

A directory policy might be:

owner: service administrator
group: media
directories: 775 or 2775 where appropriate
files: 664

The setgid bit on directories can help new objects inherit the directory group:

sudo chmod g+s /srv/media

Check:

ls -ld /srv/media

A setgid directory shows an s in the group execute position.

Do not apply recursive permission changes to a production library until you understand every service using it.


Worked example: Jellyfin + Sonarr + Radarr

Suppose:

media group GID: 1001
Jellyfin UID:     1000
Sonarr UID:       1002
Radarr UID:       1003

Host directories:

/srv/media/tv
/srv/media/movies

All three services need the media group.

A container can receive a supplementary group with Compose:

services:
  jellyfin:
    group_add:
      - "1001"

  sonarr:
    group_add:
      - "1001"

  radarr:
    group_add:
      - "1001"

After recreating the containers:

docker compose up -d --force-recreate jellyfin sonarr radarr

verify:

docker exec jellyfin id
docker exec sonarr id
docker exec radarr id

Each should include GID 1001.

Then test the actual media paths from each container.


Why a shared group is often better than matching every UID

It is tempting to force every container to run as the same UID.

That can work, but it is not always necessary or desirable.

A cleaner model is often:

different service UIDs
+
shared media GID

That preserves service separation while deliberately granting access to the shared data.

For example:

Jellyfin UID 1000
Sonarr UID 1002
Radarr UID 1003
All members of media GID 1001

The media group becomes the shared access boundary.


Read-only mounts for Jellyfin

Jellyfin normally needs to read the media library, not modify the source media.

A useful mount is:

volumes:
  - /srv/media:/media:ro

This can coexist with group-readable host permissions.

Sonarr and Radarr may require read-write access:

volumes:
  - /srv/media:/media:rw

The access model should match the job.

Do not make every media consumer writable merely because one automation service needs to move files.


Default ACLs can solve inheritance problems

ACLs are useful when normal owner/group modes are not enough or when you want explicit access for a service identity.

Install the tools:

sudo apt update
sudo apt install -y acl

Example using a media group:

sudo setfacl -R -m g:media:rX /srv/media
sudo setfacl -R -d -m g:media:rX /srv/media

For locations where automation services need write access:

sudo setfacl -R -m g:media:rwX /srv/media
sudo setfacl -R -d -m g:media:rwX /srv/media

Inspect:

getfacl -p /srv/media

Default ACLs affect newly created objects beneath a directory.

They are particularly useful when you have repeated cases where old media works but newly downloaded media does not.


Why new files can fail while old files work

This is one of the most useful diagnostic clues.

Suppose Jellyfin can read:

Old Show/episode.mkv

but not:

New Show/episode.mkv

Compare them:

stat -c '%A %a %U:%G %u:%g %n' \
  "/srv/media/tv/Old Show/episode.mkv" \
  "/srv/media/tv/New Show/episode.mkv"

Then:

getfacl "/srv/media/tv/Old Show/episode.mkv"
getfacl "/srv/media/tv/New Show/episode.mkv"

Look for differences in:

  • numeric UID
  • numeric GID
  • group read/write bits
  • parent directory traversal
  • ACL entries
  • ACL mask
  • creating process umask

If every new download has the same bad mode, fix the creator rather than repeatedly running chmod.


Diagnose the process that created the file

Find a recent file:

find /srv/media -type f -mtime -1 -printf '%M %u:%g %p\n' | head -30

Identify the service responsible.

Then inspect its runtime identity:

docker exec qbittorrent id
docker exec sonarr id
docker exec radarr id

If the service creates files as one group while the downstream service expects another, align the shared access design.

The problem is not “Docker permissions” in the abstract. It is a concrete mismatch between the creator, the resulting file metadata and the consumer.


Bind mounts do not fix permissions

This Compose entry:

volumes:
  - /srv/media:/media

only exposes the path.

It does not guarantee the container process can read or write it.

Inspect the live configuration:

docker inspect YOUR_CONTAINER \
  --format '{{range .Mounts}}{{println .Source "->" .Destination "RW=" .RW}}{{end}}'

Then test inside the container:

docker exec YOUR_CONTAINER ls -la /media

A correct bind mount can still return Permission denied.

Use Docker Container Cannot Access a Mounted Folder if the mount itself is the main problem.


Pooled filesystems and network shares

MergerFS, SMB/CIFS, NFS, NTFS and exFAT can change the permission picture.

Check the filesystem and mount options:

findmnt -T /srv/media
findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS -T /srv/media

For CIFS, access may depend on mount options such as:

uid=
gid=
file_mode=
dir_mode=

For NFS, server-side UID/GID mapping and export configuration matter.

For NTFS and exFAT, mount options can determine ownership and modes, so repeated chmod commands may not persist.

Always diagnose the filesystem layer before assuming a container configuration change will fix it.


Do not use chmod 777 as the solution

Avoid:

sudo chmod -R 777 /srv/media

It grants every local user read, write and execute permission and can mark ordinary media files executable.

It also destroys useful diagnostic evidence.

Prefer a deliberate model based on:

  • correct UID/GID
  • shared groups
  • suitable modes
  • default ACLs
  • read-only mounts where possible
  • creator umask

A practical diagnostic sequence

Run these checks in order.

1. Confirm the host filesystem

findmnt -T /srv/media
find /srv/media -maxdepth 3 -type f | head -20

2. Check the real file metadata

stat -c '%A %a %U:%G %u:%g %n' /srv/media

3. Inspect the active container mount

docker inspect YOUR_CONTAINER \
  --format '{{range .Mounts}}{{println .Source "->" .Destination "RW=" .RW}}{{end}}'

4. Check the container identity

docker exec YOUR_CONTAINER id

5. Test the mounted path

docker exec YOUR_CONTAINER find /media -maxdepth 3 -type f | head -20

6. Compare working and failing files

stat -c '%A %a %u:%g %n' WORKING_FILE FAILING_FILE

7. Inspect ACLs

getfacl WORKING_FILE
getfacl FAILING_FILE

8. Check creation policy

Record:

creating container:
UID:
GID:
supplementary groups:
umask:
resulting file mode:
resulting directory mode:

Fix the first failed layer and repeat the same test.


Verification checklist

  • Host storage is mounted at the expected path.
  • Container source and destination paths are correct.
  • Every service’s runtime UID and GID are known.
  • Shared media group membership is deliberate.
  • Parent directories are traversable.
  • Files are readable by every consumer that needs them.
  • Write access exists only where required.
  • New files inherit the expected group/access.
  • The creating process uses an appropriate umask.
  • Docker 29.8 --umask is used only where your deployment model supports and needs it.
  • The setup survives container recreation and host reboot.

Official source



Recap

Docker file permission problems become much easier once you stop treating “the container” as a single permission boundary.

Follow the real chain:

host filesystem
→ file UID/GID
→ permission bits / ACL
→ creating process umask
→ Docker bind mount
→ container runtime UID/GID/groups
→ application path

For shared media stacks, different service UIDs plus a deliberate shared media GID is often cleaner than making every container run as the same user.

Docker 29.8’s new --umask option gives administrators another useful control for newly created files, but it works best as part of that complete permission model rather than as a one-line cure.

Jellyfin guide cluster

More Jellyfin fixes and setup guides

These guides link the main Jellyfin setup, permissions, remote access, direct play, and hardware topics together.