Self-hosted service
Pelski/ytzero avatar
Pelski/ytzero

ytzero: a self-hosted YouTube inbox whose author has set an end date

YT Zero - own rules, own algorithm, no login required. Every video from every channel you follow, and nothing else

642 stars27 forksTypeScriptUnlicense

At a glance

What is it?
Pelski's ytzero reads public YouTube RSS feeds into your own SQLite or PostgreSQL database and serves a subscription inbox with no Google account and no API key. The code is TypeScript, the deployment is one container, and the licence is recorded two different ways. The author has announced that 2026.09.10 will be the last release he ships, over legal uncertainty in the integrations underneath.
Who is it for?
Use this if you want a subscription feed you control, you already run containers, and you are willing to host the database yourself. Skip it if you need a project with a funded roadmap behind it, because the author has said he is stepping away and has invited a new maintainer instead.
Can I use it commercially?
Yes. Unlicense is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The author has named 2026.09.10 as the last release he plans to ship

The first thing in this README is a notice, not a feature list. The maintainer says he has decided to step away from maintaining YT Zero, and gives his reason as uncertainty around the legal side of how some of the underlying software and integrations are being used. He says he does not want to keep developing and publicly distributing the project while that uncertainty is unresolved.

The consequence is dated. The current release becomes the second to last public one he maintains, and he names 2026.09.10 as the final one. He plans to address as many open issues and fixes as he reasonably can in that release, and to try to bring the tvOS app to a usable state so a successor inherits something that runs rather than something to start from scratch.

He is looking for a successor and says anyone from the community who wants to take responsibility has his full blessing. He also leaves a door open: there is a chance he revisits the idea later using only clearly supported and official paths such as RSS and the official YouTube API, but says he has no plans to, and that doing so would change the original assumptions so it would no longer be the project he set out to build.

The recorded state matches the timing rather than undercutting it. The repository is not archived, the default branch is main, the last recorded push is 2026-09-28, and release 2026.09.9 went out the same day with the name From Couch to Channel. So the code is live and the roadmap is closed.

One container on port 3001 with four interval and retention knobs

The compose file that ships with the repository is short enough to read in full, and it is where most of the tunable behaviour lives.

yaml
services:
  ytzero:
    image: ghcr.io/pelski/ytzero:latest
    container_name: ytzero
    ports:
      - "3001:3001"
    volumes:
      - ./data:/data
    environment:
      - IDLE_TIMEOUT_SECONDS=120
      - REFRESH_INTERVAL_MINUTES=5
      - FULL_SYNC_INTERVAL_MINUTES=15
      - VIDEO_MAINTENANCE_MAX_AGE_DAYS=90
      - DB_PATH=/data/db/ytzero.db
      - IMG_CACHE_DIR=/data/imgcache
    restart: unless-stopped

Four of those environment values are the ones you would actually change. REFRESH_INTERVAL_MINUTES=5 sets how often the feed is polled, and FULL_SYNC_INTERVAL_MINUTES=15 sets the slower reconciliation pass, so the cheap incremental check runs three times as often as the thorough one. VIDEO_MAINTENANCE_MAX_AGE_DAYS=90 is a retention ceiling, which is the setting that decides how much history survives in the database. IDLE_TIMEOUT_SECONDS=120 governs how long the process waits before shutting down unused work.

Everything durable is pinned to one bind mount. DB_PATH points at /data/db/ytzero.db inside the container and IMG_CACHE_DIR at /data/imgcache, and the volume maps ./data on the host to /data, so the database file and the thumbnail cache both land under a single host directory that you can back up on its own.

SQLite stays single instance, and PostgreSQL is what buys you clustering

The data layer is a deliberate fork. Subscriptions, watch progress, history, playlists, tags and rules all live in your own database, either SQLite or PostgreSQL, and nothing is stored on a service you do not run.

