Self-hosted service
pickles4evaaaa/mybibliotheca avatar
pickles4evaaaa/mybibliotheca

MyBibliotheca moved off its embedded database in version 3, and the compose file is the part to read twice

Track your reading habits, log books, and visualize your library—all in your own space.

594 stars42 forksPythonMIT

At a glance

What is it?
pickles4evaaaa/mybibliotheca is a self-hosted library manager and reading tracker built on Flask, with Goodreads and StoryGraph imports, OCR and barcode scanning for covers, and version 3.0.0 replacing the embedded Kuzu database with PostgreSQL behind an in-app migration wizard. It is MIT licensed, and its Docker Compose stack refuses to start without a generated secret key and a database password.
Who is it for?
Use MyBibliotheca if you want a private reading tracker with book metadata, sessions, ratings and import from Goodreads or StoryGraph, and you are comfortable running a small Docker Compose stack. Do not adopt it without reading the 3.0.0 migration notes, because an existing Kuzu installation has to be moved by an in-app wizard before it will run on PostgreSQL, and the stack refuses to start unless the secrets are generated first.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A reading tracker whose data stays on your own hardware

MyBibliotheca is a self-hosted library manager and reading tracker. The description in the project overview is plain: organise books, track reading progress, record reading sessions, and keep your collection on infrastructure you control.

That is a small feature list for what is really a fairly complete reading database, and the extra pieces are what make it a library manager rather than a bookmark list. There is book and contributor metadata with ISBN lookup and cover fetching, so the app can fill in the bibliographic record instead of asking you to type it. There are imports from Goodreads, from StoryGraph, from reading history exports, and from custom CSV files, which is the difference between a tracker you can bring your history into and one you start from empty.

Then there is the reading record itself: status, progress, sessions, ratings, reviews and statistics. Sessions are the interesting unit, because a log of when you read what is what lets the statistics section answer questions about your habits rather than just your shelf.

Two features deserve emphasis for a self-hosted tool. Multiple user accounts exist, and personal reading data is private, so a household or a reading group can share one instance without sharing each other's history. And the admin side is a real admin side, with tools, backups, restore and data export, which is what separates a self-hosted application from a hobby script.

The licence is MIT, the primary language is Python, and the documentation site at docs.mybibliotheca.org is described as the source of truth for installing, configuring, upgrading and maintaining the application.

Version 3 replaces Kuzu with PostgreSQL, and existing users have to migrate

The release heading is the first thing the README explains, and it is the thing every existing installation has to think about.

Version 3.0.0 uses PostgreSQL as its primary database. New installations use PostgreSQL automatically. Existing Kuzu installations need to be migrated with the in-app database migration wizard.

That is a significant change in the shape of the application. The previous version stored data in Kuzu, which is an embedded graph database, and the dependency list still carries kuzu 0.11.3 alongside the new psycopg client, so both are present in the codebase during the transition.

The reasons to move are not documented in the visible text, and the choice is defensible either way. An embedded database is simpler to back up and needs no second service, while PostgreSQL is better at concurrent writes, at tooling, and at being something an administrator already knows how to operate. The cost of the move is the migration, and the project has chosen to make that migration a guided in-application step rather than a manual database conversion.

The compose file is where the transition is visible in practice. The app service mounts a local data directory at /app/data, and the comment says the mount is there for migration support, the Kuzu database, and backups. So an upgrade path involves the old Kuzu files being present on disk while the new database is populated, and the wizard is what moves the data across.

The documentation site carries a dedicated migration page at the stable path for migrating from Kuzu to PostgreSQL, along with the install guide and the backups and restore guide. The README points there rather than duplicating the procedure, which is the right call for something with this many moving parts.

The Compose stack is postgres, redis and the app, with health checks as the glue

The deployment is a Docker Compose file with three services, and it is the most useful piece of documentation in the repository because it shows the intended production shape.

The database service runs postgres:17-alpine with a named volume for the data directory, PGDATA pointed inside the volume, a health check, and a restart policy of unless-stopped:

