Author: Thorsten Schröpel · 🇩🇪 Deutsche Version
MedInv is a self-hosted, responsive web app for centrally managing physical media collections — books, CDs and DVDs/Blu-rays — across multiple independent libraries, with per-library sharing and role-based access. Scan a barcode, let MedInv fetch the metadata and cover art for you, and keep track of what you own, what it's worth, and who it's shared with.
⚠️ Beta. MedInv is under active development. Core features work and are covered by an automated test suite, but expect rough edges, and take backups before relying on it with real data. See Project status below.
The full concept/requirements document lives at docs/medinv-briefing.md (German) — it's the source of truth for behavior; this README covers running and building the implementation.
- Three dedicated media types — books, CDs, DVDs/Blu-ray — each with its own fixed, purpose-built attribute set (no generic "extra field" clutter).
- Any number of independent libraries, each scoped to one media type, with per-library sharing to individual users at guest/user/admin-equivalent access levels.
- Manual entry, bulk field updates and bulk delete across selected items.
- Camera-based barcode scanning or manual entry — either way, MedInv looks up the item automatically instead of leaving you to type everything by hand.
- A pluggable metadata-provider system with real, working providers per media type: OpenLibrary, Google Books, Hardcover, Amazon and JPC (books); MusicBrainz, Discogs, Amazon and JPC (CDs); UPCMDB, Amazon and JPC (DVD/Blu-ray) — plus optional AI-assisted lookup via Claude, ChatGPT or Gemini for all three. Results from every enabled provider are merged field-by-field rather than picking one whole record.
- Cover art is downloaded and stored locally (with a generated thumbnail), never just hot-linked.
- A "no match" is never a dead end — items can always be captured manually, and metadata can be re-fetched later from the item's detail view.
- Full-text search with genuine typo-tolerant fuzzy matching, tuned per database backend for the best available performance.
- Collection statistics: genre/language/year/publisher-artist-director distributions, and value growth over time — with automatic currency conversion so a mixed-currency library still adds up correctly.
- Three account levels (guest/user/admin) plus fine-grained, per-library sharing on top.
- Library ownership can be transferred; a user who owns libraries can't be deleted without first reassigning them.
- Optional OpenID Connect / OAuth 2.0 login (tested against Pocket ID) alongside classic email/password accounts, with configurable brute-force protection and a trusted-IP exemption range.
- Scheduled automatic backups (interval- or cron-based) plus manual, on-demand backups, with configurable retention.
- A backup taken automatically before every update that changes the database schema, so a problematic update always has a restore point.
- Full instance restore, and per-library export/import between MedInv instances — both include cover images and go through the same conflict-resolution logic (rename/merge/overwrite/skip).
- 18 bundled language packs (German, English, French, Spanish, Italian, Portuguese, Dutch, Polish, Russian, Ukrainian, Turkish, Japanese, Chinese, Norwegian, Swedish, Finnish, Icelandic) with an admin UI to add custom ones.
- Six bundled visual themes plus a custom CSS/template plugin system, switchable per instance.
MedInv was built with vibe coding: the requirements document (docs/medinv-briefing.md) was authored by a human, and the entire implementation — backend, frontend, Docker deployment, tests — was generated and iterated on with Claude Code (Anthropic) in conversation with that spec, rather than hand-written line by line. Code has been run and verified (tests, linters, live containers) at each step, and core CRUD, auth and permission handling are implemented and tested end to end — but several metadata providers are marked Beta and disabled by default (web-scraping- and LLM-based lookups in particular; see the plugin list in the admin area for details), and this should still be treated as an early-stage, AI-assisted project to review and build on, not battle-tested production software. Keep backups, and expect the occasional rough edge.
MedInv ships as a single, self-contained Docker image published to the GitHub Container Registry — no build step required:
docker run -d \
--name medinv \
-p 8080:8080 \
-e MEDINV_ADMINUSER=admin@example.com \
-e MEDINV_ADMINPASS='ChangeMe123!' \
-v medinv-storage:/var/www/backend/storage \
--restart unless-stopped \
ghcr.io/vulture20/medinv:latestThen open http://localhost:8080 and log in with the admin account you just set. That's the whole setup — MedInv uses an embedded SQLite database by default, so no extra database container is needed to get started.
The medinv-storage volume is what makes your data (database, covers, backups, the auto-generated app encryption key) survive container restarts and updates — always mount it, or everything resets on the next docker run.
:latest always points at the most recently released version (e.g. :0.8 — the same tags stay available individually if you want to pin one). Prefer bleeding-edge, unreleased fixes instead? :nightly tracks the tip of the main branch on every push, with no release testing behind it.
| Variable | Required | Default | What it does |
|---|---|---|---|
MEDINV_ADMINUSER |
✅ | — | Email address of the admin account created on first start. |
MEDINV_ADMINPASS |
✅ | — | Password for that admin account. Only used on first start; change it later via the UI. |
MEDINV_PortWeb |
8080 |
Port nginx listens on inside the container, serving both the UI and the API (under /api//sanctum) — there's deliberately no separate API port. If you change this, update both -p and the value itself to match. |
|
MEDINV_URL |
— | The public URL this instance is actually reachable at (e.g. behind a reverse proxy on a real domain). Required for logins to work from anywhere other than localhost/127.0.0.1 — without it, login fails with a generic error even with correct credentials. |
|
MEDINV_DB_CONNECTION |
sqlite |
Database backend: sqlite (default, no extra services), mariadb, or pgsql. See docker/docker-compose.yml for a ready-made multi-container setup with --profile mariadb/--profile postgres. |
|
MEDINV_LOGLEVEL |
WARNING |
Initial log verbosity (DEBUG/INFO/WARNING/ERROR); changeable later in the admin area without a restart. |
|
MEDINV_TRUSTEDIP |
— | IP or CIDR range exempt from the login brute-force throttle. | |
MEDINV_RESTOREBACKUP |
— | Filename of a backup to restore automatically on every container start — useful for demo/staging deployments that should always reset to a known state. |
This is the shortlist to get going. For the full reference — including all MEDINV_DB_* variables for MariaDB/PostgreSQL — see the bilingual docker/.env.template (works with docker compose too: copy it to docker/.env, fill it in, then cd docker && docker compose up) or docs/medinv-briefing.md chapter 16.
Just pull the new image and recreate the container (docker run with the same flags, or docker compose up -d --pull always) — pending database migrations apply automatically on start, with a safety backup taken beforehand whenever there are any.
- Backend: PHP / Laravel 13 (
backend/), REST API under/api, Sanctum SPA-cookie authentication, Eloquent as the multi-dialect database layer (SQLite / MariaDB / PostgreSQL — selectable viaMEDINV_DB_CONNECTION). - Frontend: React + TypeScript SPA (
frontend/), built with Vite,react-routerfor routing,react-i18nextfor the bundled UI languages. - Deployment: a single Docker image (
docker/Dockerfile) runs nginx + php-fpm together via supervisord, serving the built SPA and proxying/api+/sanctumto Laravel. The database backend runs as a separate container/service, never bundled into the app image.
backend/ Laravel API — see backend/app/Domain/* for the feature modules (Libraries, Metadata, Capture, Backup, Search, Statistics, Security, Mail, ExportImport)
frontend/ React SPA — see frontend/src/pages/* for one folder per sidebar section
docker/ Dockerfile, docker-compose.yml, entrypoint.sh, nginx.conf.template, supervisord.conf
docs/ docs/medinv-briefing.md — the concept document driving all of the above
Two dev servers, run separately (they talk to each other over CORS + Sanctum SPA cookies):
# Backend — http://localhost:8000 (or --port=<your port>)
cd backend
cp .env.example .env # then set MEDINV_ADMINPASS at minimum
php artisan key:generate
touch database/database.sqlite # only needed for the sqlite default
php artisan migrate
php artisan db:seed # creates the MEDINV_ADMINUSER/MEDINV_ADMINPASS account
composer install
php artisan serve
# Frontend — http://localhost:5173 (or $MEDINV_PortWeb, see vite.config.ts)
cd frontend
cp .env.example .env
npm install
npm run devBackend tests / linting:
cd backend
php artisan test # PHPUnit
./vendor/bin/pint # code style (auto-fixes; add --test to check without fixing)Frontend type-check / lint / build:
cd frontend
npx tsc -b # type-check
npx oxlint # lint
npm run build # production build (frontend/dist)Building and running the Docker image locally (instead of pulling from ghcr.io) works the same way — see docker/.env.template and docker compose up --build from within docker/.
See CHANGELOG.md for a version-by-version history of notable changes.
MedInv is licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
In plain terms: you're free to run, modify, and self-host MedInv. The one obligation AGPL adds on top of a regular GPL license is that if you run a modified version of MedInv and let other users interact with it over a network (e.g. you offer it as a hosted service to others), you must also offer those users access to your modified source code — not just people you hand a copy of the software to directly. Simply running an unmodified copy for yourself carries no extra obligation beyond the standard copyleft terms.