Self-hosted service
Stardm0/MoonTV avatar
Stardm0/MoonTV

MoonTV: a self-hosted video aggregation front end that ships no sources

🎬 MoonTV 是一个开箱即用的、跨平台的影视聚合播放器。本项目现基于2.0.1开始自行维护更新(提醒: cloudflare部署有封pages的风险)

989 stars1,679 forksTypeScriptMIT

At a glance

What is it?
MoonTV is a Next.js 14 and TypeScript player for streaming media, deployable to Docker, Vercel, Netlify or Cloudflare Pages, with multi-source search, PWA support and a choice of storage backends. The key fact is that a fresh deployment is an empty shell until you add your own sources.
Who is it for?
Adopt MoonTV if you want a responsive, installable front end for video sources you already control, and if you are comfortable wiring up storage and an optional danmaku backend yourself. Do not adopt it expecting a built-in content library, since the documentation states plainly that a fresh deployment contains no playback sources at all.
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 received new commits within the last day.
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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A deployment with no content in it

The single most important line in this documentation is the warning that a deployed project is an empty shell with no built-in playback sources, and that you need to collect your own. Everything else in the feature list, multi-source aggregated search, detail pages with episode lists and cast, online playback, downloads, favourites, watch history and following of ongoing series, describes the application shell rather than a catalogue. Two optional pieces follow the same pattern. Danmaku comments need a backend you deploy yourself, with the danmu_api project named as the reference implementation. The weekly new-anime calendar is built on the Bangumi API, and if it will not load you are pointed at a proxy project. The documentation's table of contents also lists sections covering AndroidTV usage, TVBox integration and Selene, so the same front end is meant to serve several kinds of client.

Next.js 14, Tailwind 3 and the player stack

The technical stack is a Next.js 14 App Router application styled with Tailwind CSS 3 and written in TypeScript 4, with ESLint, Prettier and Jest for code quality. Playback is built on ArtPlayer and HLS.js, and the dependency list adds `@vidstack/react` alongside the artplayer danmaku plugin, `next-pwa` for the installable progressive web app behaviour, `next-themes` for the light and dark switch and `swiper` for the carousel interfaces. The video download feature handles M3U8 with concurrent downloading and a save-as-you-watch mode, which the documentation restricts to Chrome and Edge. One dependency deserves a flag rather than a summary: `mux.js` is not pulled from the registry but from a GitHub fork, pinned in package.json as `github:Momo707577045/mux.js`, so an upgrade of that package follows a third party repository rather than the upstream release line.

Four storage backends and a matrix that admits what is untested

Storage is where the deployment matrix earns its keep, because the four backends are not interchangeable across the four hosting targets. Local browser storage works everywhere, Docker. Cloudflare D1 works only on Cloudflare, and native Redis works only on Docker. Upstash Redis is the option that spans Vercel, Netlify, Cloudflare and Docker. The legend under the table is the honest part: a check mark means tested and supported, while a different mark means theoretically supported but untested. Reading the two together, the combination of Upstash Redis on Docker is one of the untested ones, and native Redis has no entry outside Docker. Among the Docker compose options, Kvrocks is the recommended one, Redis is annotated as carrying some risk of data loss, and Upstash is offered as well. Since favourites, watch history and follow lists all live in this layer, the choice of backend is a data durability decision, not a configuration detail.

Deploying with a single Docker command

The shortest path to a running instance pulls the prebuilt image and starts the container:

bash
docker pull ghcr.io/stardm0/moontv:latest

docker run -d --name moontv -p 3000:3000 --env PASSWORD=your_password ghcr.io/stardm0/moontv:latest

The container listens on port 3000 and the mapping publishes it on the host, while `PASSWORD` is passed in as the environment variable that protects the site. The compose file shipped for the same purpose looks like this:

yaml
services:
  startv-core:
    image: ghcr.io/stardm0/moontv:latest
    container_name: startv-core
    restart: on-failure
    ports:
      - '3000:3000'
    environment:
      - PASSWORD=password

Either route gives you an application with no sources in it, so the remaining work is configuration: setting `config.json` in your fork, adding your sources, and choosing an admin account.

Cloudflare Pages needs a non-default build and a D1 bootstrap

