MeTube: a self-hosted web UI for yt-dlp, run from a single Docker image
Self-hosted video downloader for YouTube and other sites (web UI for yt-dlp).
At a glance
- What is it?
- MeTube wraps yt-dlp in a browser interface with a persistent queue, playlists, channels and subscriptions. It is a thin layer over the downloader, and that thinness is both the point and the limit.
- Who is it for?
- Adopt MeTube if you already trust yt-dlp and want a queue, a folder picker and subscription polling without writing your own wrapper. Do not adopt it if you need a stable HTTP API for other services to call, or if you cannot accept that extraction breakage is fixed upstream in yt-dlp, not here.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 26, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap MeTube fills between yt-dlp and a browser
yt-dlp is a command line program. That is fine when you are downloading one video and awkward when you are pasting links from three devices, or when a download takes twenty minutes and you want to close the terminal. MeTube puts a web interface in front of it: you paste a URL, pick options, and the server runs yt-dlp on your behalf, writing the result into a directory you mounted.
The README describes it as a "self-hosted web UI for yt-dlp" that works with YouTube and dozens of other sites, and the supported-site list is delegated to yt-dlp's own documentation. So the audience is narrower than "anyone who downloads video". It is for people who already run something on a home server or NAS, are comfortable with Docker volumes and environment variables, and want the downloader to keep running when their laptop is closed. The subscription feature, which periodically checks channels and playlists and queues new uploads, is the clearest signal of that intent: it only makes sense on a machine that stays on.
How the queue, state files and subscription polling actually work
The architecture visible in the repository is a Python application (pyproject.toml requires Python 3.13) built on aiohttp and python-socketio, with a separate Angular UI under ui/ that is compiled in a Node build stage and served by the Python process. The socket layer matters: the UI is not polling a REST endpoint for progress, it is receiving events.
Persistence is deliberately plain. STATE_DIR holds queue.json, pending.json, completed.json and subscriptions.json. There is no database. That makes backup and inspection trivial, and it also means the state files are the system of record. SUBSCRIPTION_MAX_SEEN_IDS caps stored video IDs per subscription at 50000 by default, which is an explicit acknowledgement that these files grow. SUBSCRIPTION_SCAN_PLAYLIST_END limits each check to the newest 50 entries, so a channel with a long back catalogue is scanned from the top rather than in full.
Concurrency is bounded by MAX_CONCURRENT_DOWNLOADS, default 3. Anything beyond that waits. This is a queue, not a scheduler: there is no priority, no time window, no per-subscription concurrency. If you subscribe to twenty channels with a one hour check interval, you are relying on that single limit to keep the machine from being saturated.
Installing MeTube with Docker and running a first download
The README's quickest path is a single docker run. The image is published as ghcr.io/alexta69/metube and the container listens on port 8081, so the host port and the container port are the same number in the documented example. The only volume that is strictly required is the downloads directory, mounted at /downloads.
docker run -d -p 8081:8081 -v /path/to/downloads:/downloads ghcr.io/alexta69/metubeAfter that, the UI is reachable at port 8081 on the host. Note the defaults that come with this minimal invocation: MeTube runs as PUID 1000 and PGID 1000, DOWNLOAD_DIR and TEMP_DIR are both /downloads, and STATE_DIR is /downloads/.metube. If your host user is not 1000, the container will try to chown the mounted directories on start, because CHOWN_DIRS defaults to true.
The Compose equivalent in the README keeps the same port and volume and adds a restart policy:
services:
metube:
image: ghcr.io/alexta69/metube
container_name: metube
restart: unless-stopped
ports:
- "8081:8081"
volumes:
- /path/to/downloads:/downloadsFor a first real use, paste a video URL into the UI and start the download. If you want the files somewhere other than the root of the mount, CUSTOM_DIRS defaults to true, which exposes a Download Folder field under Advanced Options. With CREATE_CUSTOM_DIRS also true by default, that field accepts free text and the directory is created recursively. The suggestion list hides entries starting with a dot or an @ sign, controlled by CUSTOM_DIRS_EXCLUDE_REGEX. If most of your downloads go to one subfolder, DEFAULT_FOLDER pre-selects it; the README notes it requires CUSTOM_DIRS and is ignored with a warning otherwise.
Where MeTube breaks, and when it is the wrong tool
The most important limitation is not in MeTube at all. Extraction from YouTube and other sites changes constantly, and MeTube does not implement extraction. It depends on yt-dlp, declared in pyproject.toml as yt-dlp[default,curl-cffi,deno]. When a site changes and downloads start failing, the fix arrives as a yt-dlp release, and your remedy is to pull a newer MeTube image or otherwise update that dependency. MeTube's own release cadence, with releases on 2026-08-20, 2026-08-21 and 2026-08-28, suggests the project tracks this closely, but the failure mode is real: a working instance can stop working without anything changing on your side.
The second limitation is that this is a UI, not an API. The README does not document an HTTP API for programmatic submission. If you want another service to hand a URL to MeTube and get a callback when the file is ready, you are working against the grain; the socket events are for the bundled UI. A script that shells out to yt-dlp directly is a better fit for that job.
Third, TEMP_DIR defaults to /downloads, the same filesystem as the final output. The README suggests pointing it at an SSD or a tmpfs for better performance, and then warns that a RAM filesystem may prevent downloads from being resumed. That is a genuine trade-off with no free option: faster intermediate writes, or resumable partial downloads.
Finally, the state files are JSON and they are the only record. There is no documented rollback, no migration tooling, and no documented recovery procedure for a corrupted queue.json. Backing up STATE_DIR is on you.
MeTube compared with Pinchflat and TubeSync
The comparison people search for is MeTube versus Pinchflat or TubeSync, and the difference is scope rather than quality. MeTube is a general downloader with a queue and an optional subscription feature bolted on. Pinchflat and TubeSync are built around the subscription model first: you define sources, and the tool's job is to keep an archive in sync with them. If your actual goal is "keep a local mirror of these channels", the archive-first tools match that mental model more directly than a paste-a-URL interface with polling attached.
The second axis is yt-dlp exposure. MeTube passes options through with YTDL_OPTIONS as a JSON object, YTDL_OPTIONS_FILE for a file that is monitored and reloaded on changes, and YTDL_OPTIONS_PRESETS for named bundles selectable per download in the UI. That is a lot of the underlying tool's surface area made reachable without editing code, which is the main reason to pick MeTube over writing a small wrapper yourself. The cost is that you are configuring yt-dlp through a JSON string in an environment variable, and mistakes there surface as failed downloads rather than as configuration errors at startup.
Maintenance cost, licensing and what AGPL-3.0 means here
The repository is not archived, and the last push was on 2026-08-28, which is recent enough that calling it actively maintained is defensible on the facts. Practically, your upgrade path is the image tag. The Dockerfile builds the UI with pnpm in a node:22-alpine stage and the runtime on python:3.13-slim, installing ffmpeg, aria2, unzip and deno, then syncing dependencies with uv from a lockfile. A comment in the Dockerfile explains that node:22-alpine is pinned to a major version rather than the lts-alpine floating tag because that tag once resolved to a Node patch older than the Angular CLI's minimum supported version and broke the build. That is a useful signal about what upgrading can involve.
The licence is AGPL-3.0. The consequence worth understanding before you build on it: if you modify MeTube and let other people interact with it over a network, the AGPL's network clause is the part that differs from GPL, and it is the reason some companies restrict AGPL software to internal use. Running an unmodified image for yourself or your household is not the case that clause targets. If you plan to fork it into a product, read the licence text rather than a summary.
Editorial conclusion
Adopt MeTube if you already trust yt-dlp and want a queue, a folder picker and subscription polling without writing your own wrapper. Do not adopt it if you need a stable HTTP API for other services to call, or if you cannot accept that extraction breakage is fixed upstream in yt-dlp, not here. Before committing, verify that your download and state directories are writable by the PUID and PGID you configure, and decide where STATE_DIR lives, because queue.json, pending.json, completed.json and subscriptions.json are the only record of what has been queued and downloaded.
Frequently asked questions
What is MeTube?
It is a self-hosted web UI for yt-dlp, described in the README as a way to download media from YouTube and dozens of other sites. It adds a browser interface, a queue and optional channel and playlist subscriptions on top of the command line downloader.
How to run MeTube?
The README gives a single docker run command that publishes port 8081 and mounts a downloads directory at /downloads, or the equivalent Docker Compose service definition. The image is ghcr.io/alexta69/metube.
What are the drawbacks of using MeTube?
It depends on yt-dlp for extraction, so a site change is fixed upstream rather than in MeTube. It also exposes no documented HTTP API for programmatic submission, and its queue and history live only in JSON state files under STATE_DIR.
How to install MeTube?
Installation is by container image, not by a package manager. The README documents docker run and Docker Compose with ghcr.io/alexta69/metube, port 8081, and a volume for the downloads directory.
How to set up MeTube?
Setup beyond the container is done through environment variables. PUID and PGID control the user it runs as, DOWNLOAD_DIR and STATE_DIR control where files and state go, and YTDL_OPTIONS or YTDL_OPTIONS_FILE pass extra options through to yt-dlp.
How does MeTube compare to TubeSync?
The README does not compare them. What the repository shows is that MeTube is a general downloader with an optional subscription feature, while archive-first tools are organised around keeping sources in sync.
Official sources
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.
[](https://hysenlabs.com/projects/alexta69-metube)