Self-hosted service
manbearwiz/youtube-dl-server avatar
manbearwiz/youtube-dl-server

youtube-dl-server: putting an HTTP endpoint in front of yt-dlp

Web / REST interface for downloading youtube videos onto a server.

933 stars293 forksPythonMIT

At a glance

What is it?
youtube-dl-server is a deliberately small Starlette service that accepts a video URL and hands it to the yt-dlp Python API, saving the result into a mounted directory. It is aimed at people who want downloads triggered from a script, a browser bookmarklet or another service rather than from a shell.
Who is it for?
Use youtube-dl-server when a remote caller, a bookmarklet or a scheduled job needs to start a download on a machine that already has the server running, and you are content with a spartan interface and no documented authentication. Skip it if you need a job queue, per user accounts, a file listing API or a supported interface beyond the single download endpoint.
Can I use it commercially?
Yes. MIT 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 60 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 October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A web wrapper with a deliberately narrow surface

The project describes itself as a very spartan web and REST interface for downloading YouTube videos onto a server. Two libraries do the work: Starlette for the web framework and yt-dlp for the downloading. The README states that the integration with yt-dlp makes use of their Python API, which is the embedded interface rather than a subprocess call to the yt-dlp command.

Spartan is an accurate word and it cuts both ways. There is a download form at `/youtube-dl`, one REST path, a template directory and a single Python module, `youtube-dl-server.py`. There is no user model, no job identifier returned to the caller and no queue. The service starts a download when asked and the file appears in the output directory.

That design is coherent for a specific use: a home server, a media box, a NAS, or a small machine you control where you want to trigger a download from your phone or from another script. It is not a pipeline service, and nothing in the repository layout suggests it is trying to become one.

One POST endpoint starts the download

The request interface is a single form encoded POST to `/youtube-dl/q`. The README gives the curl form, where the video URL is supplied as a `url` field:

shell
curl -X POST --data-urlencode "url={{url}}" http://{{host}}:8080/youtube-dl/q

A browser fetch call shows the same contract, and adds a `format` field:

javascript
fetch(`http://${host}:8080/youtube-dl/q`, {
  method: "POST",
  body: new URLSearchParams({
    url: url,
    format: "bestvideo"
  }),
});

The `format` field is the hook for yt-dlp's own format selection, since the request body is passed through to the downloader rather than being interpreted by a bespoke layer.

The third documented client is a bookmarklet, which sends the current page URL with the same `bestvideo` format so you can trigger a download from the browser you are already in:

javascript
javascript:!function(){fetch("http://${host}:8080/youtube-dl/q",{body:new URLSearchParams({url:window.location.href,format:"bestvideo"}),method:"POST"})}();

For the interactive case the README points you at `http://{{host}}:8080/youtube-dl` and describes entering the requested URL in a form on the page. The template directory in the repository holds that HTML.

The container mounts its output directory

The Docker path is the one the README leads with, and the shape of the command tells you how the service is meant to be deployed:

shell
docker run -d --net="host" --name youtube-dl -v /home/core/youtube-dl:/youtube-dl kmb32123/youtube-dl-server

Host networking is used for simplicity, and the `-v` argument maps a host directory to `/youtube-dl` inside the container, which is where the finished videos are written. The Dockerfile confirms this by declaring `VOLUME ["/youtube-dl", "/root/.config/yt-dlp"]` and exposing port 8080, with the command line starting uvicorn on `0.0.0.0` and port `8080`.

The compose example adds a networking twist worth reading, because it is the more realistic arrangement:

yml
  youtube-dl:
    image: "kmb32123/youtube-dl-server"
    network_mode: "service:vpn"
    volumes:
      - /home/core/youtube-dl:/youtube-dl
      - /home/core/yt-dlp-config:/root/.config/yt-dlp  # optional: custom yt-dlp config
    restart: always

The service joins the network namespace of a VPN client container through `network_mode: "service:vpn"`, which means the download traffic leaves through the tunnel rather than the host's normal route. For a tool that fetches from video hosts that geo-block, that is the difference between a working service and one that returns nothing.

Custom yt-dlp options arrive through a mounted config file

Authentication and other yt-dlp settings are not configured through the REST API. They come from yt-dlp's own configuration mechanism, mounted into the container at `/root/.config/yt-dlp`:

shell
docker run -d --net="host" --name youtube-dl \
  -v /home/core/youtube-dl:/youtube-dl \
  -v /home/core/yt-dlp-config:/root/.config/yt-dlp \
  kmb32123/youtube-dl-server

The README instructs you to place a `config` file in the mounted directory with yt-dlp options, one per line, and points at the yt-dlp configuration documentation for details. This is the standard yt-dlp config location, so anything valid there works here.

That arrangement has a practical consequence. Cookies for age restricted or member only videos go in that same file, which means the container holds credentials for a media account, and any caller of the endpoint inherits them. There is no per request credential path in the documented interface, so you cannot give one caller your cookies and another caller nothing.

The Dockerfile also shows the runtime preparation, which is more involved than a plain Python image. The base is `python:alpine`, and the build installs `ffmpeg` and `tzdata`, then adds a JavaScript runtime, choosing deno on x86_64 and aarch64 and falling back to nodejs on other architectures. On the fallback path it writes `--js-runtimes node` into `/etc/yt-dlp/config`. yt-dlp needs a JS runtime for some sites, and this image ships one per architecture.

Running from source with uvicorn and one environment override

If you would rather not use Docker, the README gives a direct route, with the caveat that Python 3.6 or newer must be in your PATH:

shell
YDL_UPDATE_TIME=False python3 -m uvicorn youtube-dl-server:app --port 8123

