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.
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:
--umaskcontrols 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
--umaskis used only where your deployment model supports and needs it. - The setup survives container recreation and host reboot.
Official source
Related SmallGrid guides
- Jellyfin Docker Permissions
- Jellyfin Docker Volume Paths Explained
- Jellyfin Not Scanning New Files
- Docker Container Cannot Access a Mounted Folder
- How to Fix Permission Denied on Ubuntu Server
- How to Install Docker on Ubuntu Server
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.
Jellyfin 12 Upgrade Guide: 10 Things to Check Before You Update
Prepare Jellyfin 10.10 or 10.11 for version 12: verify backups, plugins, Docker or Ubuntu packages, migrations, permissions, playback and rollback.
How to Reduce Jellyfin Server Power Use on Ubuntu
Reduce Jellyfin server electricity use on Ubuntu by measuring idle and playback power, avoiding unnecessary transcoding, checking background load, storage, CPU behaviour and reboot health.
Give Jellyfin Access to Media Folders on Ubuntu
Fix Jellyfin permission denied errors on Ubuntu. Test the service user, find blocked parent folders, apply safe ACLs, verify inheritance, and check mounted-drive options.
Jellyfin Library Not Showing Files? 8 Checks That Fix It
Fix a Jellyfin library that is empty or missing media. Check mounts, Linux permissions, Docker paths, library folders, naming, scans and logs in the correct order.
Jellyfin Docker Permissions: Fix Media Folder Access and UID/GID Errors
Fix Jellyfin Docker permission denied errors. Check bind mounts, container paths, UID and GID values, read-only media access, active mounts, and file visibility step by step.
Jellyfin Direct Play vs Transcoding: What Is the Difference?
Why is Jellyfin transcoding instead of Direct Playing? Compare Direct Play, Direct Stream and transcoding, CPU usage, quality and common triggers.