A self-hosted Radarr/Sonarr inspired automated music video management/grabber/streamer application.
apps/web(port3000): web UIapps/server(port3002): REST API, streaming, metadataapps/worker(no port): background downloads and processingpostgres(port5432): data storeredis(port6379): queue/backplane
Any Docker environment works. For Ubuntu 20.04:
- Install Docker and the Compose plugin:
sudo apt update sudo apt install -y ca-certificates curl gnupg lsb-release sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- Allow your user to run Docker without sudo:
sudo usermod -aG docker $USER newgrp docker
Create a .env file alongside your docker-compose.yml (repo root if you cloned it):
POSTGRES_DB=mudarr
POSTGRES_USER=mudarr
POSTGRES_PASSWORD=changeme
# Optional but recommended
MEDIA_ROOT=/data/media
API_PORT=3002
WEB_PORT=3000
REDIS_PORT=6379
POSTGRES_PORT=5432
VITE_API_URL=http://localhost:3002
# Optional: move HLS temp files onto the storage volume
HLS_TMP_DIR=/data/storage/hls
# Optional integrations
PLEX_ENABLED=false
SPOTIFY_ENABLED=false
LASTFM_ENABLED=false
LASTFM_API_KEY=
AUDIODB_API_KEY=
# Optional downloader controls
WORKER_CONCURRENCY=
YT_DLP_OUTPUT_FORMAT=mp4-remux
YT_DLP_COOKIES=
YT_DLP_COOKIES_FROM_BROWSER=The web UI defaults to http://localhost:3002. If you expose the API on a different host port
(API_PORT), set VITE_API_URL to that host/port. The web container reads VITE_API_URL
at startup (no rebuild required).
- Create
apps/web/.env(for local builds):VITE_API_URL=http://localhost:3002
- Restart the web container:
docker compose up -d web
The compose file mounts two host paths:
./media->/data/media(downloaded videos)./storage->/data/storage(optional scratch space, cookies, HLS tmp)
Make sure your chosen media root in the UI matches the container path. With the default compose mounts, use /data/media.
If you want custom host paths, update docker-compose.yml:
volumes:
- /mnt/media/videos:/data/media
- /mnt/media/storage:/data/storagemkdir -p ./media ./storage
docker compose up -dThen open http://localhost:3000 and complete the initial setup:
- Create the admin user.
- Set the media storage destination to
/data/media(or your custom path). - If you plan to stream externally, set the public base URL in Settings.
To view logs:
docker compose logs -f server worker- Use the provided
docker-compose.yml(clone the repo) or create your own compose file that references the GHCR images.git clone https://github.com/vicebooster/mudarr.git cd mudarr - Create your
.envfile:cp .env.example .env
- Create required host folders:
mkdir -p ./media ./storage
- (Optional) Customize storage paths in
docker-compose.yml:volumes: - /mnt/media/videos:/data/media - /mnt/media/storage:/data/storage
- Start the stack:
docker compose up -d
- Open
http://localhost:3000and finish setup:- Create the admin user.
- Set media storage destination to
/data/media(or your custom path). - If streaming externally, set the public base URL in Settings.
Notes:
./mediamaps to/data/mediainside the containers (downloads)../storagemaps to/data/storage(scratch space, cookies, HLS temp).
Available images:
ghcr.io/vicebooster/mudarr/server:latestghcr.io/vicebooster/mudarr/web:latestghcr.io/vicebooster/mudarr/worker:latest
Then pull and restart:
docker compose pull
docker compose up -dRun these commands from the folder that contains your docker-compose.yml (or pass -f /path/to/docker-compose.yml).
- Pull the latest images:
docker compose pull
- Restart containers:
docker compose up -d
- Optional cleanup:
docker image prune -f
For a single domain, proxy /api to the server and everything else to the web UI.
server {
listen 80;
server_name mudarr.example.com;
location /api/ {
proxy_pass http://127.0.0.1:3002/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 3600s;
}
location / {
proxy_pass http://127.0.0.1:3000/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}If you use a single domain, set VITE_API_URL=https://mudarr.example.com and set the public base URL in Settings to the same value.
For Cloudflare Tunnel, the simplest setup is two hostnames:
mudarr.example.com-> web (http://localhost:3000)api.mudarr.example.com-> server (http://localhost:3002)
Example ~/.cloudflared/config.yml:
tunnel: mudarr
credentials-file: /home/ubuntu/.cloudflared/mudarr.json
ingress:
- hostname: mudarr.example.com
service: http://localhost:3000
- hostname: api.mudarr.example.com
service: http://localhost:3002
- service: http_status:404Then:
cloudflared tunnel create mudarr
cloudflared tunnel route dns mudarr mudarr.example.com
cloudflared tunnel route dns mudarr api.mudarr.example.com
cloudflared tunnel run mudarrSet VITE_API_URL=https://api.mudarr.example.com, and set the public base URL in Settings to https://api.mudarr.example.com so streams resolve correctly.
Plex, Spotify, and Last.fm are optional and can be enabled via .env and the Settings UI.
Mudarr is open source under the GNU Affero General Public License v3.0.
See LICENSE for the full text and THIRD_PARTY_NOTICES.md for attributions.
You are responsible for how you use this software and for complying with applicable laws and content rights. The authors and contributors are not responsible for any unauthorized or unlawful use.

