How to Watch YouTube Privately with Invidious (No Ads, No Account, No Tracking, on Your Server)

YouTube's site is an advert-and-recommendation machine wrapped round the videos. Invidious is an alternative front end: your server fetches the video and metadata from YouTube and shows them on a clean page — no ads, no autoplay rabbit hole, subscriptions stored locally with no Google account, an RSS feed per channel for FreshRSS (89), and phone apps that use it. The honest caveat up front: YouTube actively fights unofficial clients, so Invidious needs updates and sometimes workarounds. For a household it's worth it.
Quick reference
| Task | What |
|---|---|
| Folder | ~/docker/invidious/compose.yaml |
| Web | http://server-ip:3010 |
| Needs | PostgreSQL, an hmac_key, and (now) a companion or PO-token helper for playback |
| Account | local, no email; stores subscriptions and watch history on your server |
| RSS | https://yt.example.com/feed/channel/UC… |
| Phone | Clipious (Android), Yattee (iOS) → your instance |
| Browser | the LibRedirect extension sends youtube.com links to your instance |
1. The compose file
mkdir -p ~/docker/invidious && cd ~/docker/invidious
nano compose.yaml
services:
invidious:
image: quay.io/invidious/invidious:latest
container_name: invidious
ports:
- "127.0.0.1:3010:3000"
environment:
INVIDIOUS_CONFIG: |
db:
dbname: invidious
user: kemal
password: ${DB_PASSWORD}
host: invidious-db
port: 5432
check_tables: true
hmac_key: "${HMAC_KEY}"
domain: yt.example.com
https_only: true
external_port: 443
registration_enabled: true
captcha_enabled: false
default_user_preferences:
quality: dash
dark_mode: dark
region: GB
invidious_companion:
- private_url: "http://companion:8282"
invidious_companion_key: "${COMPANION_KEY}"
depends_on: [invidious-db, companion]
healthcheck:
test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/trending || exit 1
interval: 30s
timeout: 5s
retries: 2
restart: unless-stopped
companion:
image: quay.io/invidious/invidious-companion:latest
container_name: invidious-companion
environment:
SERVER_SECRET_KEY: ${COMPANION_KEY}
cap_drop: [ALL]
read_only: true
security_opt: [no-new-privileges:true]
restart: unless-stopped
invidious-db:
image: postgres:16-alpine
container_name: invidious-db
environment:
POSTGRES_DB: invidious
POSTGRES_USER: kemal
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- ./db:/var/lib/postgresql/data
restart: unless-stopped
.env: DB_PASSWORD, HMAC_KEY (openssl rand -hex 20), COMPANION_KEY (exactly 16 characters, openssl rand -hex 8). The companion handles the video-stream side that YouTube keeps changing; check Invidious's docs for the current required pieces — this is the part that moves.
docker compose up -d
docker compose logs -f invidious
2. Reverse proxy
yt.example.com → 127.0.0.1:3010 (28), HTTPS, and client_max_body_size 0 isn't needed but proxy_buffering off helps playback. Open it: a plain search box. Search, click, play — no ads.
3. An account and subscriptions
Log in → Register: a username and password, stored on your server only. Subscribe to channels; the Subscriptions page is a chronological feed, no algorithm. Import your YouTube subscriptions: Google Takeout (149) → subscriptions.csv → Invidious Preferences → Import/export. Then set registration_enabled: false once the household's accounts exist.
Every channel has an RSS feed (/feed/channel/UC…), and your subscriptions page has a private one — FreshRSS (89) turns YouTube into a newspaper.
4. Phones and redirects
- Android: Clipious → your instance URL. iOS: Yattee → add instance.
- Browser: the LibRedirect extension rewrites youtube.com links to yt.example.com automatically — share links from anywhere, they open on your server. It does the same for Reddit (Redlib), Twitter (Nitter) and others you might host.
- SearXNG (180) !yt can point at your instance.
5. The catch, honestly
YouTube rate-limits and blocks IPs it identifies as unofficial clients. Symptoms: "Sign in to confirm you're not a bot", videos that won't play, empty search. Mitigations, in order: keep the image current (Watchtower, 50 — this is one to leave unpinned); run the companion/PO-token helper the docs currently recommend; route the stack through a VPN container (136) and change server when blocked; keep the instance private (unlisted, LAN/VPN, no public registration) so it looks like one household, not a proxy. Most private instances work most of the time; some weeks need a pull.
6. Downloads for offline
Invidious can download (Preferences → Download), and for archiving channels you care about, yt-dlp on the server (pipx install yt-dlp, a cron with --download-archive) into a folder Jellyfin (42) indexes — your own content and creators who permit it.
Troubleshooting
Videos won't play but pages load. The companion isn't running or its key doesn't match; docker compose logs companion. Or YouTube is blocking — see step 5.
"Database error" on first start. check_tables: true creates the schema; the db wasn't ready — restart invidious once.
Thumbnails missing. Outbound access from the container; or image_proxy-style settings in preferences.
Registration page gone. registration_enabled: false — intended, once accounts exist.
Wrapping up
Three containers behind the proxy, a local account, subscriptions imported from Takeout, LibRedirect in the browser, Clipious or Yattee on the phone. YouTube without the machine around it — and a maintenance note: this is the one app in the series that fights back, so keep it updated and private.