yaml
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-bibliotheca}
      POSTGRES_USER: ${POSTGRES_USER:-bibliotheca}
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?Required setting is missing. Run: python3 scripts/setup_docker.py}"
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 20s
    restart: unless-stopped

The cache service is redis:7.4-alpine, and its command line is tuned for a cache rather than a store, with saving disabled, appendonly off, a 256 megabyte ceiling and the allkeys-lru eviction policy. That configuration is the tell: this Redis is a cache that is allowed to forget, which is exactly what you want for sessions and computed views and exactly what you do not want for data.

The application service is where the rest of the details live:

yaml
  bibliotheca:
    image: pickles4evaaaa/mybibliotheca:3.0.0
    ports:
      - "5054:5054"
    depends_on:
      redis:
        condition: service_healthy
      postgres:
        condition: service_healthy
    volumes:
      # Mount local data directory for migration support, Kuzu database, and backups
      - ./data:/app/data

The depends_on conditions are set to service_healthy rather than service_started, so the application waits for both dependencies to report healthy rather than merely existing. That is the difference between a stack that comes up reliably and one that occasionally fails on a first run because the database is not accepting connections yet.

Port 5054 is the application port, and the image tag is pinned to 3.0.0 to match the release, which is the practice you should copy when you fork the compose file.

The stack will not start until you generate the secrets

The compose file fails loudly when a required setting is missing, and the error message tells you exactly what to run.

Three settings use the shell parameter expansion form that makes the substitution mandatory, and all three point at the same setup script. POSTGRES_PASSWORD, SECRET_KEY and SECURITY_PASSWORD_SALT are each written as a required value with the message Required setting is missing. Run: python3 scripts/setup_docker.py.

The distinction between SECRET_KEY and SECURITY_PASSWORD_SALT is a Flask one. The secret key signs sessions and other signed values, and SECURITY_PASSWORD_SALT is what the password hashing scheme is salted with, so that two users with the same password do not produce the same hash. Both have to be present, both have to be long, and both have to be stable across restarts, because changing the session secret logs everyone out and changing the salt invalidates every stored password hash.

That stability requirement is the operational trap. A generated secret that is regenerated on every deployment, for example by a script that runs on each start, will lock out your users and invalidate their passwords. The script is in scripts/, and the repository also carries a .env.example, so the intended flow is to run the setup once, keep the resulting .env out of version control, and reuse it.

There is a second sharp edge in the same file, and it is a platform specific one. A comment notes that you should not bind mount the static directory on macOS, because Docker Desktop's osxfs can trigger EDEADLK, a resource deadlock avoided error. The application is served by gunicorn, and the image is built on python:3.13-slim, so the deployment is a conventional WSGI container with a static asset directory that the compose file deliberately does not mount on the developer's own machine.

OCR and barcode scanning are in the image, not optional extras

The dependency list is unusual for a reading tracker, and the reason is the cover handling.

Alongside the expected web stack of Flask, Flask-Login, Flask-WTF, Flask-Session, WTForms, Werkzeug, requests and python-dotenv, there is Pillow for image handling, and a block of OCR and barcode detection dependencies: opencv-python, pyzbar and pytesseract with numpy. The requirements file groups them under a comment labelled OCR and Barcode Detection Dependencies.

The corresponding system packages are installed in the Dockerfile, including tesseract-ocr, tesseract-ocr-eng, libtesseract-dev, libzbar0 and libzbar-dev, alongside the graphics libraries OpenCV needs such as libgl1, libglib2.0-0 and libgtk-3-0. The base image is python:3.13-slim with byte code writing disabled and unbuffered logging enabled.

So the project can read a barcode from a photo of a book and look the title up, which is the sensible way to add a physical book to a digital shelf, and it can run OCR over a cover image. Both need system packages, which is why the image is not as slim as a pure Python application image would be.

One more environment detail in the Dockerfile is worth flagging if you hit problems. It sets OPENSSL_CONF to its own configuration file and enables OPENSSL_ENABLE_SHA1_SIGNATURES, and the file is edited to define a provider section with a default provider. The image is configured to accept SHA-1 signatures, which is legacy compatibility, and SHA-1 based signing is what some older systems still use. It is a deliberate compatibility choice made in the base image rather than something you have to configure.

