media-vault

media-vault

apps.detail.types.app from halvar100's Repository

apps.detail.sections.overview

A self-hosted, open-source catalog for physical media (games, movies, vinyl, CDs) with automatic cover/rating/description enrichment and a signature spine-shelf view.

media-vault

Working name — placeholder. A self-hosted, open-source catalog for physical media collections: video games, movies, vinyl LPs & singles, CDs. Your data lives on your server. Think "Jellyfin for physical-media collectors."

Each item automatically gets a cover image, a rating, and a description, fetched from the right database per media type (IGDB for games, TMDB for films, Discogs for vinyl/CD) and unified into one browsable spine shelf.

status license


Why self-hosted

People who collect physical media do so precisely because they distrust digital-only, cloud-locked ownership. So this app is one you run on your own server, with your own data and your own free API keys. No global accounts, no SaaS lock-in.

Features (v1)

  • Unified catalog across games / movies / vinyl LPs / singles / CDs — one item model.
  • Automatic artwork — cover image + rating + description auto-fetched per media type.
  • Signature spine-shelf view (colour = platform/format) plus a gallery grid.
  • Add flows: title search with auto-fill, CSV bulk import, best-effort in-browser barcode scan.
  • Server-side metadata cache — each title fetched once, respecting API terms.
  • Valuation — per-item market value + a collection total, condition-aware. Music via Discogs marketplace (free), games via PriceCharting (paid API token required), and manual values for anything else.
  • Multi-user within one instance (e.g. a household) via local email + password.

Metadata sources (bring your own free keys)

Media Source Get a key
Games IGDB (via Twitch app) https://dev.twitch.tv/console/apps
Movies TMDB https://www.themoviedb.org/settings/api
Vinyl / Singles / CD Discogs https://www.discogs.com/settings/developers

Enrichment stays idle for any source whose keys are blank — the app still runs.


Quick start (Docker)

cp .env.example .env      # then fill in your keys + a SESSION_SECRET
docker compose up -d      # pulls the prebuilt image (no build step)

Open http://localhost:8080, log in as the seed user (admin@media-vault.local / SEED_USER_PASSWORD), then click Enrich collection to fetch covers/ratings/descriptions. The app is a single image (API + UI on one port) plus a Postgres container.

Updating is just a pull — no rebuild:

docker compose pull && docker compose up -d

Images are published automatically to ghcr.io/halvar20000/media-vault on every commit.

Unraid

Install a postgres:16-alpine container first and point the DB variables at it, then either:

  • Community Applications — search for media-vault in the Apps tab (once listed), or
  • Docker → Add Container → Template with: https://raw.githubusercontent.com/halvar20000/media-vault/main/templates/media-vault.xml, or
  • the Docker Compose Manager plugin with the docker-compose.yml above.

The CA store listing is defined by ca_profile.xml + templates/media-vault.xml.

Local development (no Docker)

Requires Node 20+ and a local Postgres.

# 1. Postgres — create a db matching your .env, then:
cd backend && npm install && npm run migrate && npm run seed && npm run dev   # API on :4000
cd frontend && npm install && npm run dev                                     # UI on :5173 (proxies /api)

Configuration

All config is via environment variables — see .env.example for the full list (database, session secret, app origin, and the three metadata-source keys).

MARKETPLACE picks which second-hand marketplace(s) the find deals / bundles buttons search, so titles and lot phrasing come out in each site's language. Comma-separated — each value adds its own button (e.g. MARKETPLACE=leboncoin,medimops,kleinanzeigen). Values: leboncoin (France, default), kleinanzeigen (Germany), ebay-de, ebay-com, ebay-uk, ebay-fr, marktplaats (Netherlands), wallapop (Spain), medimops (German used-media shop), or none to hide the buttons.

Tech stack

  • Backend — Node.js + TypeScript + Express, PostgreSQL (pg, plain SQL migrations).
  • Frontend — React + Vite + TypeScript.
  • Packaging — docker-compose (Postgres + backend + nginx). Works behind Cloudflare Tunnel / nginx / Traefik.

Project docs

License

AGPL-3.0 — anyone who offers this as a network service must share their changes back to the community.

apps.marketingCta.appInstallTitle

apps.marketingCta.appInstallDescription

apps.installHelp.stepOpen apps.installHelp.stepSearchApp apps.installHelp.stepReview apps.installHelp.stepInstall

apps.detail.sections.requirements

A PostgreSQL 16 database. Install a postgres:16-alpine container first (set POSTGRES_USER/PASSWORD/DB) and link it via the DB variables below.

