youtubedl-material: three Node versions, two port numbers, and a compose file that does what the readme calls an optional upgrade
Self-hosted YouTube downloader built on Material Design
At a glance
- What is it?
- YoutubeDL-Material is a self-hosted web interface over youtube-dl, built with Angular 15 and Node, and shipped four ways: a release archive you configure by hand, a Docker compose file, a Heroku button, and a build-from-source path. The interesting content is entirely in where those four paths disagree with each other, since the documentation quotes a port the compose file does not use, the container already uses a database the readme presents as an upgrade, and the toolchain pins Node 12 in one place and Node 16 in two others.
- Who is it for?
- Use the container path if you can, because it is the only one where the configuration, the ports, and the database all arrive pre-wired, and prefer it over hand-editing a JSON file in an extracted archive. Before you start, note that the last commit is dated 2026-03-08 and the newest release tag is v4.3.2 from 2023-05-26, so the manifest version and the downloadable release have not moved in nearly three years.
- 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?
- Activity is slowing. The repository last received commits 7 months 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 5, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three Node versions appear in three files that all describe the same app
The toolchain requirements are stated three times and they do not agree.
The prerequisites section asks for Node.js 16 and Python.
The container image pins a specific patch of that line, setting an environment variable to 16.14.2 and installing it through a version manager rather than from the base image. A comment in the image file says that specific version is required and cites a question-and-answer thread as the reason.
The manifest disagrees with both. Its engines field names two exact versions:
"engines": {
"node": "12.3.1",
"npm": "6.10.3"
}Node 12 and npm 6, pinned to a patch, in a project whose dependencies are Angular 15 and a modern build chain.
There is a further wrinkle inside the image itself. The frontend build stage starts from a plain Node 16 tag, so the build runs on whatever patch that tag currently resolves to, while the runtime stage installs 16.14.2 through the version manager. The exact pin that the comment insists is required protects only the half of the image that runs the server.
The CentOS block enables Node 12 and then installs Node 16
One installation block contains three different Node references, and they do not compose.
sudo yum install rh-nodejs12
scl enable rh-nodejs12 bashThose two lines install Node 12 from a software collection and enable it. The next lines then install a different Node entirely, from a distribution's own package feed, configured for line 16.
So the block asks for Node 12, enables Node 12, and installs Node 16, in that order, on a platform whose prerequisites section says 16.
The enable line has a second problem on its own. Enabling a software collection changes the environment of the shell that runs it, which is useful interactively and does nothing for the commands that follow in a copied block. Someone following these lines step by step ends up with Node 16 from the package feed and a leftover enable command that will matter the next time they open a shell.
The block also installs the downloader itself, the media tools, and the archive tool, on both platform paths, so the dependency list is where the difference between the two instructions should have shown up and does not.
The Docker section names a port the compose file does not use
There are three port numbers in this project and one of them is wrong.
The manual installation reads the port from the configuration file in the appdata folder, and says it defaults to 17442. You are then told to forward that port.
The Docker section says to start the stack and watch for a success message that reports port 17443, describing it as the container-internal port, then to look in the compose file for the external port, which it says defaults to 8998 if you downloaded the file the way it told you to.
The compose file that ships in the repository maps neither of those pairs:
ports:
- "8998:17442"So the external port in the documentation is right, the container port is not. The image listens on 17442, and a user who took the success message at face value would be looking for a message the application never prints.
That is the kind of mismatch that costs an evening, and it is checkable in ten seconds by opening the compose file, which the same section tells you to consult.
The shipped compose file already uses the database the readme calls an upgrade
There is a section recommending a database, and it recommends something the container already does.
The section says that for much better scaling with large datasets you should run the instance against MongoDB rather than the json file-based default, that this fixes a lot of performance problems, especially with datasets in the tens of thousands of videos or audios, and that a tutorial exists in the wiki for setting it up.
The compose file in the repository already contains a database service, sets a connection string pointing at it, and sets the flag that turns the local file store off. It even carries a note telling Raspberry Pi users which database image tag to use instead.
So the advice and the artefact disagree about the default. A manual installation starts on the json store and is told to upgrade. A container installation starts on the database whether or not the reader has read the section at all.
That is not necessarily a defect in the configuration, since the compose file is a curated artefact. It is a documentation problem, because the section frames the database as something the operator must go and arrange.
A script named prebuild does postbuild work, and npm will run it for you
The script list contains a lifecycle trap that is worth naming.
"prebuild": "node src/postbuild.mjs",A script called prebuild does postbuild work: it runs a script named postbuild, and npm runs every pre-hook before the matching command. So the production build, which is invoked as the build script with a production configuration, is always preceded by a separate Node step that nobody asked for by name.
Two consequences follow. Anyone invoking the build tool directly, bypassing the script runner, gets a build without the postbuild step. And anyone reading the script list expecting postbuild to run finds it under the name prebuild instead.
The same list holds two more examples of the pattern. One script installs the backend dependencies under a different prefix, which is how the Heroku deployment gets its server-side packages. Another generates frontend types from an OpenAPI file that sits at the repository root, exporting the models but explicitly not the core or the services.
Two linters and two test runners, and the readme points at neither
The root directory carries four tool configurations where most projects carry two, and in each pair one of the two has been superseded by the other.
For linting there is a JSON configuration for the newer linter and a configuration file for the older one, and the npm script invokes the newer one. So the older configuration is present and unused by the documented command.
For tests the same split appears. There is a unit test runner configuration and an end-to-end runner configuration, plus an end-to-end directory, and the scripts invoke both. The end-to-end runner in question has been deprecated by its maintainers, which makes the one place a project is most likely to have a working end-to-end suite the place it is least likely to run on a current Node.
There is also a development container definition and a matching serve configuration in the script list, so the hosted development path is a first-class option with its own configuration, and none of these four files is mentioned in the readme.
A screenshot promise, a caption with no image, and an iOS section that stops mid-word
Two small gaps in the document are worth recording because they are where a reader loses the thread.
The getting started section promises a picture. It says here is an image of what it will look like once you are done, and the next line is a caption reading Dark mode. Nothing follows it. The section that is supposed to orient a new user before they install anything ends with a label for an image that is not in the file.
At the other end, the iOS section begins by inviting iOS users to use the application more conveniently with a short workflow, and stops in the middle of the word for that workflow. So the one platform-specific convenience feature is announced and never explained.
Between those two points the document is thorough. The install steps number what each one does, the troubleshooting paragraph tells you that problems are usually configuration and walks you to the browser console, the reverse proxy and port-forwarding options are both described, and there is a separate page for the public API, which is disabled by default and enabled from a settings tab with a key you generate yourself.
Editorial conclusion
Use the container path if you can, because it is the only one where the configuration, the ports, and the database all arrive pre-wired, and prefer it over hand-editing a JSON file in an extracted archive. Before you start, note that the last commit is dated 2026-03-08 and the newest release tag is v4.3.2 from 2023-05-26, so the manifest version and the downloadable release have not moved in nearly three years. And check the port you are about to forward: the manual instructions, the compose file, and the Docker section each name a different number.
Frequently asked questions
What is the replacement for YouTube-dl?
This repository does not propose one. It describes itself as a Material Design frontend for youtube-dl, and both of its platform-specific install paths install the youtube-dl package itself alongside the media tools. Nothing in the readme mentions yt-dlp, so if you are deciding between the two, this project is not making that argument.
Is yt-dlp a legitimate tool?
Nothing here addresses it; this repository wraps youtube-dl rather than yt-dlp. What it states about itself is that it is MIT licensed, self-hosted, and serves both a web interface and an optional public API over its own backend, with a database option for larger collections.
How do I install YoutubeDL-Material with Docker?
Download the compose file from the latest release with curl and save it locally, then run docker-compose pull followed by docker-compose up. The readme tells you to read the container-internal port from the startup message and the external port from the compose file, and says the external port defaults to 8998 for a file downloaded that way.
What port does YoutubeDL-Material use?
Three numbers appear in the documentation. A manual installation reads the port from the configuration file in the appdata folder, which it says defaults to 17442. The Docker section tells you to look for a startup message reporting port 17443. The compose file in the repository maps host port 8998 to container port 17442, so 17443 does not appear in the configuration that ships with the project.
Does YoutubeDL-Material need a database?
Not for the documented default, which is a json file-based store. The readme recommends MongoDB for much better scaling, particularly with datasets in the tens of thousands of videos, and links a wiki tutorial. The compose file shipped in the repository already includes a database service and disables the local-file store, so the container path arrives with it configured.
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/tzahi12345-youtubedl-material)