yt-dlp-web-ui: a Go RPC server and queue in front of yt-dlp
A terrible web ui and RPC server for yt-dlp. Designed to be self-hosted.
At a glance
- What is it?
- The project wraps yt-dlp in a self-hosted Go server with a web front end, a download queue and JWT auth. It is a thin control plane over an external binary, and that shape decides both its fit and its limits.
- Who is it for?
- Adopt it if you already run yt-dlp on a NAS or Raspberry Pi and want a browser front end, a queue limit and optional JWT auth without writing your own wrapper; the Docker image and the standalone binary both start with a single flag set. Do not adopt it if you need a supported download engine of its own, a stable API contract across major versions, or a front end whose direction is settled.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 26 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap between a yt-dlp command and a download server
yt-dlp is a command line program. Running one download is easy; running several, from a machine you are not sitting at, with a queue and a way to see what finished, is not. The author describes the project as created for one purpose: fetching videos from a server or NAS and monitoring upcoming livestreams. That is a narrower brief than a general download manager. The README calls it a not so terrible web ui for yt-dlp, and the repository description is blunter still, calling it a terrible web ui and RPC server. The self-deprecation is a signal about scope: this is a personal tool that grew a UI, not a product with a roadmap.
It is written in Go and ships as a single binary or a container, which matters on the hardware named in the repository topics. A Raspberry Pi or a low-power NAS is the target environment, and the README claims low impact on resources. The audience is therefore people who already have yt-dlp working and want a control surface, not people looking for a download engine. If you have never used yt-dlp, this project does not replace the need to learn it; it sits on top.
Architecture: a Go server that shells out to an external binary
The server does not embed a downloader. The -driver flag sets the path to the yt-dlp executable, defaulting to yt-dlp on PATH, and the config key downloaderPath does the same from a file. Everything the UI does ultimately becomes an invocation of that binary. This is the central design decision and it explains most of the rest.
Because the downloader is external, upgrading the extraction logic is a matter of updating yt-dlp, not the server. It also means the server inherits yt-dlp's failure modes without being able to do much about them. When a site changes its markup and extraction breaks, the web UI will report a failed job; the fix lives in a different repository.
State is local. The -db flag points at a local database, defaulting to local.db, and the config exposes session_file_path for the session database directory. There is a -session flag for a session file path as well. The material does not describe the schema or what is stored where, so treat the persistence layer as opaque until you inspect it yourself. The front end is served from resources that can be overridden with the -web flag, which suggests the UI is embedded in the binary by default and can be replaced.
The RPC layer is what the name promises. Authentication is optional and, when enabled, uses a JWT secret supplied through the JWT_SECRET environment variable in the Docker examples, with --auth, --user and --pass on the command line or require_auth, username and password in the config file.
Getting it running: Docker, Compose, and the standalone binary
The README gives three deployment paths. The fastest is the published image:
docker pull marcobaobao/yt-dlp-webui:v4
docker run -d -p 3033:3033 -v <your dir>:/downloads marcobaobao/yt-dlp-webui
The image is also published at ghcr.io/marcopiovanello/yt-dlp-web-ui. Port 3033 is the default listener, matching the -port flag and the port config key. The Compose example adds a healthcheck that curls http://localhost:3033 and a restart policy of unless-stopped, and mounts the host directory at /downloads.
Authentication is opt in. The README shows it with an environment variable and flags together:
docker run -d -p 3033:3033 -e JWT_SECRET randomsecret -v /path/to/downloads:/downloads marcobaobao/yt-dlp-webui --auth --user your_username --pass your_pass
Queue limiting is a single flag. The example sets --qs 2 for a maximum of two concurrent downloads; the config file equivalent is queue_size, and the sample comments note a minimum of 2 and a default of the logical CPU core count. That default is worth noticing. On a machine with many cores, the queue will allow more parallel downloads than a small NAS can comfortably sustain, so setting queue_size explicitly is the safer choice.
Without Docker, the prebuilt binary is moved into place and run with an output directory:
yt-dlp-webui --out /home/user/downloads
You can point it at a specific yt-dlp build with --driver /opt/somedir/yt-dlp, or load a config file with --conf /home/user/.config/yt-dlp-webui.conf. The argument list also includes -fl to enable file based logging, -lf for the log file location defaulting to yt-dlp-webui.log, -host defaulting to 0.0.0.0, and -web for the frontend resource path. One detail in the README is easy to miss: the config file overwrites what was passed as a CLI argument. If you set port in config.yml and also pass -port, the file wins. In Docker the config is expected at /config/config.yml inside the mounted volume.
Queue, format selection and the settings that change behaviour
The settings page lists server address, theme, extract audio, language, optional format selection, output filename override, output path override, custom yt-dlp arguments, and the download queue limit. Two of these deserve attention.
Format selection is disabled by default, and the README explains why: the intent is to retrieve the best quality automatically. Enabling the Enable video/audio formats selection flag exposes the choice. If you leave it off, you get whatever yt-dlp considers best, which is usually what you want for archiving and rarely what you want when disk space is tight.
The custom arguments field is described as a way to pass yt-dlp arguments safely. The word safely is doing real work here, because arbitrary arguments to yt-dlp can include output templates, cookies, and network options. The material does not document how the arguments are parsed or escaped, and I would not assume the field is a security boundary. If the UI is reachable by anyone you do not trust, treat it as equivalent to shell access to the downloader.
The queue is the other behavioural lever. A limit of 2 concurrent downloads is the documented example, and the config comment says the default is the logical CPU core count with a minimum of 2. For a Raspberry Pi, the core count default is likely to be wrong in the other direction: too many simultaneous network-heavy jobs competing for limited memory.
Where it breaks: thin documentation, an unsettled frontend, and a major version cut
The most concrete limitation is the state of the front end. The README carries a poll to decide the future of the frontend, which tells you the current UI is not considered final by its own author. Building a workflow around a UI that may be replaced is a risk, though the RPC surface is the more stable thing to depend on.
Documentation is uneven. The README is truncated in the supplied material mid-way through the config file comments, and several flags are listed without explanation of their interaction. The session file path, the local database, and the -web resource override are all named but not described. The migration guide for v4 lives on a wiki page rather than in the repository, which means an upgrade path exists but is separated from the code you are reading.
The v4 release is a server rewrite, judging by the release title Server v4 and the migration note placed before the update instructions. Major version jumps in a self-hosted tool usually mean config or storage changes, and the README's caution note supports that reading. If you are running v3.2.6 or v3.2.5, the migration page is not optional reading.
Licensing is GPL-3.0. For self-hosted personal use this is unremarkable. If you intend to modify the server and distribute it, or to offer it as a hosted service, the copyleft terms apply and you should read them rather than take my summary. The README also states that unsupervised AI code is prohibited in the repository, which affects contribution, not use.
Finally, the project is a wrapper. When yt-dlp itself breaks against a site, this project cannot fix it, and no amount of UI polish changes that. The -driver and downloaderPath options exist precisely so you can pin a working yt-dlp build independently.
Alternatives: MeTube and the DIY wrapper
The closest widely used alternative in this space is MeTube, which also provides a self-hosted web interface around yt-dlp, typically distributed as a container with a Python backend. The practical difference is the implementation language and what that implies for deployment: this project is a Go binary with no runtime dependency beyond yt-dlp itself, which is friendlier to a Raspberry Pi or a minimal NAS image. MeTube's Python stack brings its own dependency surface. The two overlap heavily in features, and the choice usually comes down to which container you can maintain on your hardware, not to a capability gap.
The other alternative is writing the wrapper yourself, and for a narrow case it is a real option. If all you need is a scheduled download and a file on disk, a cron entry calling yt-dlp directly has no queue to configure, no JWT secret to manage, and no major version migrations. The reason to take on this project instead is the monitoring use case the author names: seeing upcoming livestreams and tracking a queue in a browser is genuinely more work to build than to deploy. If that visibility is not something you need, the wrapper is overhead.
Maintenance cost and what to check before you commit
The upgrade story has two moving parts. The first is yt-dlp, which changes frequently to keep up with sites; pinning it via downloaderPath or -driver gives you control, but you still have to update it. The second is this server, where v4 arrived roughly fifteen months after v3.2.6 and came with a migration guide. Neither cadence is alarming, but neither is maintenance-free, and the config-file-overrides-CLI-argument rule means a stale config.yml can silently undo a flag you just changed.
Before deploying, verify three things in your own environment. Confirm the queue behaviour by setting -qs or queue_size explicitly rather than relying on the CPU core default. Confirm where the local database and session files land, since the defaults are the working directory and that may not be a persistent volume in your container setup. And read the migrate-to-v4 wiki page even on a fresh install, because it is the only documentation of what changed in the server and it tells you which settings survived. If you mount /config, remember the file must be named config.yml. If you enable auth, set JWT_SECRET; the README shows it alongside the flags, and a missing secret is the kind of thing you discover at the wrong moment. The licence is GPL-3.0, so decide early whether your use is personal or distribution, because that decision is cheaper to make before you fork than after.
Editorial conclusion
Adopt it if you already run yt-dlp on a NAS or Raspberry Pi and want a browser front end, a queue limit and optional JWT auth without writing your own wrapper; the Docker image and the standalone binary both start with a single flag set. Do not adopt it if you need a supported download engine of its own, a stable API contract across major versions, or a front end whose direction is settled. Before deploying, read the migrate-to-v4 wiki page, confirm the GPL-3.0 obligations you are taking on, and verify that -qs and queue_size behave as you expect under concurrent load.
Community notes