Self-hosted home library catalog — barcode & shelf-photo scanning, metadata, lending, offline PWA
6.6K
A self-hosted home library catalog — scan barcodes or photograph whole shelves, and Shelf fetches metadata and cover art, tracks lending, series and reading, and works offline in a bookstore. Single Docker container, SQLite, no cloud.
Photo Intake: snap a shelf, AI reads the spines, books land in your library with covers and metadata.
mkdir -p shelf-data
docker run -d \
--name shelf \
-p 18888:18888 \
-v ./shelf-data:/data:z \
dangahagan/shelf:latest
Open https://localhost:18888 and create your admin account via the setup wizard. That's it.
Note: Shelf uses HTTPS with a self-signed certificate generated on first run. Your browser will show a certificate warning — this is expected. Click through to proceed.
Create a docker-compose.yml:
services:
shelf:
image: dangahagan/shelf:latest
container_name: shelf
ports:
- "18888:18888"
environment:
- CERT_SAN=${CERT_SAN:-DNS:shelf,DNS:localhost}
volumes:
- ./data:/data:z
restart: unless-stopped
Then run:
docker compose up -d
Create a .env file in the same directory as your docker-compose.yml:
# Add your machine's hostname or IP so other devices on your network
# can access Shelf without certificate warnings
CERT_SAN=IP:192.168.1.100,DNS:shelf,DNS:localhost
| Variable | Default | Description |
|---|---|---|
CERT_SAN | DNS:shelf,DNS:localhost | TLS certificate Subject Alternative Names. Add your machine's IP or hostname so other devices can connect |
SECRET_KEY | (auto-generated) | JWT signing key. If unset, generated at data/signing.key (0600) on first start; an existing key from before 0.30 is moved there from the database on the first start after upgrading, so sessions survive. Set it explicitly to run several instances against one database |
SHELF_ENCRYPTION_KEY | (auto-generated) | Encryption key for stored API credentials. Auto-generated at /data/encryption.key if not set. Set it explicitly (e.g. openssl rand -hex 32) so the data directory alone can't decrypt credentials |
SHELF_TRUST_PROXY | (unset) | Set to 1 when running behind a reverse proxy so client IPs are read from proxy headers |
All data is stored in a single volume mounted at /data:
data/
shelf.db — SQLite database (your entire catalog)
covers/ — cached cover images
certs/ — auto-generated TLS certificates
encryption.key — key for API credentials stored in the DB
(unless SHELF_ENCRYPTION_KEY is set)
signing.key — signs login sessions (unless SECRET_KEY is set)
Keep both key files out of anything you share. The database itself holds no key material.
Backups: Copy the data/ directory, or use the built-in backup/restore feature in Settings — with an optional passphrase, backup downloads are AES-encrypted and safe to store off-site.
| Browse | Scan (Add Mode) |
|---|---|
![]() | ![]() |
| Scan (Lend Mode) | Item Detail |
|---|---|
![]() | ![]() |
| Stats | Admin Logs |
|---|---|
![]() | ![]() |
| Valuation Report | Browse (Tag Filter) |
|---|---|
![]() | ![]() |
| Mode | What it does |
|---|---|
| Add | Scan barcodes to add items with full metadata lookup |
| Wishlist | Scan at a bookstore to save items you want |
| Lend | Select a borrower, then scan items to check them out |
| Return | Scan items to check them back in |
| Move | Select a target location, then batch-scan items to relocate them |
| Inventory | Select a location, scan everything there, then check for missing items. Counts physical copies, so a book with copies in two rooms is expected in both; a scan that cannot say which copy reports instead of moving one |
| Lookup | Scan to check if an item is in your collection — no changes made |
| Quick Rate | Scan to mark items as read/completed |
signed, first-edition, …) with a tag filter on BrowseShelf works fully out of the box with no API keys. These optional integrations add extra features — configure them in the Settings page after setup:
| Service | What it adds | Free? |
|---|---|---|
| Hardcover | Reading status sync, richer metadata, series gap checks, Discover page | Yes |
| Audiobookshelf | Sync selected audiobook libraries, link physical + digital formats | Yes |
| IGDB (Twitch) | Video game metadata, cover art, platform info — on UPC scan, title search, and Photo Intake confirm | Yes |
| RomM | Sync a self-hosted RomM server's digital game library | Yes |
| Komga | Sync a self-hosted Komga server's digital comics and manga | Yes |
| TMDb | DVD/Blu-ray metadata — from UPC barcodes, title search, and Photo Intake confirm | Yes |
| ISBNdb | Collection valuation with market prices | Paid |
| Anthropic | Photo Intake spine recognition (best accuracy) | Pay-per-use |
| OpenAI-compatible | Photo Intake via any OpenAI Chat Completions endpoint (OpenAI, OpenRouter, vLLM, LM Studio…) | Pay-per-use / free |
| Ollama | Photo Intake with a fully local vision model | Free |
docker compose pull
docker compose up -d
Or with docker run:
docker pull dangahagan/shelf:latest
docker stop shelf && docker rm shelf
docker run -d --name shelf -p 18888:18888 -v ./shelf-data:/data:z dangahagan/shelf:latest
Your data in the /data volume is preserved across updates.
| Tag | Description |
|---|---|
latest | Latest stable release |
beta | Latest beta — may have rough edges |
x.y | Latest patch of a minor line (e.g., 0.10) |
x.y.z | Specific version (e.g., 0.10.1) |
| Layer | Technology |
|---|---|
| Backend | Python 3.12, FastAPI, SQLite (WAL mode) |
| Frontend | Jinja2, HTMX, Alpine.js, Tailwind CSS |
| Auth | bcrypt, JWT in HTTP-only secure cookies |
| Container | Non-root user, self-signed HTTPS |
Content type
Image
Digest
sha256:409743ede…
Size
68.9 MB
Last updated
about 2 hours ago
docker pull dangahagan/shelf