The environment variable is the interesting part. The README states that `YDL_UPDATE_TIME=False` is the same as the command line option `--no-mtime`, which controls whether the file modification time is set from the source rather than from the download time. For a media library, keeping the original timestamp is usually what you want, since it lets sorting and renaming tools see the upload date.

The pinned dependency set in `requirements.txt` shows the whole runtime surface: aiofiles, Jinja2, python-multipart for form parsing, Starlette, uvicorn, and `yt-dlp[default]`. The uvicorn pin is split by architecture, with the `standard` extra on x86_64 and the plain package elsewhere.

text
aiofiles==25.1.0
Jinja2==3.1.6
python-multipart==0.0.20
starlette==1.3.1
uvicorn[standard]==0.34.2; platform_machine == 'x86_64'
uvicorn==0.34.3; platform_machine != 'x86_64'
yt-dlp[default]==2026.6.9

Note that the pinned yt-dlp is dated 2026.6.9 while the README still calls the project youtube-dl-server. The name is historical; the downloader underneath is yt-dlp.

The gaps that make this the wrong tool for some jobs

The README does not document any authentication, and there is no mention of users, tokens or API keys in the documented interface. Every request to `/youtube-dl/q` is accepted. That is fine on a machine reachable only from your own network, and it is a problem the moment you bind the service to a public address. The `0.0.0.0` bind in the container command line is the default, so exposing it means putting your own proxy, firewall or VPN in front of it.

There is also no status reporting. The caller posts a URL and gets no job identifier, no progress, and no way to ask whether the file is finished. The progress bar you see is inside the container logs. For a service with one user clicking a bookmarklet, that is tolerable. For anything queuing many downloads, there is no mechanism here to coordinate them.

The output side is equally thin. The finished file lands in the mounted directory under whatever name yt-dlp chooses, and the service exposes no listing, no delete and no download endpoint. You are expected to reach the directory over your own file sharing. If you wanted an HTTP service that also serves the files back, this is not it.

Finally, the whole thing depends on yt-dlp keeping pace with YouTube. The project updates its pinned yt-dlp version in `requirements.txt`, but the README documents no update mechanism of its own, so an image that has been running for months can be running an extractor that no longer works.

MIT terms, a 2022 tag and a 2026 commit

The licence field on the repository says MIT, and the repository carries a LICENSE file and a SECURITY.md alongside a pre-commit configuration and a GitHub workflow for building the Docker image. MIT is permissive and imposes no obligation on a private or commercial deployment of the service.

The release history is where this project looks unusual, so read it carefully. The tagged releases are v1.0.0 from 2015-10-14 and v2.0.0 from 2022-07-30. There has been no tag since 2022. The last push was on 2026-08-24, and the pinned yt-dlp version in `requirements.txt` is 2026.6.9, which tells you the maintenance that is happening is dependency upkeep rather than feature work.

So the repository is not abandoned and it is not busy either. It is a small service that gets its dependency bumped so that downloads keep working. That is exactly what you want from this kind of wrapper, and exactly what you should not expect a feature roadmap from. If you need the Docker image rebuilt with a specific base image, or the interface extended, plan on doing that work yourself in a fork.

Editorial conclusion

Use youtube-dl-server when a remote caller, a bookmarklet or a scheduled job needs to start a download on a machine that already has the server running, and you are content with a spartan interface and no documented authentication. Skip it if you need a job queue, per user accounts, a file listing API or a supported interface beyond the single download endpoint. Verify first that the container can reach the video host, that `/youtube-dl` is a volume you control, and that whatever protection you put in front of the endpoint is yours to maintain, since the README documents none. The licence field on the repository says MIT, the tagged releases stop at v2.0.0 from 2022-07-30, and the last push was on 2026-08-24.

Frequently asked questions

How do I run youtube-dl-server with Docker?

The README gives `docker run -d --net="host" --name youtube-dl -v /home/core/youtube-dl:/youtube-dl kmb32123/youtube-dl-server`, using host networking for simplicity and a volume that becomes the output directory for finished videos. The container exposes port 8080.

How do I trigger a download through the REST API?

Send a form encoded POST to `/youtube-dl/q` with a `url` field, as in `curl -X POST --data-urlencode "url={{url}}" http://{{host}}:8080/youtube-dl/q`. The request body can also carry a `format` field, for example `bestvideo`, which is passed to yt-dlp.

How do I pass yt-dlp options such as authentication to youtube-dl-server?

Mount a directory at `/root/.config/yt-dlp` and place a `config` file in it with yt-dlp options, one per line. The Docker image declares `/root/.config/yt-dlp` as a volume for exactly this purpose.

Does youtube-dl-server need a JavaScript runtime?

The Dockerfile installs one. It adds deno on x86_64 and aarch64 and falls back to nodejs on other architectures, writing `--js-runtimes node` into `/etc/yt-dlp/config` on the fallback path. It also installs ffmpeg and tzdata.

What is the YDL_UPDATE_TIME environment variable for?

The README states that `YDL_UPDATE_TIME=False` is the same as the yt-dlp command line option `--no-mtime`. It appears in the documented uvicorn run command as an inline override.

What licence is youtube-dl-server released under?

The licence field on the repository says MIT. The tagged releases are v1.0.0 from 2015-10-14 and v2.0.0 from 2022-07-30, with the last push on 2026-08-24.

Official sources

  1. Issues
  2. License: MIT
  3. manbearwiz/youtube-dl-server on GitHub
  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/manbearwiz-youtube-dl-server.svg)](https://hysenlabs.com/projects/manbearwiz-youtube-dl-server)

Was this useful?