Web GUI for youtube-dl (using the yt-dlpβ fork) with playlist support. Allows you to download videos from YouTube and dozens of other sitesβ .

docker run -d -p 8081:8081 -v /path/to/downloads:/downloads ghcr.io/alexta69/metube
services:
metube:
image: ghcr.io/alexta69/metube
container_name: metube
restart: unless-stopped
ports:
- "8081:8081"
volumes:
- /path/to/downloads:/downloads
Certain values can be set via environment variables, using the -e parameter on the docker command line, or the environment: section in docker-compose.
5, then at most five downloads will run concurrently, and any additional downloads will wait until one of the active downloads completes. Defaults to 3.true, downloaded files are deleted on the server, when they are trashed from the "Completed" section of the UI. Defaults to false.0 (no limit)./downloads in the Docker image, and . otherwise.DOWNLOAD_DIR.true.true.(^|/)[.@].*$, which means directories starting with . or @.true, the download directories (DOWNLOAD_DIR and AUDIO_DOWNLOAD_DIR) are indexable on the web server. Defaults to false./downloads/.metube in the Docker image, and . otherwise./downloads in the Docker image, and . otherwise.
tmpfs) for better performance.false, ownership of DOWNLOAD_DIR, STATE_DIR, and TEMP_DIR (and their contents) will not be set on container start. Ensure user under which MeTube runs has necessary access to these directories already. Defaults to true.%(title)s.%(ext)s.%(title)s - %(section_number)s %(section_title)s.%(ext)s.%(playlist_title)s/%(title)s.%(ext)s. When empty, then OUTPUT_TEMPLATE is used.%(channel)s/%(title)s.%(ext)s. When empty, then OUTPUT_TEMPLATE is used.--recode-video has to be specified via postprocessors. Also note that dashes are replaced with underscores. You may find this scriptβ helpful for converting from command-line options to YTDL_OPTIONS.YTDL_OPTIONS above. Please note that if both YTDL_OPTIONS_FILE and YTDL_OPTIONS are specified, the options in YTDL_OPTIONS take precedence. The file will be monitored for changes and reloaded automatically when changes are detected.0.0.0.0 (all interfaces).8081./.https instead of http (CERTFILE and KEYFILE required). Defaults to false.robots.txt file mounted in the container.1000 (legacy UID also supported).1000 (legacy GID also supported).022.light, dark, or auto. Defaults to auto.DEBUG, INFO, WARNING, ERROR, CRITICAL, or NONE. Defaults to INFO.false.The project's Wiki contains examples of useful configurations contributed by users of MeTube:
In case you need to use your browser's cookies with MeTube, for example to download restricted or private videos:
volumes:
- /path/to/cookies:/cookies
environment:
- YTDL_OPTIONS={"cookiefile":"/cookies/cookies.txt"}
cookies.txtBrowser extensions allow right-clicking videos and sending them directly to MeTube. Please note that if you're on an HTTPS page, your MeTube instance must be behind an HTTPS reverse proxy (see below) for the extensions to work.
Chrome: contributed by Rpslβ . You can install it from Google Chrome Webstoreβ or use developer mode and install from sourcesβ .
Firefox: contributed by nanocortexβ . You can install it from Firefox Addonsβ or get sources from hereβ .
rithaskβ created an iOS shortcut to send URLs to MeTube from Safari. Enter the MeTube instance address when prompted which will be saved for later use. You can run the shortcut from Safariβs share menu. The shortcut can be downloaded from this iCloud linkβ .
iOS has strict requirements for video files, requiring h264 or h265 video codec and aac audio codec in MP4 container. This can sometimes be a lower quality than the best quality available. To accommodate iOS requirements, when downloading a MP4 format you can choose "Best (iOS)" to get the best quality formats as compatible as possible with iOS requirements.
To force all downloads to be converted to an iOS-compatible codec, insert this as an environment variable:
environment:
- 'YTDL_OPTIONS={"format": "best", "exec": "ffmpeg -i %(filepath)q -c:v libx264 -c:a aac %(filepath)q.h264.mp4"}'
kushfestβ has created a Chrome bookmarklet for sending the currently open webpage to MeTube. Please note that if you're on an HTTPS page, your MeTube instance must be configured with HTTPS as true in the environment, or be behind an HTTPS reverse proxy (see below) for the bookmarklet to work.
GitHub doesn't allow embedding JavaScript as a link, so the bookmarklet has to be created manually by copying the following code to a new bookmark you create on your bookmarks bar. Change the hostname in the URL below to point to your MeTube instance.
javascript:!function(){xhr=new XMLHttpRequest();xhr.open("POST","https://metube.domain.com/add");xhr.withCredentials=true;xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function(){if(xhr.status==200){alert("Sent to metube!")}else{alert("Send to metube failed. Check the javascript console for clues.")}}}();
shoonya75β has contributed a Firefox version:
javascript:(function(){xhr=new XMLHttpRequest();xhr.open("POST","https://metube.domain.com/add");xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function(){if(xhr.status==200){alert("Sent to metube!")}else{alert("Send to metube failed. Check the javascript console for clues.")}}})();
The above bookmarklets use alert() as a success/failure notification. The following will show a toast message instead:
Chrome:
javascript:!function(){function notify(msg) {var sc = document.scrollingElement.scrollTop; var text = document.createElement('span');text.innerHTML=msg;var ts = text.style;ts.all = 'revert';ts.color = '#000';ts.fontFamily = 'Verdana, sans-serif';ts.fontSize = '15px';ts.backgroundColor = 'white';ts.padding = '15px';ts.border = '1px solid gainsboro';ts.boxShadow = '3px 3px 10px';ts.zIndex = '100';document.body.appendChild(text);ts.position = 'absolute'; ts.top = 50 + sc + 'px'; ts.left = (window.innerWidth / 2)-(text.offsetWidth / 2) + 'px'; setTimeout(function () { text.style.visibility = "hidden"; }, 1500);}xhr=new XMLHttpRequest();xhr.open("POST","https://metube.domain.com/add");xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function() { if(xhr.status==200){notify("Sent to metube!")}else {notify("Send to metube failed. Check the javascript console for clues.")}}}();
Firefox:
javascript:(function(){function notify(msg) {var sc = document.scrollingElement.scrollTop; var text = document.createElement('span');text.innerHTML=msg;var ts = text.style;ts.all = 'revert';ts.color = '#000';ts.fontFamily = 'Verdana, sans-serif';ts.fontSize = '15px';ts.backgroundColor = 'white';ts.padding = '15px';ts.border = '1px solid gainsboro';ts.boxShadow = '3px 3px 10px';ts.zIndex = '100';document.body.appendChild(text);ts.position = 'absolute'; ts.top = 50 + sc + 'px'; ts.left = (window.innerWidth / 2)-(text.offsetWidth / 2) + 'px'; setTimeout(function () { text.style.visibility = "hidden"; }, 1500);}xhr=new XMLHttpRequest();xhr.open("POST","https://metube.domain.com/add");xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function() { if(xhr.status==200){notify("Sent to metube!")}else {notify("Send to metube failed. Check the javascript console for clues.")}}})();
dotvhsβ has created an extension for Raycastβ that allows adding videos to MeTube directly from Raycast.
It's possible to configure MeTube to listen in HTTPS mode. docker-compose example:
services:
metube:
image: ghcr.io/alexta69/metube
container_name: metube
restart: unless-stopped
ports:
- "8081:8081"
volumes:
- /path/to/downloads:/downloads
- /path/to/ssl/crt:/ssl/crt.pem
- /path/to/ssl/key:/ssl/key.pem
environment:
- HTTPS=true
- CERTFILE=/ssl/crt.pem
- KEYFILE=/ssl/key.pem
It's also possible to run MeTube behind a reverse proxy, in order to support authentication. HTTPS support can also be added in this way.
When running behind a reverse proxy which remaps the URL (i.e. serves MeTube under a subdirectory and not under root), don't forget to set the URL_PREFIX environment variable to the correct value.
If you're using the linuxserver/swagβ image for your reverse proxying needs (which I can heartily recommend), it already includes ready snippets for proxying MeTube both in subfolderβ and subdomainβ modes under the nginx/proxy-confs directory in the configuration volume. It also includes Authelia which can be used for authentication.
location /metube/ {
proxy_pass http://metube:8081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
Note: the extra proxy_set_header directives are there to make WebSocket work.
Contributed by PIE-ytβ . Source hereβ .
# For putting in your Apache sites site.conf
# Serves MeTube under a /metube/ subdir (http://yourdomain.com/metube/)
<Location /metube/>
ProxyPass http://localhost:8081/ retry=0 timeout=30
ProxyPassReverse http://localhost:8081/
</Location>
<Location /metube/socket.io>
RewriteEngine On
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /(.*) ws://localhost:8081/socket.io/$1 [P,L]
ProxyPass http://localhost:8081/socket.io retry=0 timeout=30
ProxyPassReverse http://localhost:8081/socket.io
</Location>
The following example Caddyfile gets a reverse proxy going behind caddyβ .
example.com {
route /metube/* {
uri strip_prefix metube
reverse_proxy metube:8081
}
}
The engine which powers the actual video downloads in MeTube is yt-dlpβ . Since video sites regularly change their layouts, frequent updates of yt-dlp are required to keep up.
There's an automatic nightly build of MeTube which looks for a new version of yt-dlp, and if one exists, the build pulls it and publishes an updated docker image. Therefore, in order to keep up with the changes, it's recommended that you update your MeTube container regularly with the latest image.
I recommend installing and setting up watchtowerβ for this purpose.
Before asking a question or submitting an issue for MeTube, please remember that MeTube is only a UI for yt-dlpβ . Any issues you might be experiencing with authentication to video websites, postprocessing, permissions, other YTDL_OPTIONS configurations which seem not to work, or anything else that concerns the workings of the underlying yt-dlp library, need not be opened on the MeTube project. In order to debug and troubleshoot them, it's advised to try using the yt-dlp binary directly first, bypassing the UI, and once that is working, importing the options that worked for you into YTDL_OPTIONS.
In order to test with the yt-dlp command directly, you can either download it and run it locally, or for a better simulation of its actual conditions, you can run it within the MeTube container itself. Assuming your MeTube container is called metube, run the following on your Docker host to get a shell inside the container:
docker exec -ti metube sh
cd /downloads
Once there, you can use the yt-dlp command freely.
MeTube development relies on code contributions by the community. The program as it currently stands fits my own use cases, and is therefore feature-complete as far as I'm concerned. If your use cases are different and require additional features, please feel free to submit PRs that implement those features. It's advisable to create an issue first to discuss the planned implementation, because in an effort to reduce bloat, some PRs may not be accepted. However, note that opening a feature request when you don't intend to implement the feature will rarely result in the request being fulfilled.
Make sure you have Node.js 22+ and Python 3.13 installed.
# install Angular and build the UI
cd ui
curl -fsSL https://get.pnpm.io/install.sh | sh -
pnpm install
pnpm run build
# install python dependencies
cd ..
curl -LsSf https://astral.sh/uv/install.sh | sh
uv sync
# run
uv run python3 app/main.py
A Docker image can be built locally (it will build the UI too):
docker build -t metube .
Note that if you're running the server in VSCode, your downloads will go to your user's Downloads folder (this is configured via the environment in .vscode/launch.json).
Content type
Image
Digest
sha256:c03732481β¦
Size
304.3 MB
Last updated
7 months ago
docker pull hinmanjp/metube