mvbar Buy me a coffeeDownload

DOCUMENTATION

Set up mvbar
your way.

Install mvbar on your own hardware, connect your music folders, and listen from the web player or companion apps. Choose the setup that fits your home.

YOUR MEDIAMusic · podcasts · audiobooks
→
mvbar SERVERWindows · Linux · Docker
→
YOUR PLAYERSWeb · Android · TV · Wear OS
01

Install the server

Choose one server method. You do not need all three.

Windows

Download the Windows x64 standalone executable and open it. The launcher prepares the local server and opens mvbar.

Windows guide →

Linux

On Debian or Ubuntu x64 or ARM64 (including Raspberry Pi OS 64-bit), open a terminal as your regular user and run:

curl -fsSL https://mvbar.co.uk/install.sh | sh

The installer detects your architecture, downloads the matching latest stable release, verifies its SHA-256 checksum, and opens the setup wizard to configure your libraries and install an automatically starting service. It requests sudo for system dependencies. Existing configuration and data are retained. Internet access and systemd are required.

View installer script. For a development build or manual installation, download and rename the selected executable to mvbar, then run:

chmod +x ./mvbar
./mvbar install-service
Linux guide →

Docker

Docker is a good fit for a NAS or an always-on home server. mvbar ships as one container, while its application data stays in persistent volumes.

Docker quick start →
02

First run

Point mvbar at your media folders, create the first administrator account and let the library scan complete.

Docker secrets

Set strong values for JWT_SECRET, MEILI_MASTER_KEY, ADMIN_EMAIL and ADMIN_PASSWORD before first start.

Your source music can be mounted read-only. Persistent mvbar application data should live separately from the media itself.

03

Connect players

The web player is served by your mvbar server. Android phone/tablet, Android TV and Wear OS clients connect to that same server address.

  • Android phone/tablet: Android 8.0+.
  • Android Auto support is part of the phone client.
  • TV and Wear are separate APKs released alongside the phone app.
Get current clients →
04

Remote access

For listening away from home, expose mvbar through your own secure HTTPS address or VPN. Do not expose unrelated internal services or database ports to the internet.

If you already use an external reverse proxy, mvbar supports running behind that proxy and secure cookies.

05

Backups & updates

mvbar can create portable .mvbar-backup archives from Admin → Settings. Database-only backups are the default and can move between Docker, Windows and Linux installations.

Source media files and deployment secrets are intentionally not included, so keep those protected separately.

See current releases →
06

Plugins

Official plugins are installed from Admin → Plugins. mvbar validates package identity and checksum and shows requested permissions before a plugin is enabled.

The public plugin catalogue on the homepage is loaded directly from the published plugin registry.

Browse official plugins →

DOCKER

Basic and advanced examples

Both examples run mvbar and its internal services in one container. Use one Compose file and a matching .env in the same directory. Basic uses port 8080; advanced uses 18080.

Basic: get your library running

Download basic Compose and basic environment settings. Rename the environment file to .env, set your media paths, administrator login and two separately generated secrets (openssl rand -hex 32). Then run:

docker compose config --quiet
docker compose up -d
View the basic Compose example
name: mvbar
services:
  app:
    image: mvbar.co.uk/mvbar:latest
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      TZ: ${TZ:-Europe/London}
      DATABASE_URL: postgresql://mvbar:mvbar@127.0.0.1:5432/mvbar
      REDIS_URL: redis://127.0.0.1:6379
      MEILI_HOST: http://127.0.0.1:7700
      MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:?Set MEILI_MASTER_KEY in .env}
      JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in .env}
      ADMIN_EMAIL: ${ADMIN_EMAIL:?Set ADMIN_EMAIL in .env}
      ADMIN_PASSWORD: ${ADMIN_PASSWORD:?Set ADMIN_PASSWORD in .env}
      TRUST_PROXY: ${TRUST_PROXY:-false}
      COOKIE_SECURE: ${COOKIE_SECURE:-auto}
      APP_DOMAIN: ${APP_DOMAIN:-}
      MUSIC_DIRS: /music
      AUDIOBOOK_DIRS: /audiobooks
      PLUGINS_ENABLED: "true"
      PLUGINS_DIR: /data/plugins
      PLUGIN_REGISTRY_URL: https://mvbar.co.uk/plugins/registry.json
      RESCAN_INTERVAL: ${RESCAN_INTERVAL:-1h}
      API_INTERNAL_BASE: http://127.0.0.1:3001
    volumes:
      - ${MUSIC_PATH:?Set MUSIC_PATH in .env}:/music:ro
      - ${AUDIOBOOK_PATH:-audiobooks}:/audiobooks:ro
      - pg:/var/lib/postgresql/data
      - redis:/data/redis
      - meili:/meili_data
      - caddy_data:/data/caddy
      - caddy_config:/config/caddy
      - cache:/data/cache
      - backups:/data/backups
      - plugins:/data/plugins
      - deezer_staging:/data/deezer-staging
      - device_logs:/data/device-logs
      - hls:/hls
      - podcasts:/podcasts
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost/health >/dev/null"]
      interval: 10s
      timeout: 5s
      retries: 15
