Invidious

Invidious to otwartoźródłowy, alternatywny frontend do YouTube napisany w Crystalu (licencja AGPLv3). Pobiera dane z YouTube po stronie serwera i serwuje własny, lekki interfejs: bez reklam, bez skryptów Google w przeglądarce, bez konta Google. Subskrypcje, playlisty i historia oglądania są trzymane lokalnie w PostgreSQL, a nie na koncie YouTube.

Invidious — strona Popularne w jasnym motywie, widok bez logowania

Interfejs jest prosty i szybki: feed subskrypcji, wyszukiwarka z filtrami i strony kanałów. Feed jest chronologiczny, bez algorytmu rekomendacji i bez wplecionych Shorts, a każdy kanał udostępnia RSS.

Zakładka Popularne nie odpowiada trendom YouTube: Invidious wylicza ją z subskrypcji użytkowników instancji. Pod odtwarzaczem jest lista Pobierz jako ze wszystkimi formatami i tryb audio-only. Konto Invidious nie ma związku z kontem Google, a subskrypcje można zaimportować z Google Takeout, NewPipe, FreeTube lub OPML.

Architektura

Instalacja składa się z trzech kontenerów: aplikacji Invidious, serwisu invidious-companion i bazy PostgreSQL. Od 2025 roku samo Invidious nie wystarcza. YouTube blokuje zapytania z adresów serwerowych komunikatem „Sign in to confirm you're not a bot”, a pobranie strumieni wymaga generowania tokenów po_token i deszyfrowania sygnatur playera. Tę część przejął osobny serwis invidious-companion napisany w TypeScript (Deno, biblioteka YouTube.js).

Kontenery tworzące instalację Invidious i ich odpowiedzialności.
KontenerOpis
invidiousInterfejs WWW, API, konta, subskrypcje
invidious-companionPlayer YouTube, po_token, proxy wideo
postgres_17Baza danych (współdzielona)

Architektura w sieci lokalnej

graph LR
    B[Przeglądarka] -->|HTTP :80| P[Reverse proxy]
    P -->|"/* → :3000"| I[invidious]
    P -->|"/companion/* → :8282"| C[invidious-companion]
    I -->|SQL :5432| DB[(PostgreSQL<br/>baza invidious)]
    I -->|"private_url :8282<br/>SERVER_SECRET_KEY"| C
    C --- V[(companioncache<br/>cache playera)]

Reverse proxy obsługuje jedną domenę i rozdziela ruch po ścieżce. Żądania z prefiksem /companion trafiają bezpośrednio do companiona, wszystko inne do Invidious. Baza nie jest wystawiona na zewnątrz: Invidious łączy się z nią po nazwie kontenera w sieci postgresql_17_default. Z companionem Invidious rozmawia wewnątrz sieci Docker (private_url), a tę komunikację zabezpiecza wspólny klucz COMPANION_KEY.

Połączenie z YouTube

Odtworzenie filmu przebiega w dwóch krokach. Najpierw przeglądarka otwiera stronę filmu w Invidious, które pyta companiona o dane playera:

sequenceDiagram
    participant B as Przeglądarka
    participant I as invidious
    participant C as companion
    participant Y as YouTube
    B->>I: /watch?v=ID
    I->>C: dane playera
    C->>Y: player API + po_token
    Y-->>C: formaty strumieni
    C-->>I: formaty i napisy
    I-->>B: strona z linkami /companion/...

Potem player w przeglądarce pobiera wideo już bez udziału Invidious, bezpośrednio od companiona:

sequenceDiagram
    participant B as Przeglądarka
    participant C as companion
    participant Y as YouTube
    B->>C: manifest DASH
    C-->>B: lista segmentów
    loop każdy segment
        B->>C: /companion/videoplayback
        C->>Y: segment z googlevideo.com
        Y-->>C: audio/wideo
        C-->>B: segment
    end

Metadane potrzebne do list (wyszukiwanie, kanały, feedy subskrypcji, komentarze) Invidious pobiera samodzielnie z wewnętrznego API YouTube. Do companiona trafia wszystko, co dotyczy odtwarzania: dane playera, manifest DASH, napisy i same segmenty wideo.

Invidious obsługuje dwa tryby dostępu do companiona:

  • Tylko private_url: przeglądarka nie zna adresu companiona. Wszystkie żądania /companion/... przyjmuje Invidious i przekazuje dalej, więc każdy fragment wideo przechodzi przez dwa kontenery. Przy kilku równoległych odtworzeniach w jakości DASH Invidious staje się wąskim gardłem.
  • private_url + public_url: Invidious nadal pyta companiona o dane playera wewnątrz sieci Docker, ale w HTML wstawia linki do public_url. Przeglądarka pobiera manifest i segmenty bezpośrednio od companiona. Wymaga to dodatkowej trasy w reverse proxy: prefiks ścieżki /companion w domenie Invidious kierowany na port 8282 companiona.