apps.detail.sections.details

apps.detail.details.repository
ghcr.io/halvar20000/media-vault:latest
apps.detail.details.lastUpdated2026-08-11
apps.detail.details.firstSeen2026-08-10

apps.detail.sections.runtime

apps.detail.details.webui
http://[IP]:[PORT:4000]
apps.detail.details.network
bridge
apps.detail.details.shell
sh
apps.detail.details.privileged
false

apps.detail.sections.configuration

WebUI PortPorttcp

Port the app is reached on.

apps.detail.config.target
4000
apps.detail.config.default
8080
apps.detail.config.value
8080
Covers VolumePathrw

Where cached cover images are stored.

apps.detail.config.target
/app/data/covers
apps.detail.config.default
/mnt/user/appdata/media-vault/covers
apps.detail.config.value
/mnt/user/appdata/media-vault/covers
DB HostVariable

Postgres container IP or hostname.

apps.detail.config.target
POSTGRES_HOST
DB PortVariable

Postgres port.

apps.detail.config.target
POSTGRES_PORT
apps.detail.config.default
5432
apps.detail.config.value
5432
DB NameVariable

Database name.

apps.detail.config.target
POSTGRES_DB
apps.detail.config.default
mediavault
apps.detail.config.value
mediavault
DB UserVariable

Database user.

apps.detail.config.target
POSTGRES_USER
apps.detail.config.default
mediavault
apps.detail.config.value
mediavault
DB PasswordVariable

Database password.

apps.detail.config.target
POSTGRES_PASSWORD
Session SecretVariable

Long random string for signing session cookies.

apps.detail.config.target
SESSION_SECRET
App OriginVariable

Public URL of the app (e.g. http://TOWER-IP:8080).

apps.detail.config.target
APP_ORIGIN
apps.detail.config.default
http://localhost:8080
apps.detail.config.value
http://localhost:8080
Allow RegistrationVariable

Let new users self-register. Keep false on an exposed instance.

apps.detail.config.target
ALLOW_REGISTRATION
apps.detail.config.default
false
apps.detail.config.value
false
MarketplaceVariable

Which second-hand marketplace(s) the 'find deals / bundles' buttons search. Comma-separated — each value adds its own button (e.g. leboncoin,medimops,kleinanzeigen). Values: leboncoin (France), kleinanzeigen (Germany), ebay-de, ebay-com, ebay-uk, ebay-fr, marktplaats (Netherlands), wallapop (Spain), medimops (German used-media shop), or none to hide the buttons.

apps.detail.config.target
MARKETPLACE
apps.detail.config.default
leboncoin
apps.detail.config.value
leboncoin
HTTPS (production cookies)Variable

Set to 'production' ONLY behind HTTPS (reverse proxy); otherwise leave 'development'.

apps.detail.config.target
NODE_ENV
apps.detail.config.default
development
apps.detail.config.value
development
Seed user emailVariable

Default login created on first boot.

apps.detail.config.target
SEED_USER_EMAIL
apps.detail.config.default
admin@media-vault.local
apps.detail.config.value
admin@media-vault.local
Seed user passwordVariable

Change this before exposing the instance.

apps.detail.config.target
SEED_USER_PASSWORD
apps.detail.config.default
changeme
apps.detail.config.value
changeme
IGDB Client IDVariable

Twitch app Client ID (games).

apps.detail.config.target
IGDB_CLIENT_ID
IGDB Client SecretVariable

Twitch app Client Secret (games).

apps.detail.config.target
IGDB_CLIENT_SECRET
TMDB Access TokenVariable

TMDB v4 Read Access Token (movies).

apps.detail.config.target
TMDB_ACCESS_TOKEN
TMDB LanguageVariable

Movie metadata language, e.g. de-DE.

apps.detail.config.target
TMDB_LANGUAGE
apps.detail.config.default
en-US
apps.detail.config.value
en-US
Discogs KeyVariable

Discogs consumer key (vinyl/CD).

apps.detail.config.target
DISCOGS_KEY
Discogs SecretVariable

Discogs consumer secret (vinyl/CD).

apps.detail.config.target
DISCOGS_SECRET
PriceCharting TokenVariable

PAID PriceCharting API token for game valuation (optional; music valuation uses Discogs, movies have no price source).

apps.detail.config.target
PRICECHARTING_TOKEN
Valuation CurrencyVariable

Currency for Discogs marketplace prices (EUR, USD, GBP…).

apps.detail.config.target
VALUATION_CURRENCY
apps.detail.config.default
EUR
apps.detail.config.value
EUR