The Cloudflare route is the most involved of the four, and the project description warns separately that Cloudflare deployments carry a risk of the Pages account being banned. The build settings are not defaults. The build command is `pnpm run pages:build`, the preset framework is set to none, and the output directory is `.vercel/output/static`, which follows from that command wrapping the Next.js build for Cloudflare. After the first deployment you add the compatibility flag `nodejs_compat` and set `PASSWORD` as a secret rather than as plain text, and the documentation repeats that variables should be configured as secrets where possible. For the D1 backend, you create a database, paste the contents of `d1-init.sql` into the query window and run it, then add a D1 binding named `DB` and set `NEXT_PUBLIC_STORAGE_TYPE` to `d1` along with `USERNAME` and `PASSWORD` for the admin account.

The Docker build rewrites your source files as it compiles

The Dockerfile is unusual enough to be worth reading before you trust the image. It runs three stages on `node:20-alpine`: a dependency stage that enables corepack, activates pnpm and runs `pnpm install --frozen-lockfile`, a builder stage that copies the sources and then uses `sed` to replace `export const runtime = 'edge';` with `export const runtime = 'nodejs';` across every `route.ts`, `layout.tsx` and `not-found.tsx` under `src`, and a runner stage that copies the standalone output, `scripts`, `start.js`, `config.json` and the static assets. The builder also inserts `export const dynamic = 'force-dynamic';` into `src/app/layout.tsx` so that runtime environment variables are read at request time rather than baked in at build time, and the runner creates a non-root user with `addgroup -g 1001 -S nodejs` and `adduser -u 1001 -S nextjs -G nodejs`. The consequence for you is that the Docker image and a Pages build are not the same artefact, and the runtime the routes execute under depends on that in-image rewrite.

Version drift between the package and the releases

Two numbering schemes sit side by side here. The package manifest declares version 0.1.0 with `private: true`, while the tagged releases are v4.3.0 on 2026-10-13, v4.2.5 on 2026-10-06 and v4.2.1 on 2026-09-20, and the last push was on 2026-10-19. The package version is an internal marker rather than a release number, so it tells you nothing about which upstream features are present. The build scripts are more informative about how the project expects to be worked on: `build` runs the manifest and runtime generators before `next build` and then restores the service worker, `typecheck` runs `tsc --noEmit --incremental false`, `lint:strict` runs ESLint with `--max-warnings=0`, and `prepare` installs husky, with a `commitlint.config.js` and a `.husky/` directory at the top level. The changelog lives in a `CHANGELOG` file and the version marker in `VERSION.txt`.

Editorial conclusion

Adopt MoonTV if you want a responsive, installable front end for video sources you already control, and if you are comfortable wiring up storage and an optional danmaku backend yourself. Do not adopt it expecting a built-in content library, since the documentation states plainly that a fresh deployment contains no playback sources at all. Verify first which storage backend you will run, because the matrix marks only some combinations as tested, and note that Redis carries a stated risk of data loss while Cloudflare Pages deployments carry a stated risk of the account being banned.

Frequently asked questions

Does MoonTV come with built-in video sources?

No. The documentation states that after deployment the project is an empty shell with no built-in playback sources, and that you need to collect sources yourself. The danmaku feature likewise requires you to deploy the danmu_api backend separately.

Which storage backends can MoonTV use on which platforms?

Local browser storage works on Docker, Vercel, Netlify and Cloudflare. Native Redis is listed for Docker only, Cloudflare D1 for Cloudflare only, and Upstash Redis across all four. The table marks some combinations as tested and others as theoretically supported but untested.

What environment variables does a MoonTV deployment need?

`PASSWORD` protects the site and is required on every platform. With Upstash you add `UPSTASH_URL` and `UPSTASH_TOKEN` and set `NEXT_PUBLIC_STORAGE_TYPE` to `upstash`, and with D1 you bind a database named `DB` and set `NEXT_PUBLIC_STORAGE_TYPE` to `d1`. `USERNAME` and `PASSWORD` together form the admin account.

How do I run MoonTV with Docker?

Pull the image `ghcr.io/stardm0/moontv:latest` and run it with `docker run -d --name moontv -p 3000:3000 --env PASSWORD=your_password ghcr.io/stardm0/moontv:latest`, or use the compose file in the repository, which maps port 3000 and sets `PASSWORD` in the environment.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. Stardm0/MoonTV on GitHub
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/stardm0-moontv.svg)](https://hysenlabs.com/projects/stardm0-moontv)

Was this useful?