That fork has a consequence that is stated plainly. PostgreSQL deployments can run multiple HTTP replicas with one nominated background worker. SQLite deployments remain single instance. So the horizontal scaling story is only available on one of the two backends, and the worker has to be nominated rather than elected, which means a replica list where exactly one instance runs the scheduled sync and the rest only serve requests.

The requirements for that topology, covering the worker, shared storage and a load balancer, are not in the README. They are pointed at a clustered deployment configuration page in the project wiki, which is the first thing to read if you are planning more than one replica.

The other half of the design is what it refuses to need. YT Zero reads public YouTube RSS feeds and the feature list states that it works without a Google account and without a YouTube Data API key, which is the source of the no setup promise in the project's own description.

The image pins Deno and BGUtil but pulls yt-dlp from latest

The runtime image is assembled from oven/bun:1.3-slim, with a separate oven/bun:1.3 stage above it used to build the interface. The UI build installs from ui/package.json and ui/bun.lock first, then copies the ui and shared directories, and a YTZERO_CHANGELOG_PREGENERATED build argument decides between a prepared changelog build and the ordinary one.

The runtime layer installs python3, ffmpeg, curl, ca-certificates and unzip with apt, then fetches yt-dlp straight from the GitHub releases latest download path into /usr/local/bin. It installs Deno from the deno.land install script, and it downloads BGUtil twice: once as a plugin zip into /opt/ytzero/yt-dlp-plugins, and once as a source tarball that is extracted to /opt/ytzero/bgutil, where deno install runs in the server directory with --frozen. curl and unzip are purged at the end of the same layer.

The version handling is uneven and worth knowing. Deno is pinned to ARG DENO_VERSION=2.8.1 and BGUtil to ARG BGUTIL_VERSION=1.3.1, both overridable at build time. yt-dlp has no version argument at all, so its version is whatever was current when the image was built. If you build locally and run the published image weeks later, those are two different binaries underneath you.

Six deploy targets share one repository root

A single docker run is the advertised path, but the root of the repository lists considerably more ways out. There is Dockerfile.railway alongside railway.json, heroku.yml alongside app.json, render.yaml, a .do/ directory for DigitalOcean, a .gitea/ directory, and a generic deploy/ directory. Two extra compose files sit next to the one above: docker-compose.dev.yml and docker-compose.postgres.yml.

The package manifest is the thread through them. It declares the package private, which suits a monorepo root rather than something published to a registry, and every entry point is a shell script under scripts/: setup, dev, build and start. The app and the interface are separate bun workspaces with their own dev, test and typecheck scripts, so dev:app and dev:ui can run independently.

There is also a hooks story. hooks:install sets core.hooksPath to .githooks, and the check scripts cover large files, database migrations and a precommit pass that also runs as check:validate. That matters for anyone picking the project up, because the migration check is the sort of gate that assumes a working local bun setup and will be the first thing to trip on a fresh machine.

package.json says AGPL-3.0-only and the repository record says Unlicense

Two licence answers are recorded and they do not match. The repository licence field for Pelski/ytzero reads Unlicense, and a LICENSE file sits at the root of the tree. The package manifest, in the same repository, declares "license": "AGPL-3.0-only".

Neither the README nor the compose files pick a winner, so the ambiguity is live rather than a display quirk in one place. The Unlicense is the public domain dedication, and AGPL-3.0-only is a strong copyleft licence with network use conditions. If you run an unmodified instance for yourself the two may not matter to you at all, but if you modify the code, redistribute a container, or offer it to other people over a network, the difference decides what you owe and to whom.

That gap sits next to the maintainer's stated reason for stepping away, which was legal uncertainty. It is worth resolving before you build anything on top of this, and the cheapest way is to ask on the issue tracker or read both files in full rather than trusting either field.

Two ways to watch, and the direct one honours your quality ceiling