Opisywana instancja używa drugiego trybu. Manifest DASH generowany jest z local=true, a jego BaseURL wskazuje na /companion/videoplayback, więc segmenty dalej idą przez companiona. Przeglądarka nie nawiązuje połączeń z serwerami Google, a YouTube widzi jedynie publiczny adres IP łącza domowego. Kosztem trybu proxy jest transfer, bo każdy oglądany film przechodzi przez serwer.

Konfiguracja

config.yml

Konfiguracja Invidious leży w osobnym pliku config.yml, montowanym do kontenera tylko do odczytu.

check_tables: true

invidious_companion:
  - private_url: "http://invidious-companion:8282/companion"
    public_url: "http://invidious.mini-1.lan/companion"

domain: invidious.mini-1.lan
external_port: 80
https_only: false

statistics_enabled: false
registration_enabled: true
captcha_enabled: false

default_user_preferences:
  locale: pl
  region: PL
  dark_mode: light
  quality: dash
  related_videos: true
  # bez "Na czasie" (Trending)
  feed_menu: ["Popular", "Subscriptions", "Playlists"]
  default_home: Popular

docker-compose.yml

services:
  invidious:
    image: quay.io/invidious/invidious:latest
    container_name: invidious
    restart: unless-stopped
    environment:
      INVIDIOUS_DATABASE_URL: postgres://invidious:${DB_PASSWORD}@postgres_17:5432/invidious
      INVIDIOUS_HMAC_KEY: ${HMAC_KEY}
      INVIDIOUS_INVIDIOUS_COMPANION_KEY: ${COMPANION_KEY}
    volumes:
      - ./config.yml:/invidious/config/config.yml:ro
    healthcheck:
      test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/stats || exit 1
      interval: 30s
      timeout: 5s
      retries: 2
    depends_on:
      - invidious-companion
    networks: [proxy, postgres_internal]
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: "1.0"

  invidious-companion:
    image: quay.io/invidious/invidious-companion:latest
    container_name: invidious-companion
    restart: unless-stopped
    environment:
      SERVER_SECRET_KEY: ${COMPANION_KEY}
    read_only: true
    cap_drop: [ALL]
    security_opt:
      - no-new-privileges:true
    volumes:
      - companioncache:/var/tmp/youtubei.js:rw
    networks: [proxy]
    labels:
      com.centurylinklabs.watchtower.enable: "true"
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"

volumes:
  companioncache:

networks:
  proxy:
    external: true
  postgres_internal:
    external: true
    name: postgresql_17_default

.env

DB_PASSWORD=<haslo-do-bazy>
HMAC_KEY=<losowy-ciag>          # openssl rand -hex 20
COMPANION_KEY=<16-znakow>       # dokładnie 16 znaków [a-zA-Z0-9]: openssl rand -hex 8

Parametry konfiguracyjne

Najważniejsze opcje Invidious w opisywanej instalacji. Pełna lista w config.example.yml.
ParametrWartośćOpis
database_urlINVIDIOUS_DATABASE_URLPołączenie z PostgreSQL w formacie URL, z hasłem z .env. Instancja korzysta ze współdzielonego kontenera bazy przez zewnętrzną sieć Docker.
check_tablestruePrzy starcie tworzy brakujące tabele i kolumny. Bez tego bazę trzeba inicjalizować ręcznie skryptami SQL.
invidious_companionprivate_url, public_urlLista instancji companiona. private_url to adres w sieci Docker, używany przez Invidious. public_url to adres companiona widziany przez przeglądarkę (domena instancji + /companion); bez niego Invidious przekazuje cały ruch wideo samodzielnie.
invidious_companion_keyINVIDIOUS_INVIDIOUS_COMPANION_KEYWspólny sekret z SERVER_SECRET_KEY companiona.
hmac_keyINVIDIOUS_HMAC_KEYKlucz do podpisywania tokenów. Wymagany, bez niego Invidious nie wystartuje.
domainnazwa hosta instancjiDomena, pod którą instancja jest widoczna. Używana w generowanych linkach, RSS i ciasteczkach.
external_port80Port widoczny dla klienta (port reverse proxy), nie port kontenera.
https_onlyfalseInstancja działa po HTTP w sieci lokalnej. Przy true linki i ciasteczka wymuszałyby HTTPS.
statistics_enabledfalseWyłącza publiczne statystyki instancji (/api/v1/stats zwraca tylko nazwę, wersję i gałąź oprogramowania).
registration_enabledtruePozwala zakładać konta. Instancja jest dostępna tylko z sieci lokalnej.
captcha_enabledfalseBrak captchy przy logowaniu i rejestracji.
default_user_preferenceslocale: pl, region: PL, dark_mode: lightDomyślne preferencje dla nowych kont i niezalogowanych: polski interfejs, region treści PL, jasny motyw.
qualitydashDomyślnie adaptacyjny strumień DASH (do najwyższej dostępnej rozdzielczości) zamiast pojedynczego pliku MP4 360p/720p.
feed_menu / default_homePopular, Subscriptions, Playlists / PopularZakładki w menu. Usunięta zakładka Trending, ponieważ YouTube wycofał stronę „Na czasie”.

