All apps · 0 apps
media-vault
Docker app from halvar100's Repository
Overview
Readme
View on GitHubmedia-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 & series, Discogs or MusicBrainz for vinyl/CD) and unified into one browsable spine shelf.
📖 New here? Follow the Installation & Setup Guide — install the app + database, get the free artwork keys, and add them in the app, step by step.
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 eBay used-listing prices (free API key) — or PriceCharting if you have a paid token — 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 |
| Vinyl / Singles / CD (fallback) | MusicBrainz + Cover Art Archive | no key needed |
Enrichment stays idle for any source whose keys are blank — the app still runs. Music is the exception: without Discogs credentials it falls back to MusicBrainz, which needs no key (Discogs still adds liner notes and marketplace valuation when configured).
Every matched item also has a Choose cover… picker: browse every alternate the source knows — TMDB posters per language (and per season for series boxes), IGDB regional box art, Discogs scans of the release and its other pressings, Cover Art Archive fronts — and pick the one that matches the disc on your shelf.
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.ymlabove.
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).
API keys (IGDB, TMDB, Discogs, eBay, PriceCharting) can be added in-app via the ⚙ Settings panel — no env editing or restart. They're stored on your server, shown masked, and override the env defaults below. IGDB unlocks game covers + the catalogue, TMDB movies, Discogs music.
Shops can be configured in-app via the ⚙ Settings panel — toggle the built-in shops, set
an Easy Cash store, and add your own custom shops by search-URL (use {query} as the placeholder,
e.g. https://shop.xy/search?q={query}). Changes save to the database and apply instantly. The
env vars below are just the initial defaults used until you save settings in the app.
MARKETPLACE picks the initial second-hand marketplace(s) for the find deals / bundles
buttons, 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),
easycash (French used-goods shop), gamestop (US game shop), craigslist (US classifieds —
set CRAIGSLIST_SITE to your city, e.g. newyork), or none to hide the buttons.
Releasing (versioned images)
Images are tagged by version. Pushing a vX.Y.Z git tag builds and publishes
ghcr.io/halvar20000/media-vault:X.Y.Z, :X.Y, and :latest. The running version
is shown in the app header and at GET /api/health.
To cut a release:
# bump the version in backend/package.json + frontend/package.json to X.Y.Z, commit, then:
git tag -a vX.Y.Z -m "vX.Y.Z"
git push origin vX.Y.Z
Every push to main still publishes :latest, so Unraid's "check for updates" keeps working.
Pin a specific version by setting the container's repository to …/media-vault:X.Y.Z.
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
docs/INSTALL.md— installation & setup guide (start here).docs/CSV_IMPORT.md— the universal CSV format (incl. an AI prompt to fill it from photos).CHANGELOG.md— version history (also viewable in-app via the version badge).PROJECT_BRIEF.md— founding spec.CHAT_SUMMARY.md— design reasoning.media-vault-prototype.html— original design prototype.
Feedback and ideas
One shelf in one household is a narrow test. If your collection has a media type, a region, a format or a source this does not handle, that is exactly what is worth hearing.
- Crash, missing artwork, wrong match? Open a bug report — version (click the badge in the header), media type, and the exact title.
- Want something? Say so in Discussions → Ideas, or 👍 an idea already there. The roadmap thread lists what is being considered, and the votes decide the order.
- Setup or API-key trouble? Discussions → Q&A.
- Proud of your shelf? Show and tell is there for it.
License
AGPL-3.0 — anyone who offers this as a network service must share their changes back to the community.
Install media-vault on Unraid in a few clicks.
Find media-vault in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Requirements
Step-by-step setup guide: https://github.com/halvar20000/media-vault/blob/main/docs/INSTALL.md
Related apps
Explore more like this
Explore allDetails
ghcr.io/halvar20000/media-vault:latestRuntime arguments
- Web UI
http://[IP]:[PORT:4000]- Network
bridge- Shell
sh- Privileged
- false
Template configuration
Port the app is reached on.
- Target
- 4000
- Default
- 8080
- Value
- 8080
Where cached cover images are stored.
- Target
- /app/data/covers
- Default
- /mnt/user/appdata/media-vault/covers
- Value
- /mnt/user/appdata/media-vault/covers
Postgres container IP or hostname.
- Target
- POSTGRES_HOST
Postgres port.
- Target
- POSTGRES_PORT
- Default
- 5432
- Value
- 5432
Database name.
- Target
- POSTGRES_DB
- Default
- mediavault
- Value
- mediavault
Database user.
- Target
- POSTGRES_USER
- Default
- mediavault
- Value
- mediavault
Database password.
- Target
- POSTGRES_PASSWORD
Leave BLANK — one is auto-generated and saved on first boot. Only set this if you want to supply your own long random string (e.g. from: openssl rand -base64 32).
- Target
- SESSION_SECRET
Public URL of the app (e.g. http://TOWER-IP:8080).
- Target
- APP_ORIGIN
- Default
- http://localhost:8080
- Value
- http://localhost:8080
Let new users self-register. Keep false on an exposed instance.
- Target
- ALLOW_REGISTRATION
- Default
- false
- Value
- false
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), easycash (French used-goods shop), or none to hide the buttons.
- Target
- MARKETPLACE
- Default
- leboncoin
- Value
- leboncoin
Optional: pin Easy Cash 'find deal' links to one physical store. Use the store's exact facet value (department + name), e.g. '68 - Mulhouse'. Blank = all stores / full online catalogue.
- Target
- EASYCASH_STORE
US only: Craigslist is per-city. Set your site subdomain, e.g. newyork, sfbay, losangeles. Required for Craigslist links (no national search). Blank = Craigslist hidden. (Also settable in the app's Settings panel.)
- Target
- CRAIGSLIST_SITE
Set to 'production' ONLY behind HTTPS (reverse proxy); otherwise leave 'development'.
- Target
- NODE_ENV
- Default
- development
- Value
- development
The admin account created on first boot — this is the email you log in with. Change it or keep the default.
- Target
- SEED_USER_EMAIL
- Default
- admin@media-vault.local
- Value
- admin@media-vault.local
Password for the login above (created on first boot). CHANGE THIS from 'changeme', especially if the instance is reachable outside your LAN.
- Target
- SEED_USER_PASSWORD
- Default
- changeme
- Value
- changeme
Twitch app Client ID (games).
- Target
- IGDB_CLIENT_ID
Twitch app Client Secret (games).
- Target
- IGDB_CLIENT_SECRET
TMDB v4 Read Access Token (movies).
- Target
- TMDB_ACCESS_TOKEN
Movie metadata language, e.g. de-DE.
- Target
- TMDB_LANGUAGE
- Default
- en-US
- Value
- en-US
Discogs consumer key (vinyl/CD).
- Target
- DISCOGS_KEY
Discogs consumer secret (vinyl/CD).
- Target
- DISCOGS_SECRET
PAID PriceCharting API token for game valuation (optional; eBay below is a free alternative). Music valuation uses Discogs; movies have no price source.
- Target
- PRICECHARTING_TOKEN
FREE game valuation via eBay. Register an app at developer.ebay.com (Production keyset) and paste the App ID here. When set, games are valued from eBay used-listing prices (takes priority over PriceCharting).
- Target
- EBAY_CLIENT_ID
eBay Cert ID (Client Secret) that pairs with the App ID above.
- Target
- EBAY_CLIENT_SECRET
Which eBay site/currency to price against: EBAY_DE (EUR), EBAY_FR (EUR), EBAY_GB (GBP), EBAY_US (USD), EBAY_IT, EBAY_ES…
- Target
- EBAY_MARKETPLACE_ID
- Default
- EBAY_DE
- Value
- EBAY_DE
Optional: import your owned Steam games via Add → Steam. Get a free key at https://steamcommunity.com/dev/apikey. Your Steam profile's Game details must be Public. Can also be set in the app's Settings.
- Target
- STEAM_API_KEY
Currency for Discogs marketplace prices (EUR, USD, GBP…).
- Target
- VALUATION_CURRENCY
- Default
- EUR
- Value
- EUR