Jellyfin na VPS w Dockerze - instalacja
Instalacja Jellyfin w Docker na VPS. Rozwiązania problemów z uprawnieniami do plików oraz konfiguracja transkodowania CPU zamiast GPU w kontenerze.
Cel projektu
Serwer multimedialny Jellyfin na VPS: jeden kontener, trzy wolumeny oraz dysk block-storage przechowujący filmy i seriale, dostępny z poziomu dowolnej przeglądarki lub aplikacji Jellyfin. Instalacja wymaga pliku compose o długości piętnastu linii. Większość błędów wynika z dwóch przyczyn: braku uprawnień do plików dla kontenera oraz próby transkodowania wideo na VPS bez dedykowanego GPU. Przewodnik skupia się na tych dwóch kwestiach, ponieważ to one generują najwięcej problemów technicznych.
Jellyfin jest darmowy i posiada otwarty kod źródłowy. Nie wymaga zakładania konta, nie posiada płatnych funkcji ani nie przesyła danych telemetrycznych. To sprawia, że produkt ten znajduje się na listach rzeczy wartych self-hostingu w 2026. Oprogramowanie odtwarza własne zasoby użytkownika. Nie dostarcza własnych treści; niniejszy przewodnik nie dotyczy pozyskiwania materiałów.
Rzeczywistość transkodowania przed wynajmem serwera
Należy przeczytać tę sekcję w pierwszej kolejności, ponieważ wpływa ona na wybór produktu. Serwer multimedialny wykonuje jedną z dwóch operacji po uruchomieniu odtwarzania. Direct play przesyła plik w oryginalnej formie: VPS odczytuje bajty z dysku i przesyła je przez sieć, co prawie nie obciąża procesora CPU. Transcoding polega na re-enkodowaniu wideo w czasie rzeczywistym — zmienia rozdzielczość, kodek lub nakłada napisy — co stanowi proces obciążający procesor CPU.
Typowy VPS nie posiada jednostki GPU. Każde transkodowanie odbywa się na procesorze CPU przy użyciu libx264/libx265, a kodowanie programowe jest kosztowne pod względem zasobów. Pojedyncze transkodowanie 1080p H.264 może nasycić kilka współdzielonych vCPU; transkodowanie 4K lub HEVC zazwyczaj nie osiąga prędkości czasu rzeczywistego, co powoduje przerywanie odtwarzania i ciągłe buforowanie. Transkodowanie sprzętowe — rozwiązanie zapewniające niskie koszty na urządzeniach domowych z iGPU Intel lub kartą Nvidia — nie jest dostępne na VPS, chyba że dostawca oferuje instancje z GPU.
Strategia dla VPS polega zatem na: unikaniu transkodowania. Należy przechowywać bibliotekę w kodekach obsługiwanych natywnie przez klientów — wideo H.264, audio AAC lub AC3, w kontenerach MP4 lub MKV — oraz wybierać aplikacje klienckie wspierające direct-play: natywne aplikacje Jellyfin dla Android TV, iOS i Roku, a także Infuse, Kodi oraz desktopowy Jellyfin Media Player. Dzięki temu serwer VPS nie uruchamia procesu ffmpeg, co pozwala skromnemu serwerowi z 2 vCPU obsługiwać kilku użytkowników jednocześnie. Planowanie transkodowania wymaga znacznie większej i droższej maszyny, a nawet wtedy obsługa 4K jest ryzykowna.
Należy również obliczyć zapotrzebowanie na pasmo transmisji, ponieważ jest to kolejna istotna kwestia. Direct play przesyła plik z jego oryginalnym bitrate. Skompresowany plik 1080p wymaga 8-12 Mbps; remux 1080p Blu-ray wymaga 20-30 Mbps; 4K HDR wymaga 40-80 Mbps. Trzech użytkowników odtwarzających pliki 10 Mbps generuje stały ruch upload na poziomie 30 Mbps z serwera VPS. Należy sprawdzić dwie wartości w planie taryfowym: prędkość portu (czy pozwala na upload 30 Mbps?) oraz miesięczny limit transferu. Jeden dwugodzinny film 10 Mbps to około 9 GB danych; limit 1 TB/miesiąc pozwala na odtworzenie nieco ponad stu takich filmów miesięcznie (trzy lub cztery dziennie). Rodzina oglądająca materiały 4K, przy bitrate wyższym czterokrotnie lub ośmiokrotnie, zużyje limit znacznie szybciej.
Wymagania wstępne
- Nowy serwer VPS Ubuntu 24.04 KVM z uprawnieniami root lub sudo oraz zainstalowanym pakietem Docker i wtyczką Compose.
- Wolumen typu block-storage na pliki multimedialne, o rozmiarze dostosowanym do biblioteki (patrz: wyznaczanie rozmiaru poniżej). Pliki wideo nie powinny być przechowywane na małym dysku root dostarczanym przez VPS.
- Domena, jeśli wymagany jest publiczny dostęp przez HTTPS, lub WireGuard VPN na tym samym VPS, jeśli system ma pozostać prywatny.
- Materiały multimedialne, do których posiadane są prawa do strumieniowania — własne kopie (rips), własne nagrania lub pliki będące własnością użytkownika.
Najpierw zamontuj pamięć block storage
Podłącz wolumen w panelu dostawcy, a następnie zlokalizuj go i zamontuj. Nazwę urządzenia można sprawdzić w lsblk — będzie to wartość typu /dev/sdb lub /dev/vdb, nigdy dysk root.
lsblk
sudo mkfs.ext4 /dev/sdb # ONLY on a new, empty volume — this ERASES it
sudo mkdir -p /mnt/media
sudo blkid /dev/sdb # copy the UUID shown for this deviceMontuj wolumen za pomocą UUID, a nie za pomocą /dev/sdb. Litery urządzeń mogą ulec zmianie po restarcie systemu, co może spowodować błędne formatowanie lub montowanie niewłaściwego dysku. Dodaj jedną linię do pliku /etc/fstab:
UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt/media ext4 defaults,nofail 0 2sudo mount -a
df -h /mnt/mediaKluczowe jest nofail: brak tej opcji spowoduje, że po odłączeniu wolumenu system nie uruchomi się i przejdzie do trybu emergency shell. Najczęstszym błędem jest uruchomienie mkfs.ext4 na wolumenie zawierającym już dane — powoduje to ich usunięcie. Formatuj wyłącznie nowe wolumeny; jeśli dysk zawiera już bibliotekę, pomiń ten krok i przejdź bezpośrednio do konfiguracji fstab.
Przygotuj media zgodnie z wymaganiami Jellyfin
Jellyfin dopasowuje metadane na podstawie nazw folderów i plików. Nieprawidłowa struktura spowoduje, że filmy zostaną wyświetlone jako pliki bez tytułów i plakatów lub że odcinek zostanie przypisany do niewłaściwego serialu. Obowiązują dokładnie trzy zasady: każdy film musi znajdować się w osobnym folderze Name (Year) o nazwie zgodnej z nazwą pliku; foldery sezonów muszą mieć nazwę Season 01, a nie S01; pliki odcinków muszą używać S01E01; a wydania specjalne należy umieścić w Season 00.
/mnt/media
├── Movies
│ ├── Blade Runner (1982)
│ │ └── Blade Runner (1982).mkv
│ └── Arrival (2016)
│ └── Arrival (2016).mkv
└── Shows
└── Severance (2022)
├── Season 01
│ ├── Severance - S01E01.mkv
│ └── Severance - S01E02.mkv
└── Season 00
└── Severance - The Lexington Letter.mkvStosowanie (Year) w przypadku filmów nie służy celom estetycznym — pozwala to odróżnić remake'i, dzięki czemu mechanizm dopasowywania wybierze właściwy tytuł. Foldery Movies oraz Shows należy zachować jako oddzielne katalogi główne, ponieważ każdy z nich tworzy osobną bibliotekę Jellyfin dla konkretnego typu treści. Mieszanie tych folderów powoduje błędy w dostawcy metadanych.
Uprawnienia: główna przyczyna braku bibliotek
Poniższy błąd powszechnie marnuje czas użytkowników. Oficjalny obraz jellyfin/jellyfin nie obsługuje zmiennych środowiskowych PUID/PGID — należą one do obrazu LinuxServer.io (lscr.io/linuxserver/jellyfin). W oficjalnym obrazie użytkownik jest konfigurowany za pomocą klucza user: w pliku compose. Jeśli zostanie on pominięty, kontener uruchamia się jako root. Niezależnie od wybranego obrazu, zasada pozostaje taka sama: uid/gid, pod którym działa kontener, musi mieć uprawnienia do odczytu oraz przechodzenia (traverse) przez każdy katalog z mediami.
Będziemy używać uid/gid 1000, co odpowiada pierwszemu użytkownikowi spoza grupy root w systemie Ubuntu. Należy sprawdzić własne wartości i ustawić własność plików:
id # confirm your user is uid=1000 gid=1000
sudo chown -R 1000:1000 /mnt/media
sudo find /mnt/media -type d -exec chmod 755 {} \;
sudo find /mnt/media -type f -exec chmod 644 {} \;
mkdir -p ~/jellyfin/config ~/jellyfin/cache
sudo chown -R 1000:1000 ~/jellyfinKatalogi wymagają uprawnień do wykonywania (bit x w 755), a nie tylko do odczytu — bez tego kontener nie może wejść do folderu, mimo że może wyświetlić listę nazw plików. Przyczyną całkowitego braku bibliotek jest często katalog nadrzędny: jeśli uid kontenera nie może przejść przez sam punkt montowania, nie uzyska dostępu do /media/Movies lub /media/Shows. W takim przypadku każda biblioteka staje się pusta, a w logach pojawia się błąd Access to the path ... is denied. Każdy pojedynczy folder z mediami, którego kontener nie może odczytać, zostaje zarejestrowany w logach i pominięty. Powoduje to, że pliki skopiowane jako root bezpowrotnie znikają z biblioteki. Dlatego należy stosować rekurencyjne polecenie chown oraz ustawić bit wykonywania dla każdego katalogu, zamiast naprawiać tylko jeden folder.
Plik docker-compose
services:
jellyfin:
image: jellyfin/jellyfin:10
container_name: jellyfin
user: "1000:1000"
restart: unless-stopped
ports:
- "127.0.0.1:8096:8096"
volumes:
- ./config:/config
- ./cache:/cache
- /mnt/media:/media:ro
environment:
- JELLYFIN_PublishedServerUrl=https://jellyfin.example.comAnaliza linii: user: "1000:1000" odpowiada za ustawienie uprawnień do plików, co odpowiada przypisanemu wyżej właścicielowi. /config zawiera całą zawartość serwera — konta, biblioteki, metadane oraz stan monitorowania — dlatego musi posiadać uprawnienia do zapisu i jest to element podlegający kopii zapasowej. /cache to tymczasowa przestrzeń robocza. Montowanie mediów jako :ro (tylko do odczytu) jest celowe: Jellyfin domyślnie przechowuje okładki i metadane w /config, więc nie wymaga zapisu w bibliotece, a tryb read-only chroni pliki przed przypadkowym usunięciem lub błędem wtyczki. Port jest celowo przypisany do 127.0.0.1 — logowanie webowe Jellyfin odbywa się przez HTTP, więc port 8096 nie jest udostępniany w publicznym internecie. JELLYFIN_PublishedServerUrl to adres ogłaszany przez serwer w celu lokalnego autodyskrylowania — jest to rozgłoszenie UDP w sieci LAN, dzięki czemu klienci w internecie go nie widzą i używają adresu wpisanego ręcznie w aplikacji. Należy ustawić tutaj adres, który mają otrzymać klienci, biorąc pod uwagę konieczność ręcznego wpisania tego adresu na urządzeniach zdalnych.
Uruchomienie z katalogu compose:
docker compose up -d
docker logs -f jellyfinPierwsze uruchomienie: kreator konfiguracji i biblioteki
Ponieważ port jest przypisany do localhost, należy uzyskać dostęp do kreatora poprzez tunel SSH z laptopa, zamiast otwierać port w firewallu:
ssh -L 8096:127.0.0.1:8096 you@your-vps-ipNastępnie należy przejść pod adres http://localhost:8096. Kreator przeprowadzi przez wybór języka, a następnie tworzenie użytkownika admin z silnym hasłem — konto to stanowi uprawnienia administratora serwera, dlatego nie należy używać słabych haseł. Należy dodać pierwszą bibliotekę: wybrać typ zawartości Movies, wskazać ścieżkę /media/Movies (ścieżka wewnątrz kontenera, a nie ścieżka hosta), a następnie powtórzyć proces dla Shows w /media/Shows. Po zakończeniu proces rozpocznie skanowanie Jellyfin. Poprawny wynik to wyświetlenie plakatów i tytułów w ciągu jednej lub dwóch minut dla małej biblioteki. Dodawanie lub edycja bibliotek odbywa się później w sekcji Dashboard → Libraries; wymuszenie ponownego skanowania następuje poprzez Scan All Libraries.
W przypadku korzystania z transkodowania należy otworzyć Dashboard → Playback → Transcoding i ustawić ścieżkę tymczasową transkodowania na /cache/transcodes, aby operacje odbywały się na wolumenie cache, zamiast zajmować miejsce na /config. Opcję hardware acceleration należy pozostawić ustawioną na None — system nie posiada akceleratora GPU.
Dostęp zdalny: TLS reverse proxy czy VPN
Istnieją dwa bezpieczne sposoby na dostęp do Jellyfin z zewnątrz oraz jeden niebezpieczny sposób, którego należy unikać. Niebezpiecznym sposobem jest wystawienie portu 8096 bezpośrednio na publiczny internet: dane logowania są przesyłane tekstem jawnym, a port zostaje poddany atakom brute-force w ciągu kilku godzin.
Opcja A — TLS reverse proxy. Umieść Jellyfin na subdomenie za Traefik z automatycznym TLS dla aplikacji Docker, lub za nginx z certyfikatem Let's Encrypt wydanym przez Certbot. Jellyfin wykorzystuje WebSockets do aktualizacji w czasie rzeczywistym, dlatego proxy musi przekazywać nagłówki upgrade. Traefik realizuje to automatycznie; nginx wymaga ich jawnego zdefiniowania oraz obsługi HTTP/1.1 w komunikacji z upstream, aby proces upgrade został zainicjowany:
location / {
proxy_pass http://127.0.0.1:8096;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}Ustaw JELLYFIN_PublishedServerUrl na adres https://, aby lokalne mechanizmy autodetekcji przesyłały poprawny adres URL — aplikacje zdalne korzystają z podanego adresu — oraz dodaj fail2ban w celu ograniczenia ataków brute-force dla panelu logowania. Po upublicznieniu serwera skonfiguruj Uptime Kuma dla danego adresu URL, aby otrzymać powiadomienie o awarii przed użytkownikami.
Opcja B — zachowanie prywatności poprzez VPN. Nie wystawiaj portu 8096 do sieci. Uzyskuj dostęp do Jellyfin wyłącznie przez tunel WireGuard terminujący na tym samym urządzeniu. Dla gospodarstwa domowego jest to najprostszy bezpieczny wybór — brak certyfikatów, brak ekspozycji publicznej i brak podatności na ataki brute-force. Przypisz kontener do adresu tunelu lub localhost i łącz się przez VPN. Szczegóły dotyczące konfiguracji tunelu znajdują się w konfiguracji WireGuard VPN dla prywatnego VPS.
Rozmiarowanie pamięci masowej i kopie zapasowe
Należy planować budżet na podstawie jakości, a nie liczby plików. Skompresowane filmy 1080p zajmują od 4 do 15 GB każdy; remux 1080p zajmuje 20-40 GB; sezon serialu 1080p zajmuje 15-40 GB; dowolny plik 4K zajmuje 40-100 GB na film. Biblioteka zawierająca kilkaset filmów oraz kilka seriali wymaga wolumenu o rozmiarze 2-4 TB. Taniej jest raz zwiększyć przydział wolumenu blokowego, niż przeprowadzać późniejszą migrację.
/config zawiera cały stan serwera, dlatego jest to jedyny element wymagający kopii zapasowej. Należy wykonać snapshot lub procedurę stop-and-tar i przechowywać kopię poza urządzeniem:
docker compose down
sudo tar czf jellyfin-config-$(date +%F).tgz -C ~/jellyfin config
docker compose up -d/cache oraz folder transcode są plikami tymczasowymi. Dane multimedialne na /mnt/media należy kopiować oddzielnie lub uznać za możliwe do ponownego nagrania — ze względu na rozmiar większość użytkowników wybiera tę drugą opcję. Aktualizacje są docker compose pull && docker compose up -d; tag :10 pozostaje w obrębie wersji 10.x, więc przejście na kolejną wersję główną wymaga celowej edycji tagu — należy zapoznać się z opisem zmian Jellyfin przed dokonaniem zmiany, ponieważ migracje schematu biblioteki występują przy wersjach głównych.
Tryby awarii oraz wyświetlane komunikaty
Biblioteka jest pusta po skanowaniu. Log w sekcji Dashboard → Logs (lub ~/jellyfin/config/log/log_*.log) zawiera:
System.UnauthorizedAccessException: Access to the path '/media/Movies' is denied.UID kontenera nie ma uprawnień do odczytu tej ścieżki. Przyczyna: pliki są własnością użytkownika root lub innego użytkownika niż wartość user:, katalog nie posiada bitu wykonawczego (execute bit) lub punkt montowania nie jest dostępny dla tego UID. Rozwiązanie: chown -R 1000:1000 /mnt/media, katalogi 755, pliki 644, a następnie ponowne skanowanie.
Playback obciąża CPU i buforuje. docker stats jellyfin wskazuje obciążenie CPU bliskie 100% pomnożone przez liczbę rdzeni, a Dashboard → Playback wyświetla sesję jako Transcode przy prędkości poniżej 1.0x. Klient nie korzysta z trybu direct-play, zatem VPS wykonuje transkodowanie CPU wolniej niż czas rzeczywisty, co powoduje błędy. Przyczyna: nieobsługiwany kodek lub kontener, wypalanie napisów (subtitle burn-in) lub tone-mapping HDR. Rozwiązanie: należy użyć klienta obsługującego direct-play, przechowywać źródła w formacie H.264/AAC, używać napisów tekstowych (SRT) zamiast obrazowych (PGS/VOBSUB) wymuszających wypalanie oraz całkowicie unikać treści 4K HDR na urządzeniach opartych wyłącznie na CPU.
"No compatible streams are available." Pełny komunikat brzmi zazwyczaj: "This client isn't compatible with the media and the server isn't sending a compatible media format." Klient odrzucił źródło, a alternatywne transkodowanie również nie mogło zostać uruchomione. Przyczyna: błędna komenda ffmpeg, nieczytelny plik lub profil użytkownika blokujący konwersję wideo. Rozwiązanie: należy sprawdzić linię ffmpeg w Dashboard → Logs, zweryfikować czy plik w ogóle się odtwarza, sprawdzić uprawnienia odtwarzania użytkownika (jeśli wymagane jest transkodowanie) oraz przetestować inny klient, aby wykluczyć problemy z kodekami w przeglądarce.
Filmy nie mają plakatów lub mają błędne plakaty. Metadane nie pasują. Przyczyna: film nie znajduje się we własnym folderze Name (Year), folder sezonu ma nazwę S01 zamiast Season 01, odcinki nie są w formacie S01E01 lub brakuje roku. Rozwiązanie: należy zmienić nazwy zgodnie z powyższym schematem, a następnie wybrać Refresh metadata → Replace all lub użyć funkcji Identify dla pojedynczego elementu, aby przypisać właściwy wpis z TMDB/TVDB.
FAQ
Czy VPS może transkodować wideo bez GPU?
Tak, ale wyłącznie przy użyciu CPU, co generuje wysokie koszty. Pojedyncza transkodacja 1080p metodą software'ową może obciążyć kilka vCPU. Transkodowanie 4K lub HEVC zazwyczaj nie osiąga czasu rzeczywistego, co powoduje buforowanie. Najlepszym rozwiązaniem jest unikanie transkodowania: należy przechowywać bibliotekę w formacie H.264/AAC i używać aplikacji klienckich obsługujących direct-play, dzięki czemu VPS przesyła jedynie dane. Instancję z GPU należy wynająć tylko wtedy, gdy transkodowanie w locie jest niezbędne.
Dlaczego moja biblioteka Jellyfin jest pusta po skanowaniu?
Przyczyną jest najczęściej brak uprawnień. Oficjalny obraz jellyfin/jellyfin działa z uprawnieniami użytkownika user: (lub root). Jeśli pliki nie są czytelne dla danego uid, skanowanie kończy się błędem w logach Access to the path ... is denied i pliki są pomijane. Należy naprawić własność plików za pomocą chown -R 1000:1000 /mnt/media, nadać katalogom bit wykonania (755) i ponownie przeprowadzić skanowanie. Należy również sprawdzić katalogi nadrzędne, ponieważ jeśli uid kontenera nie może przejść przez /mnt/media, nie dotrze do folderów biblioteki, co spowoduje jej pusty stan. Drugą najczęstszą przyczyną jest struktura folderów niezgodna z wymaganiami Jellyfin.
Jak bezpiecznie uzyskać zdalny dostęp do Jellyfin?
Istnieją dwa skuteczne rozwiązania. Pierwsze to umieszczenie usługi za reverse proxy TLS na subdomenie w celu szyfrowania logowania i strumienia oraz instalacja fail2ban. Nie należy wystawiać portu 8096, ponieważ przesyła on hasło w tekście jawnym. Drugie rozwiązanie to całkowite odizolowanie usługi i dostęp wyłącznie przez VPN, co jest najprostszym bezpiecznym wyborem dla gospodarstwa domowego. W aplikacjach należy podać bezpośredni adres publiczny, ponieważ funkcja autodiscovery opiera się na rozgłaszaniu w sieci lokalnej i nie działa dla klientów łączących się przez internet.
Ile miejsca na dysku i pasma transmisji potrzebuje VPS z Jellyfin?
Zapotrzebowanie na dysk zależy od jakości: należy założyć 4-15 GB na skompresowany film 1080p, 20-40 GB na remux oraz 40-100 GB na 4K, zatem większość bibliotek wymaga wolumenu o pojemności 2-4 TB. Pasmo transmisji zależy od bitrate'u podczas direct-play: 8-12 Mbps dla strumienia 1080p, znacznie więcej dla 4K. Należy upewnić się, że prędkość portu obsługuje liczbę jednoczesnych widzów oraz monitorować miesięczny limit transferu. Należy zapewnić zapas mocy CPU, jeśli planowane jest transkodowanie; w przypadku korzystania z direct-play priorytetem powinno być pasmo transmisji, a nie liczba rdzeni.
Czy uruchamianie Jellyfin na VPS jest legalne?
Jellyfin to darmowe oprogramowanie open-source, a jego uruchamianie jest w pełni legalne. Kluczowa jest treść: należy strumieniować wyłącznie media, do których posiada się prawa lub własność (np. własne kopie z płyt, nagrania lub pliki, do których ma się prawo). Jellyfin nie dostarcza żadnych mediów ani narzędzi do ich pozyskiwania; jest to jedynie odtwarzacz dla posiadanej już biblioteki.