🧰 Bulletproof Jellyfin Docker Setup with Local+Cloud Access and Correct Permissions
Running Jellyfin in Docker is a smart and efficient way to manage a home media server. It’s lightweight, portable, and easy to maintain. But improper configuration can lead to playback errors, metadata issues, and failed scans.
In this step-by-step guide, you'll learn how to:
- ✅ Create a dedicated
jellyfin
user on the host - 📒 Set proper read/write permissions
- 🌐 Enable LAN & Cloudflare Tunnel access
- 📧 Configure SMTP email alerts
- 🔒 Avoid common permission and playback issues
📦 Why Use Docker for Jellyfin?
Docker isolates Jellyfin from your base OS, making it easy to update, backup, and restore. It's especially useful for NAS systems or headless Debian servers. However, Docker containers use internal users, and you must ensure matching permissions on host volumes to avoid errors.
👤 Step 1: Create a Dedicated Jellyfin User
We don’t want to run Jellyfin as root
. Let’s create a system-level non-login user:
sudo useradd -r -s /usr/sbin/nologin -d /nonexistent -M jellyfin
id jellyfin
You’ll get something like:
uid=994(jellyfin) gid=989(jellyfin)
We'll use UID 994 and GID 989 in the Docker stack.
📓 Step 2: Prepare Docker Volumes and Media Folder
We'll use named Docker volumes for /config
and /cache
, and a bind-mounted host directory for media:
/var/lib/docker/volumes/jellyfin_jellyfin_config/_data
/var/lib/docker/volumes/jellyfin_jellyfin_cache/_data
/srv/store/Media
(your actual media path)
🔐 Step 3: Set Correct Permissions
This is the most crucial step. Jellyfin inside the container runs as UID 994
, so all volumes and media folders must allow that UID to access them.
✅ Fix permissions with the following commands:
sudo chown -R 994:989 /var/lib/docker/volumes/jellyfin_jellyfin_config/_data
sudo chown -R 994:989 /var/lib/docker/volumes/jellyfin_jellyfin_cache/_data
sudo chown -R 994:989 /srv/store/Media
sudo find /var/lib/docker/volumes/jellyfin_jellyfin_config/_data -type d -exec chmod 750 {} \;
sudo find /var/lib/docker/volumes/jellyfin_jellyfin_cache/_data -type d -exec chmod 750 {} \;
sudo find /srv/store/Media -type d -exec chmod 750 {} \;
sudo find /var/lib/docker/volumes/jellyfin_jellyfin_config/_data -type f -exec chmod 640 {} \;
sudo find /var/lib/docker/volumes/jellyfin_jellyfin_cache/_data -type f -exec chmod 640 {} \;
sudo find /srv/store/Media -type f -exec chmod 640 {} \;
sudo find /srv/store/Media -type f -exec chmod 750 {} \;
🧱 Step 4: Docker Compose Stack with SMTP and DLNA
version: '3.8'
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
ports:
- "8096:8096"
- "7359:7359/udp"
- "1900:1900/udp"
user: "994:989"
restart: unless-stopped
volumes:
- jellyfin_config:/config
- jellyfin_cache:/cache
- /srv/store/Media:/media
environment:
- TZ=Asia/Kolkata
- JELLYFIN_SMTP_SERVER=smtp.example.com
- JELLYFIN_SMTP_PORT=587
- JELLYFIN_SMTP_USERNAME=your-email@example.com
- JELLYFIN_SMTP_PASSWORD=your-smtp-password
- JELLYFIN_SMTP_FROM_ADDRESS=your-email@example.com
- JELLYFIN_SMTP_USE_SSL=false
- JELLYFIN_SMTP_USE_TLS=true
volumes:
jellyfin_config:
jellyfin_cache:
🌐 Step 5: Access Jellyfin on LAN and Cloud
You can access Jellyfin via:
- LAN:
http://192.168.0.x:8096
- Cloudflare Tunnel:
https://jellyfin.yourdomain.com
🚪 Step 6: Auto-Fix Permissions on Boot (Systemd)
Let’s make sure your permissions are fixed automatically every time the NAS boots.
Step 1: Create Fix Script
sudo nano /usr/local/bin/jellyfin-fix-perms.sh
Paste:
#!/bin/bash
chown -R 994:989 /var/lib/docker/volumes/jellyfin_jellyfin_config/_data
chown -R 994:989 /var/lib/docker/volumes/jellyfin_jellyfin_cache/_data
chown -R 994:989 /srv/store/Media
find /var/lib/docker/volumes/jellyfin_jellyfin_config/_data -type d -exec chmod 750 {} \;
find /var/lib/docker/volumes/jellyfin_jellyfin_cache/_data -type d -exec chmod 750 {} \;
find /srv/store/Media -type d -exec chmod 750 {} \;
find /var/lib/docker/volumes/jellyfin_jellyfin_config/_data -type f -exec chmod 640 {} \;
find /var/lib/docker/volumes/jellyfin_jellyfin_cache/_data -type f -exec chmod 640 {} \;
find /srv/store/Media -type f -exec chmod 640 {} \;
find /srv/store/Media -type f -exec chmod 750 {} \;
Then make it executable:
sudo chmod +x /usr/local/bin/jellyfin-fix-perms.sh
Step 2: Create systemd service
sudo nano /etc/systemd/system/jellyfin-perms.service
Paste:
[Unit]
Description=Auto-fix Jellyfin volume permissions at boot
After=local-fs.target docker.service
Wants=docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/jellyfin-fix-perms.sh
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
Step 3: Enable and Start
sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable jellyfin-perms.service
sudo systemctl start jellyfin-perms.service
Check status:
systemctl status jellyfin-perms.service
💪 Final Thoughts
With Docker, Jellyfin becomes easy to manage. With permissions automated and SMTP configured, you now have a self-healing, production-grade media server.
✅ Reliable, ✅ Secure, ✅ LAN + Remote, ✅ Ready to Scale
If you want GPU acceleration, Cloudflare Zero Trust, or media backups next, follow along — more posts coming soon!