Self-hosted service
museofficial/muse avatar
museofficial/muse

Muse, a self-hosted Discord music bot that expects you to run it yourself

🎧 a self-hosted midwestern Discord music bot that doesn't suck

1,302 stars352 forksTypeScriptMIT

At a glance

What is it?
Muse is a TypeScript Discord music bot for small to medium Discord servers, running either as a Docker container or directly on Node.js. It converts Spotify links, resolves YouTube media through yt-dlp, and leaves the trade-offs of running your own bot in your hands.
Who is it for?
Muse suits a small or medium Discord server that wants its own music bot, controls its own data, and accepts the burden of holding a Discord token, a YouTube API key and, for age-restricted videos, a cookie jar on its own machine. It does not suit anyone who wants a bot someone else hosts, and it does not fit a Windows setup unless ffmpeg can be pointed at manually.
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 33 days 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A music bot pitched at a server the size of a group chat

Muse describes itself as a self-hosted Discord music bot, and the project documentation is blunt about the audience: small to medium sized Discord servers, the kind made of you, your friends and your friend's friends. The feature list matches that sizing. A single Muse instance serves multiple guilds, streams are supported, SoundCloud tracks and share links work, and you can seek within a song or video. Playlists, artists, albums and songs from Spotify are converted automatically, and users can save favorite queries for reuse. Two design choices stand out. There is no vote-to-skip, which the project calls anarchy rather than democracy, so nobody can lose a track to a poll. And there is local caching, which is described as the reason for better performance, plus configurable volume controls including optional ducking when people speak. It is written in TypeScript, which the project presents as making it easy to extend.

YouTube media is resolved by yt-dlp, not by an API call

The mechanism behind playback is worth understanding before you deploy. Muse uses yt-dlp to resolve playable YouTube media URLs, and in Docker the image already includes it. For a direct Node.js install you either put yt-dlp on your PATH or set YT_DLP_PATH to its full path, and Muse logs YT_DLP_VERSION on startup so you can see what it is running. Setting YT_DLP_AUTO_UPDATE=true makes Muse attempt an update of the configured yt-dlp before it connects to Discord, which the project documentation says this works best with the Docker image's bundled virtualenv or when YT_DLP_PATH points at a virtualenv or standalone executable Muse can write to. Age-restricted videos need a Netscape cookie file from an age-verified YouTube account, mounted outside the data directory, with YT_DLP_COOKIES_PATH set to its path inside the container. Muse copies it to a private temporary file per extraction, because yt-dlp rewrites the cookie jar while it runs.

The Docker path: ghcr image, one data volume, four keys

Docker is the recommended way to run Muse, and the canonical image source has changed: the project is now community-maintained under the museofficial organization, and GHCR is the supported target for new deployments, with Docker Hub tags kept only as compatibility mirrors. The image needs a Discord token, a YouTube API key and, if you want Spotify URL conversion, the Spotify client id and secret. This starts the bot and creates a data directory in your current directory:

bash
docker run -it -v "$(pwd)/data":/data -e DISCORD_TOKEN='' -e SPOTIFY_CLIENT_ID='' -e SPOTIFY_CLIENT_SECRET='' -e YOUTUBE_API_KEY='' ghcr.io/museofficial/muse:latest

Replace the empty values with your own keys before running it. Tokens can also live in an environment file: the container looks for a /config environment file by default, and the ENV_FILE variable changes that path if you prefer something like Docker secrets.

yaml
services:
  muse:
    image: ghcr.io/museofficial/muse:latest
    restart: always
    volumes:
      - ./muse:/data
    environment:
      - DISCORD_TOKEN=
      - YOUTUBE_API_KEY=
      - SPOTIFY_CLIENT_ID=
      - SPOTIFY_CLIENT_SECRET=

Keep the same DISCORD_TOKEN, reuse the same data volume and point the service at a newer tag, and the bot comes back with the same identity and the same persisted database and cache.

The Node.js path needs 22.12, ffmpeg and yt-dlp on your PATH

Running without Docker is supported and asks for more from the host. A 64-bit OS is required. You need Node.js 22.12.0 or newer, ffmpeg 4.1 or later, and yt-dlp on your PATH, or the YT_DLP_PATH variable pointing at it. The project notes that current YouTube extraction also requires a supported JavaScript runtime, and that direct installs should install yt-dlp with its default extra and provide Node.js 22 or newer. The setup sequence is short:

bash
git clone https://github.com/museofficial/muse.git && cd muse

Copy .env.example to .env and fill in the values, check out a tagged release with git checkout, then install and start:

