User and group IDs: the part that decides whether it works
Smart Transcode reads your files, writes a new file and replaces the original. Whether Linux allows that depends on numbers, not names.
How Linux decides
Every file stores the numeric user ID (UID) and group ID (GID) of its owner, plus permission bits. A program running as UID 1000 / GID 1000 may
write a file when it is the owner and the owner bit allows it, when its GID matches and the group bit allows it, or otherwise when "others" may. Names such as alice are only
a lookup in /etc/passwd on each machine. They are not stored in the file.
$ ls -ln /srv/media/tv
drwxrwxr-x 12 1000 1000 4096 Oct 27 10:02 Show.Name # owner uid 1000, group gid 1000
$ stat -c '%u:%g %U:%G' /srv/media
1000:1000 alice:alice
$ id alice
uid=1000(alice) gid=1000(alice) groups=1000(alice),44(video),105(render)
Why it matters even more in a container
A container has its own /etc/passwd and its own idea of who exists, but the files on your mounted folders keep the numbers they have on the host.
A user called alice inside the container means nothing; only "the app runs as UID 1000" counts. That is what the two settings do:
| Setting | Meaning | Set it to |
|---|---|---|
PUID | The numeric user ID the app runs as inside the container. | The UID of the owner of your media (id -u alice). |
PGID | The numeric group ID it runs as. | The GID of your media's group (id -g alice, or the shared group's number). |
UMASK | Permission bits removed from files the app creates. 002 lets the group write; 022 keeps it to the owner. | 002 if several users or apps (Sonarr, Radarr, Plex) share the files; otherwise either. |
At start the container creates a user with exactly that UID / GID, gives it ownership of /config (the database), adds it to the groups that own the GPU devices, and drops root. Files it creates are owned by PUID:PGID on the host.
The compose file, line by line
services:
smart-transcode:
image: aarondyck/smart-transcode:latest
environment:
PUID: 1000 # = "stat -c %u /srv/media" (the owner of the files)
PGID: 1000 # = "stat -c %g /srv/media" (the group of the files)
UMASK: "002" # new files: rw for owner and group
volumes:
- ./config:/config # owned by PUID:PGID after first start
- /srv/media:/srv/media # the files keep their host owner: 1000:1000
How the host and container line up for a library owned by 1000:1000 (mode 775 folders, 664 files):
| PUID : PGID | Result |
|---|---|
1000 : 1000 | Works. The app is the owner. New files are owned by 1000:1000. |
1001 : 1000 | Works through the group (needs group write, so UMASK=002 and group-writable files). New files are owned by 1001:1000. |
1001 : 1001 | Fails. Neither owner nor group matches: you can read the files at best, but cannot replace them. |
0 : 0 | Works, but runs the app as root and leaves root-owned files behind. Avoid. |
| not set | Defaults to 1000:1000. Fine only if that happens to be your media's owner. |
Several folders, several owners
The app runs as one UID/GID. If your folders have different owners, pick a group they share and set PGID to it, give that group write access, and keep UMASK=002:
sudo groupadd -g 2000 media # once; use the same GID everywhere
sudo chgrp -R media /srv/media
sudo chmod -R g+rwX /srv/media
sudo find /srv/media -type d -exec chmod g+s {} + # new files inherit the group
# then in the compose file: PGID: 2000 UMASK: "002"
Give every container that touches the library (Sonarr, Radarr, Plex…) the same PUID, PGID and UMASK, so none of them creates files the others cannot change.
Network shares
- NFS: the server stores plain numbers, so
PUID/PGIDmust be the numbers the files have on the server (and the export must not squash them). - SMB / CIFS: the numbers come from your mount options. Mount with
uid=1000,gid=1000,file_mode=0664,dir_mode=0775and use the same PUID/PGID. - Keep
/configon a local disk. SQLite on a network share is slow and can corrupt.
When you use user: instead of PUID / PGID
If a compose file sets user: "1000:1000" (or docker run --user), the container starts as that user directly. PUID, PGID and UMASK are then ignored,
nobody fixes the ownership of /config for you (give the folder to that UID first), and the GPU groups are not added (use group_add with the numeric GID of the render group:
stat -c %g /dev/dri/renderD128). Rootless Podman works this way on purpose, see Podman.
Check it from inside
docker exec smart-transcode id transcode # uid / gid it runs as; groups include the GPU's
docker exec smart-transcode ls -ln /srv/media # the numbers it sees; should match the host's ls -ln
docker exec -u transcode smart-transcode touch /srv/media/.write-test && echo writable
docker logs smart-transcode | head # "Smart Transcode 1.0 as transcode (1000:1000, groups: …)"
| Symptom | Likely cause |
|---|---|
| The library looks empty, or folders cannot be added | The app's UID cannot read the folder (owner / group / mode mismatch), or the path inside the container is not the one you mounted. |
| Scans work, but every conversion fails at the end with "permission denied" | The app can read but not write: PUID/PGID are not the owner (or in the group) and the group lacks write permission. |
| Converted files belong to root or to another user | PUID/PGID are 0, unset, or different from the rest of your stack. |
| Sonarr / Radarr cannot read converted files | Different UMASK or group between the containers. Use the same PUID / PGID / UMASK everywhere. |
| "unable to open database file" at start | /config is owned by another UID and the container was started with user:, or sits on a read-only or network mount. |
| No Intel / AMD profile in Settings → Video | The user is not in the group that owns /dev/dri/renderD128 (check with ls -l /dev/dri inside the container). |
The Linux service follows the same rule
install.sh --media /srv/media reads the owner of the folder and runs the service as that user (or creates transcode with the same numeric IDs). It never changes ownership of your files, and it tells you at the end
if the account cannot write to a folder. Container helper: ./docker-setup.sh --media /srv/media does the same and writes the IDs into your .env.