Companion: utwardzenie kontenera

Companion przetwarza dane i kod pobierane z YouTube (skrypty playera wykonywane przez YouTube.js), więc jest najbardziej narażonym elementem instalacji. Poza cache playera nie przechowuje stanu, co pozwala uruchomić go z trzema ograniczeniami w docker-compose.yml:

Opcje ograniczające uprawnienia kontenera invidious-companion.
OpcjaOpis
read_only: trueSystem plików kontenera jest tylko do odczytu, więc nie da się podmienić plików aplikacji ani zostawić nowych binarek. Zapisywalny jest wyłącznie wolumen companioncache (/var/tmp/youtubei.js) na cache playera.
cap_drop: [ALL]Usuwa wszystkie capabilities Linuksa, które Docker domyślnie nadaje kontenerom (np. CAP_CHOWN, CAP_NET_RAW, CAP_SETUID). Nawet proces z UID 0 nie zmieni właściciela plików, nie otworzy surowych gniazd ani nie zmieni UID. Companion potrzebuje tylko zwykłych połączeń TCP, więc nie traci żadnej funkcji.
no-new-privileges:trueUstawia flagę jądra PR_SET_NO_NEW_PRIVS. Procesy nie mogą zyskać uprawnień przez binarki setuid/setgid ani file capabilities, co blokuje eskalację np. przez su po przejęciu procesu.

Te trzy opcje są niezależne i warto stosować je razem. read_only zamyka zapis, cap_drop odbiera uprawnienia, które proces już ma, a no-new-privileges uniemożliwia zdobycie nowych.

Aktualizacje

Strategia aktualizacji jest rozdzielona:

  • invidious-companion ma label com.centurylinklabs.watchtower.enable, więc Watchtower podmienia go automatycznie. YouTube często zmienia player i mechanizmy anty-botowe, a poprawki trafiają do companiona w ciągu godzin lub dni.
  • invidious aktualizowany jest ręcznie (docker compose pull && docker compose up -d), bo nowa wersja może migrować schemat bazy.

Invidious vs YouTube

Zalety

  • Brak reklam i sponsorowanych wstawek w interfejsie, bez rozszerzeń w przeglądarce.
  • Prywatność: Google nie dostaje historii oglądania, wyszukiwań ani ciasteczek. Subskrypcje i historia leżą we własnej bazie.
  • Chronologiczny feed bez algorytmu i bez Shorts wymuszanych na stronie głównej.
  • Pobieranie i tryb audio bez abonamentu.
  • Lekkość: strona działa bez ciężkiego JS YouTube.
  • API (/api/v1/...) zgodne z wieloma klientami zewnętrznymi i przydatne do automatyzacji (RSS, skrypty, agenci).

Wady

  • Kruchość: YouTube regularnie zmienia player i zabezpieczenia. Po takiej zmianie odtwarzanie przestaje działać do czasu aktualizacji companiona (stąd Watchtower na companionie).
  • Jedno wyjściowe IP: wszystkie zapytania idą z adresu domowego łącza. Przy intensywnym użyciu YouTube może ograniczyć ten adres.
  • Brak interakcji: nie da się polubić filmu, skomentować ani wspierać twórcy członkostwem z poziomu Invidious.
  • Brak oficjalnych aplikacji mobilnych i TV; wygoda zależy od klientów zewnętrznych.
  • Brak archiwum: Invidious niczego nie przechowuje, więc film usunięty z YouTube znika także z instancji. Zapisywanie filmów na własnym dysku opisuje wpis MeTube i Tube Archivist .

Rozszerzenie LibRedirect (GitHub, Firefox, Chrome/Chromium) w przeglądarce automatycznie przepisuje linki youtube.com/watch?v=... na adres własnej instancji Invidious. Linki udostępnione przez innych otwierają się wtedy od razu we własnej instancji. Tak samo działa dla linków reddit.com i instancji Redlib .

Linki