Skip to content

Kuzu to PostgreSQL migration

MyBibliotheca 3.0.0 uses PostgreSQL as its primary database. A fresh install starts directly on PostgreSQL. An existing installation with a Kuzu database continues using Kuzu until an administrator finishes the in-app migration wizard.

Updating the app does not migrate your library. Keep the Kuzu database unchanged throughout migration.

Update your Compose setup

Required before migrating: update your Compose setup

The 3.0.0 production Compose file is different from the Kuzu-only setup: it adds a PostgreSQL service and the postgres_data volume, and pins the app to pickles4evaaaa/mybibliotheca:3.0.0. You must use the 3.0.0 Compose file before running the migration.

  • If you installed from a Git checkout and did not replace docker-compose.yml, update the checkout; the new file will be included.
  • If you use a copied or customized Compose file, replace it with the 3.0.0 file or manually add the PostgreSQL service, app image tag, and PostgreSQL connection settings from the repository Compose file. Keep your custom ports and proxy settings. Preserve the ./data:/app/data mount.
  • Keep your existing SECRET_KEY and SECURITY_PASSWORD_SALT values. Run python3 scripts/setup_docker.py to fill any missing required settings; it preserves non-empty values. If you already have an initialized PostgreSQL data volume, restore its original POSTGRES_PASSWORD because changing .env does not change the password stored in that database.
  • Do not run docker compose down -v, remove data/, or delete the postgres_data volume. The upgrade creates postgres_data while retaining your existing Kuzu files.

3.0.0 Compose file

Use this as docker-compose.yml for the standard deployment. If you have custom ports, networks, or proxy labels, carry those settings into this file while keeping the PostgreSQL service, app image tag, and persistent data mounts.

services:
  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

  redis:
    image: redis:7.4-alpine
    command: ["redis-server", "--save", "", "--appendonly", "no", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    restart: unless-stopped

  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
  # NOTE: Do NOT bind-mount the static directory on macOS.
  # Docker Desktop's osxfs can trigger EDEADLK (Resource deadlock avoided)
    environment:
      # Security - MUST be set via .env file or environment
      SECRET_KEY: "${SECRET_KEY:?Required setting is missing. Run: python3 scripts/setup_docker.py}"
      SECURITY_PASSWORD_SALT: "${SECURITY_PASSWORD_SALT:?Required setting is missing. Run: python3 scripts/setup_docker.py}"

      # New installs initialize on PostgreSQL. Existing Kuzu installs remain
      # on Kuzu until the administrator completes the migration wizard.
      KUZU_DB_PATH: /app/data/kuzu
      GRAPH_DATABASE_ENABLED: "true"

      # Application settings
      SITE_NAME: ${SITE_NAME:-MyBibliotheca}
      TIMEZONE: ${TIMEZONE:-UTC}
      # PostgreSQL defaults to six workers; the entrypoint keeps Kuzu at one.
      WORKERS: ${WORKERS:-6}
      # Central log level (controls Gunicorn and Flask loggers)
      LOG_LEVEL: ${LOG_LEVEL}

      # Logging settings - disable verbose initialization to prevent duplicate messages
      MYBIBLIOTHECA_VERBOSE_INIT: ${MYBIBLIOTHECA_VERBOSE_INIT:-false}

      # Optional: use your own Google Books quota for metadata lookups/imports.
      # OpenLibrary remains available when this is unset.
      GOOGLE_BOOKS_API_KEY: ${GOOGLE_BOOKS_API_KEY:-}
      OPENLIBRARY_CONTACT_EMAIL: ${OPENLIBRARY_CONTACT_EMAIL:-}

      # Shared Redis cache.  The same cache backend remains enabled after the
      # PostgreSQL cutover; Kuzu query caching is transitional.
      CACHE_REDIS_URL: redis://redis:6379/1
      CACHE_REDIS_PREFIX: ${CACHE_REDIS_PREFIX:-mybibliotheca}
      KUZU_REDIS_CACHE_ENABLED: "true"
      KUZU_REDIS_CACHE_TTL_SECONDS: ${KUZU_REDIS_CACHE_TTL_SECONDS:-10}

      # Primary database for new installs and migration target for Kuzu upgrades.
      POSTGRES_DSN: ${POSTGRES_DSN:-}
      POSTGRES_HOST: ${POSTGRES_HOST:-postgres}
      POSTGRES_PORT: ${POSTGRES_PORT:-5432}
      POSTGRES_DB: ${POSTGRES_DB:-bibliotheca}
      POSTGRES_USER: ${POSTGRES_USER:-bibliotheca}
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?Required setting is missing. Run: python3 scripts/setup_docker.py}"

      # Migration settings (DISABLED for manual migration)
      AUTO_MIGRATE: "false"
      # - MIGRATION_DEFAULT_USER=admin
    restart: unless-stopped

volumes:
  postgres_data:

Prepare

  1. Schedule a maintenance window and ask users to stop making changes.
  2. Copy the complete application data/ directory to a safe location. Back up any existing PostgreSQL storage too.
  3. Update the checkout/Compose file as described above, then pull and start the new services:
git pull
docker compose pull
docker compose up -d
docker compose ps
  1. Confirm the postgres service is healthy and the app is running. Sign in as an administrator and open Admin → Database Migration.

Complete the wizard

  1. Create and verify Kuzu backup. The app pauses Kuzu access, archives the database and related application files, and checks the archive. Download the backup and keep it with your independent copy.
  2. Check PostgreSQL. The wizard verifies connectivity and inventories the source. The target must not contain application data. If it is not empty, configure an empty target; do not truncate it manually.
  3. Migrate and verify. Start the migration during the maintenance window. The app copies the graph and relational data and checks both. Leave the page open while the migration runs.
  4. Confirm cutover. When verification passes, the app activates PostgreSQL. Confirm the wizard reports PostgreSQL as active. If running outside the supplied Gunicorn container, restart the application when prompted.
  5. Check the library. Verify user accounts, books, reading history, covers, and integrations before normal use resumes.

If a check fails, follow the blocker reported by the wizard. Kuzu remains active until a verified cutover. Do not edit the persistence state files by hand.

After migration

Keep the original Kuzu files and backups until you are satisfied with the PostgreSQL library. PostgreSQL becomes authoritative after cutover. The old Kuzu copy will not receive later writes, so use a verified PostgreSQL backup for recovery after normal use resumes.

Keep backing up both the PostgreSQL volume and data/. See Backups & restore and Install & upgrade.