volumes:
  pg:
  redis:
  meili:
  caddy_data:
  caddy_config:
  cache:
  backups:
  plugins:
  deezer_staging:
  device_logs:
  hls:
  podcasts:
  audiobooks:

Advanced: customise your server

Download advanced Compose and advanced environment settings. Save them as compose.advanced.yml and .env, edit the settings, then run:

docker compose -f compose.advanced.yml config --quiet
docker compose -f compose.advanced.yml up -d

Open http://your-server:18080. Optional integrations and backfills start disabled; enable only the ones you configure.

View the advanced Compose example
name: mvbar

services:
  app:
    image: mvbar.co.uk/mvbar:latest
    restart: unless-stopped
    ports:
      - "18080:80"
      # - "18443:443" # Optional: container-managed HTTPS
      # - "127.0.0.1:55432:5432" # Optional: local database administration
      - "443:443"
    environment:
      TZ: Europe/London

      # internal (single-container) connection strings
      DATABASE_URL: postgresql://mvbar:mvbar@127.0.0.1:5432/mvbar
      REDIS_URL: redis://127.0.0.1:6379
      MEILI_HOST: http://127.0.0.1:7700
      MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:?Set MEILI_MASTER_KEY in .env}

      # app config
      JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in .env}
      ADMIN_EMAIL: ${ADMIN_EMAIL:?Set ADMIN_EMAIL in .env}
      ADMIN_PASSWORD: ${ADMIN_PASSWORD:?Set ADMIN_PASSWORD in .env}
      TRUST_PROXY: ${TRUST_PROXY:-true}
      COOKIE_SECURE: ${COOKIE_SECURE:-auto}
      COOKIE_NAME: ${COOKIE_NAME:-mvbar_token}
      APP_DOMAIN: ${APP_DOMAIN:-}
      LASTFM_API_KEY: ${LASTFM_API_KEY:-}
      LASTFM_API_SECRET: ${LASTFM_API_SECRET:-}
      OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
      AI_SEARCH_MODEL: ${AI_SEARCH_MODEL:-openrouter/free}
      AI_SEARCH_FREE_MODEL: ${AI_SEARCH_FREE_MODEL:-openrouter/free}
      LDAP_AUTO_CREATE_USERS: ${LDAP_AUTO_CREATE_USERS:-true}
      LDAP_USER_GROUP: ${LDAP_USER_GROUP:-}
      LDAP_ADMIN_GROUP: ${LDAP_ADMIN_GROUP:-}
      LDAP_ENABLED: ${LDAP_ENABLED:-false}
      LDAP_URL: ${LDAP_URL:-}
      LDAP_BASE_DN: ${LDAP_BASE_DN:-}
      LDAP_BIND_DN: ${LDAP_BIND_DN:-}
      LDAP_BIND_PASSWORD: ${LDAP_BIND_PASSWORD:-}
      LDAP_BIND_PASSWORD_FILE: ${LDAP_BIND_PASSWORD_FILE:-}
      LDAP_CA_FILE: ${LDAP_CA_FILE:-}
      LDAP_USER_FILTER: ${LDAP_USER_FILTER:-}
      LDAP_ID_ATTRIBUTE: ${LDAP_ID_ATTRIBUTE:-objectGUID}
      LDAP_EMAIL_ATTRIBUTE: ${LDAP_EMAIL_ATTRIBUTE:-mail}
      LDAP_DIRECTORY_ID: ${LDAP_DIRECTORY_ID:-}
      GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
      GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
      GOOGLE_CALLBACK_URL: ${GOOGLE_CALLBACK_URL:-}
      WEB_PUSH_ENABLED: ${WEB_PUSH_ENABLED:-false}
      WEB_PUSH_VAPID_PUBLIC_KEY: ${WEB_PUSH_VAPID_PUBLIC_KEY:-}
      WEB_PUSH_VAPID_PRIVATE_KEY: ${WEB_PUSH_VAPID_PRIVATE_KEY:-}
      WEB_PUSH_VAPID_SUBJECT: ${WEB_PUSH_VAPID_SUBJECT:-}
      BACKUP_MAX_UPLOAD_MB: ${BACKUP_MAX_UPLOAD_MB:-20480}
      BACKUP_DIR: /data/backups
      PLUGINS_ENABLED: ${PLUGINS_ENABLED:-true}
      PLUGINS_DIR: /data/plugins
      PLUGIN_REGISTRY_URL: ${PLUGIN_REGISTRY_URL:-https://mvbar.co.uk/plugins/registry.json}
      PLUGIN_MAX_UPLOAD_MB: ${PLUGIN_MAX_UPLOAD_MB:-50}
      PLUGIN_TIMEOUT_MS: ${PLUGIN_TIMEOUT_MS:-15000}
      PLUGIN_MEMORY_MB: ${PLUGIN_MEMORY_MB:-64}
      PLUGIN_MAX_CONCURRENCY: ${PLUGIN_MAX_CONCURRENCY:-4}
      DEEZER_ARL: ${DEEZER_ARL:-}
      DEEZER_DOWNLOAD_DIR: /data/deezer-staging
      DEEZER_QUALITY: ${DEEZER_QUALITY:-0}
      DEEZER_IMPORT_LYRICS: ${DEEZER_IMPORT_LYRICS:-true}
      DEEZER_METADATA_CONCURRENCY: ${DEEZER_METADATA_CONCURRENCY:-4}

      # worker
      MUSIC_DIRS: /music
      AUDIOBOOK_DIRS: /audiobooks
      UV_THREADPOOL_SIZE: ${UV_THREADPOOL_SIZE:-32}
      SCAN_CONCURRENCY: ${SCAN_CONCURRENCY:-25}
      METADATA_TIMEOUT_MS: ${METADATA_TIMEOUT_MS:-300000}
      RESCAN_INTERVAL: ${RESCAN_INTERVAL:-}
      RESCAN_INTERVAL_MS: ${RESCAN_INTERVAL_MS:-}
      TEMPO_DETECT: ${TEMPO_DETECT:-0}
      TEMPO_MODE: ${TEMPO_MODE:-batch}
      TEMPO_METHOD: ${TEMPO_METHOD:-energy}
      TEMPO_MIN_CONF: ${TEMPO_MIN_CONF:-0.35}
      TEMPO_CONCURRENCY: ${TEMPO_CONCURRENCY:-2}
      TEMPO_BACKFILL_INTERVAL_MS: ${TEMPO_BACKFILL_INTERVAL_MS:-1800000}
      TEMPO_BACKFILL_BATCH: ${TEMPO_BACKFILL_BATCH:-50}
      COUNTRY_BACKFILL: ${COUNTRY_BACKFILL:-0}
      COUNTRY_BACKFILL_INTERVAL_MS: ${COUNTRY_BACKFILL_INTERVAL_MS:-3600000}
      COUNTRY_BACKFILL_BATCH: ${COUNTRY_BACKFILL_BATCH:-100}
      COUNTRY_BACKFILL_NAME_MATCH: ${COUNTRY_BACKFILL_NAME_MATCH:-1}
      LANGUAGE_BACKFILL: ${LANGUAGE_BACKFILL:-0}
      LANGUAGE_BACKFILL_INTERVAL_MS: ${LANGUAGE_BACKFILL_INTERVAL_MS:-3600000}
      LANGUAGE_BACKFILL_BATCH: ${LANGUAGE_BACKFILL_BATCH:-100}
      LANGUAGE_DETECT_LYRICS: ${LANGUAGE_DETECT_LYRICS:-1}
      LANGUAGE_DETECT_MIN_CONFIDENCE: ${LANGUAGE_DETECT_MIN_CONFIDENCE:-0.72}
      MUSICBRAINZ_USER_AGENT: ${MUSICBRAINZ_USER_AGENT:-mvbar/2.7 (https://mvbar.co.uk)}
      MUSICBRAINZ_REQUEST_INTERVAL_MS: ${MUSICBRAINZ_REQUEST_INTERVAL_MS:-1100}
      MUSICBRAINZ_TIMEOUT_MS: ${MUSICBRAINZ_TIMEOUT_MS:-15000}

      # web -> api internal calls
      API_INTERNAL_BASE: http://127.0.0.1:3001

    volumes:
      # mount your music (edit these)
      - ${MUSIC_PATH:?Set MUSIC_PATH in .env}:/music:ro
      #- /path/to/music2:/music2:ro

      # mount your audiobooks
      - ${AUDIOBOOK_PATH:-audiobooks}:/audiobooks:ro

      # Optional LDAPS certificate: uncomment after creating ./certs/ldap-ca.pem
      # - ./certs/ldap-ca.pem:/run/certs/ldap-ca.pem:ro

      # persistent data
      - pg:/var/lib/postgresql/data
      - redis:/data/redis
      - meili:/meili_data
      - caddy_data:/data/caddy
      - caddy_config:/config/caddy
      - cache:/data/cache
      - backups:/data/backups
      - plugins:/data/plugins
      - deezer_staging:/data/deezer-staging
      - device_logs:/data/device-logs
      - hls:/hls
      - podcasts:/podcasts

    # Optional: attach to an existing proxy network (also uncomment networks below)
    # networks: [prodNet]

    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost/health >/dev/null"]
      interval: 10s
      timeout: 5s
      retries: 15

volumes:
  pg:
  redis:
  meili:
  caddy_data:
  caddy_config:
  cache:
  backups:
  plugins:
  deezer_staging:
  device_logs:
  hls:
  podcasts:
  audiobooks:

# networks:
#   prodNet:
#     external: true

Reverse proxy, HTTPS and networks

Point your existing HTTPS proxy at port 18080. Set APP_DOMAIN to your public HTTPS URL and configure trusted proxy headers with TRUST_PROXY and COOKIE_SECURE. For direct HTTP access, set TRUST_PROXY=false. Port 18443 is optional for container-managed HTTPS; publishing it alone does not configure a certificate.

For an existing Docker proxy network, uncomment both networks blocks and replace prodNet with your network name. An external network must already exist. PostgreSQL needs no host port; the commented mapping is bound to localhost for optional administration.

Integrations and library processing

LDAP / Active Directory

Replace the example directory URL, base DN, bind DN, directory ID and group DNs in the advanced environment file. Set the bind password, place your CA certificate at ./certs/ldap-ca.pem, and uncomment its read-only mount. LDAP_CA_FILE=/run/certs/ldap-ca.pem is the path inside the container. Then set LDAP_ENABLED=true. Your directory hostname must resolve and be reachable from the container.

Development channel and updates

Use basic Development or advanced Development for :dev. Pass the chosen filename with -f for every Compose command. Back up before updating and keep your existing project name and volume names when adapting an existing installation.

docker compose -f compose.advanced.yml pull
docker compose -f compose.advanced.yml up -d
docker compose -f compose.advanced.yml ps
docker compose -f compose.advanced.yml logs --tail=100 app

Named volumes preserve application data across container recreation. Keep your .env private and back up secrets separately. Avoid docker compose down -v when keeping your data.

READY?

Start with the server.

Once your server can see your library, every other mvbar client has somewhere to connect.

Choose a download