bash
yarn install
yarn start

npm i and npm run start work as alternatives. On Windows you may have to set the ffmpeg path by hand, and the project documentation points at issue 345 for that. One warning applies to both installation routes: the master branch is the developing branch and is not guaranteed to be stable, so a production instance should run the latest tagged release.

Caching, playlists and the SponsorBlock dependency

Two configuration details shape day to day use. Cache size is capped by default at around 2 GB, and the CACHE_LIMIT environment variable changes that, for example CACHE_LIMIT=512MB or CACHE_LIMIT=10GB. SponsorBlock integration, which skips non-music segments at the start or end of a YouTube music video, is disabled by default and is enabled with ENABLE_SPONSORBLOCK=true. It depends on someone else's server: the project documentation warns that the SponsorBlock service may be down or overloaded, and that Muse will skip requests for a few minutes when that happens, with SPONSORBLOCK_TIMEOUT controlling the delay before retrying. That is the honest trade-off of the feature. You get cleaner music videos when the upstream service answers, and occasional silent skips when it does not, which is a reasonable price for something most users will not notice at all.

Self-hosting versus letting someone else run the bot

The clearest alternative to Muse is a bot you do not host, and the difference is not about features so much as where the responsibility sits. With Muse you hold the Discord bot token, the YouTube API key and the Spotify credentials, you keep the cache and database on your own disk, and you decide when to update the image tag. You also carry the operational parts the project makes explicit: a 64-bit host, ffmpeg, a JavaScript runtime for yt-dlp extraction, and cookie handling for age-restricted videos. A hosted bot moves all of that to someone else's server and removes the keys from your hands. What you give up is control over the data and the upgrade timing, and what you get back is no Docker, no ffmpeg path, and no cookie jar to protect. Muse's design assumes you want that control, and the project guidance to run a tagged release in production shows how seriously it treats the burden.

Releases, migrations and who keeps the project moving

Muse is now community-maintained under the museofficial organization, which the project documentation flags as important for anyone picking an image source. The project is MIT licensed. Releases are frequent and versioned: v2.11.8 on 2026-09-20, v2.11.7 on 2026-08-21 and v2.11.6 on 2026-07-12, with the last push on 2026-09-20. The repository carries a migrations directory and a schema.prisma, and the start script runs a migrate-and-start step, so schema changes are applied on boot rather than by hand. There is also a yt-dlp-latest image, rebuilt on a schedule from the latest Muse release with the newest yt-dlp on PyPI, plus versioned refresh tags of the form muse-version-yt-dlp-yt-dlp-version. A separate local validation suite is available through the test script:

bash
npm test

The upgrade cost of a Muse instance is therefore a pull of a newer tag, a volume that keeps the same data, and a watch on yt-dlp, which is the component most likely to break first when YouTube changes.

Editorial conclusion

Muse suits a small or medium Discord server that wants its own music bot, controls its own data, and accepts the burden of holding a Discord token, a YouTube API key and, for age-restricted videos, a cookie jar on its own machine. It does not suit anyone who wants a bot someone else hosts, and it does not fit a Windows setup unless ffmpeg can be pointed at manually. Before deploying, check the version of yt-dlp you are shipping, since YouTube extraction depends on it and on a supported JavaScript runtime, and confirm that the tag you pull matches the master branch state you tested.

Frequently asked questions

How do I run the Muse Discord music bot?

The project documentation recommends Docker with the ghcr.io/museofficial/muse image, passing DISCORD_TOKEN and YOUTUBE_API_KEY, plus SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET if you want Spotify link conversion. You can also install it directly on Node.js 22.12.0 or newer with ffmpeg and yt-dlp present.

Which Docker image should I pull for Muse?

GHCR is the supported target for new deployments since the project moved under the museofficial organization, and Docker Hub tags are kept only as compatibility mirrors. You can also use a tag such as yt-dlp-latest, which is rebuilt from the latest Muse release with the newest yt-dlp published to PyPI.

Why does Muse fail to play age-restricted YouTube videos?

Age-restricted videos need cookies from an age-verified YouTube account exported as a Netscape cookie file, mounted outside the data directory, with YT_DLP_COOKIES_PATH set to its path in the container. Muse copies the file to a private temporary file per extraction because yt-dlp updates the cookie jar while it runs.

Official sources

  1. Issues
  2. License: MIT
  3. museofficial/muse 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/museofficial-muse.svg)](https://hysenlabs.com/projects/museofficial-muse)

Was this useful?