Other security libraries are present too, with cryptography and scrypt for hashing and cryptography work, and psutil for process and system utilities.

The repository is a Flask application with a lot of operational scripts around it

The top level listing shows what kind of project this is, and it is an application with a long tail of maintenance tooling.

The application code is in app/, with config.py, run.py and templates rendered through pystache, the mustache style templating library, plus mistune for Markdown support in personal notes. There is a prompts/ directory, which is where the prompt templates for the AI service live, and the dependencies list a pystache entry under a comment calling it the AI service dependency.

The operational scripts are the interesting part. There is docker-entrypoint.sh and migrate.sh for container startup and migration, force_schema_init.py for the case where the schema needs to be created explicitly, network_data_fix.py, and restore_covers.py. A regression_checks/ directory sits next to tests/, with pytest.ini at the root and pytest, pytest-flask and pytest-cov in the dependencies, so there is a real test suite and a separate set of regression checks for the parts that are hard to fix in place.

The scripts/ directory holds the setup helper the compose file points at, and docs/ holds the documentation sources. There is a docker-compose.dev.yml alongside the production compose file, a uv.lock at the root, which shows uv is used for dependency resolution even though requirements.txt is also committed, and a .python-version file pinning the interpreter, matching the requires-python of 3.13 or newer in the pyproject.

The CHANGELOG.md is present at the root, and the release history is quick: 3.0.0 on 2026-10-20, 2.2.1 on 2026-09-14 and 2.2.0 on 2026-08-28. So the database change of version 3 came after two point releases on the old database, and the last push was on 2026-10-20, the same day as the 3.0.0 tag.

Editorial conclusion

Use MyBibliotheca if you want a private reading tracker with book metadata, sessions, ratings and import from Goodreads or StoryGraph, and you are comfortable running a small Docker Compose stack. Do not adopt it without reading the 3.0.0 migration notes, because an existing Kuzu installation has to be moved by an in-app wizard before it will run on PostgreSQL, and the stack refuses to start unless the secrets are generated first. Before you deploy, run python3 scripts/setup_docker.py, confirm the image tag matches your version, and take a backup, because the restore path and the migration are both part of the upgrade rather than an afterthought.

Frequently asked questions

What is MyBibliotheca?

It is a self-hosted library manager and reading tracker for organising books, tracking reading progress and recording reading sessions on infrastructure you control. It includes book and contributor metadata with ISBN lookup, cover fetching, reading statistics, multiple user accounts with private data, and admin tools for backups, restore and export.

What changed in MyBibliotheca 3.0.0?

Version 3.0.0 uses PostgreSQL as its primary database, and new installations use it automatically. Existing Kuzu installations have to be migrated with the in-app database migration wizard, which the documentation covers on a dedicated migration page.

How do I deploy MyBibliotheca?

With Docker Compose, using the supplied compose file. It runs a PostgreSQL service, a Redis cache and the application image pickles4evaaaa/mybibliotheca:3.0.0, publishes port 5054, and waits for both dependencies to pass their health checks before starting the app.

Why will the MyBibliotheca containers fail to start?

Because POSTGRES_PASSWORD, SECRET_KEY and SECURITY_PASSWORD_SALT are required settings in the compose file, and the error message tells you to run python3 scripts/setup_docker.py. The values must stay stable across restarts, since changing the session secret logs users out and changing the password salt invalidates stored password hashes.

Can MyBibliotheca import my existing reading history?

Yes. The project lists imports from Goodreads, from StoryGraph, from reading history exports and from custom CSV files, alongside ISBN lookup and cover fetching for adding individual books.

Does MyBibliotheca need external services to scan book covers?

It ships the tooling in its image, including tesseract-ocr with the English language data and the zbar library for barcodes, together with opencv-python, pyzbar, pytesseract and Pillow in the Python dependencies.

Official sources

  1. Issues
  2. License: MIT
  3. pickles4evaaaa/mybibliotheca on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pickles4evaaaa-mybibliotheca.svg)](https://hysenlabs.com/projects/pickles4evaaaa-mybibliotheca)

Was this useful?