Playback is the part with the most moving pieces, and the project offers two routes that are easy to confuse. Direct video streaming plays YouTube video and audio on demand inside the built-in player, with seeking and no offline file and no background download. It supports the available H.264 and AAC formats within the quality limit you selected, which means the setting is a ceiling rather than a target. The research behind that mode is kept in docs/direct-streaming-research.md.

Downloads and local playback are the other route. The optional yt-dlp plugin fetches videos to disk and plays them in the project's own player, and the stated benefits are instant seeking, no embeds, no buffering, and working offline. That is the option that needs ffmpeg and yt-dlp in the image, and the option that puts video files on your storage.

Around those sit the smaller behaviours. A compact audio player can take a video or an active livestream and keep playing from the lock screen on supported mobile browsers. An existing TubeArchivist archive can be connected as a source, with its videos appearing in the normal feed, protected local playback, archived comments and subtitles, and watched status synchronised. Pulse breaks down actual viewing time by profile, channel, tag, hour, weekday and content type, and is described as staying on your server.

The embedded player needed fixing, so the fix lives in a browser extension

The one piece of the README written as a call to action rather than a feature bullet is the companion extension. It is headed Fix embedded player and get more, and it points at YT Zero Enhance, which is described as available for Firefox and Chrome and Chromium.

What the extension adds is a specific list: reliable controls, keyboard shortcuts, chapters, SponsorBlock, and picture in picture. Getting started is pointed at a browser extension guide in the project wiki rather than at documentation inside the README, so the extension is documented as a separate destination.

That detail is a fair summary of the project's shape. The server does the ingest, storage, triage and playback, and the parts that need to sit inside somebody else's browser tab are delegated to an extension with its own repository and its own guide. If you deploy the server without the extension, the feature list items about SponsorBlock, chapters and picture in picture are the ones you will not get, and the built-in player is what remains.

Editorial conclusion

Use this if you want a subscription feed you control, you already run containers, and you are willing to host the database yourself. Skip it if you need a project with a funded roadmap behind it, because the author has said he is stepping away and has invited a new maintainer instead. Before you build on it, resolve the licence conflict between the Unlicense record and the AGPL-3.0-only field in package.json, and read the clustered deployment notes in the wiki if you plan more than one replica on PostgreSQL.

Frequently asked questions

What is yt zero?

A self-hosted YouTube inbox from Pelski that reads public YouTube RSS feeds and keeps subscriptions, watch progress, playlists, tags and rules in your own SQLite or PostgreSQL database, with no Google account and no YouTube Data API key required. Its maintainer has announced that he is stepping away and plans 2026.09.10 as the final release he ships.

Can ytzero fetch new videos without a Google API key?

Yes. It reads public YouTube RSS feeds, which is why the feature list states that it works without a Google account or a YouTube Data API key. Sync cadence is set by environment variable: REFRESH_INTERVAL_MINUTES=5 for the incremental pass and FULL_SYNC_INTERVAL_MINUTES=15 for the full one, both in the shipped compose file.

Can I run more than one ytzero replica?

Only on PostgreSQL. The README states that PostgreSQL deployments can run multiple HTTP replicas with one nominated background worker, while SQLite deployments remain single instance. The worker, shared storage and load balancer requirements are pointed at the clustered deployment configuration page in the project wiki.

Is ytzero still being maintained?

Not by its original author, and the README says so in a notice at the top. He gives legal uncertainty around some of the underlying software and integrations as the reason, calls the current release the second to last he will maintain, and plans 2026.09.10 as the final one. He invites a new maintainer, and the repository itself is not archived, with the last recorded push on 2026-09-28.

What licence is ytzero released under?

Two answers are recorded and they differ. The repository licence field reads Unlicense, with a LICENSE file at the root, while package.json declares license AGPL-3.0-only. Nothing in the README settles which governs, so read both files before you redistribute a modified copy or expose a changed instance to other people over a network.

Official sources

  1. License: AGPL-3.0
  2. Pelski/ytzero on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pelski-ytzero.svg)](https://hysenlabs.com/projects/pelski-ytzero)