Skip to content

Deployment guide

How to run Bibliogon in production. Operator-side reference covering Docker Compose, environment variables, persistence, and common troubleshooting. For end-user installation use the installer scripts; this page is for the underlying setup.

What runs in production

Bibliogon ships as two Docker containers behind one port:

  • backend — Python + FastAPI + SQLAlchemy + SQLite. Runs Uvicorn with 2 workers. Health endpoint at /api/health.
  • frontend — Vite-built static files served by nginx. Proxies /api/* to the backend on the internal Docker network.

The frontend container exposes port 7880 by default (override with BIBLIOGON_PORT). The backend container is internal only — you should not expose port 8000 to the public internet.

The compose file is docker-compose.prod.yml. Source: https://github.com/astrapi69/bibliogon/blob/main/docker-compose.prod.yml.

Quickstart

git clone https://github.com/astrapi69/bibliogon.git
cd bibliogon
./start.sh

start.sh generates a .env file with secrets if it doesn't exist, then runs docker compose -f docker-compose.prod.yml up -d. Open http://localhost:7880. Stop with ./stop.sh.

Environment variables

Variable Default Purpose
BIBLIOGON_PORT 7880 Host port the frontend binds to. Change when 7880 is taken or when you front Bibliogon with a reverse proxy on a different port.
BIBLIOGON_DEBUG false When true, enables /api/test/reset and the API docs at /api/docs and includes stacktraces in 5xx responses. Do not enable in production.
BIBLIOGON_SECRET_KEY (auto-generated by start.sh) Used for license signing and CSRF protection. The startup script writes a random value to .env if not set.
BIBLIOGON_CREDENTIALS_SECRET (auto-generated by start.sh) Fernet-encrypts API keys + service-account files at rest in the DB. The same auto-generation applies.
BIBLIOGON_CORS_ORIGINS http://localhost:7880 Comma-separated list of allowed origins. Add your reverse-proxy hostname here when deploying behind a domain.
BIBLIOGON_DATA_DIR /app/data (in container) Container-side root for runtime data: SQLite DB at <dir>/bibliogon.db and uploads at <dir>/uploads/. Mapped to a Docker named volume for persistence.
BIBLIOGON_DB_PATH (no longer honoured) Removed in v0.30.0 (DEP-DBPATH-01 step 3). The variable has no effect on path resolution; if still set in the environment, a single warning is logged at startup naming the ignored value. Set BIBLIOGON_DATA_DIR instead — the database resolves to <BIBLIOGON_DATA_DIR>/bibliogon.db. Deprecation timeline: warning v0.27.0, precedence flip v0.28.0, removal v0.30.0.

Every variable that has a default is optional. The startup script auto-generates the two secrets if they are not already in .env, so a fresh ./start.sh works without any setup.

Persistence

Production data lives in a named Docker volume, bibliogon-data, mounted at /app/data inside the backend container.

  • SQLite DB: /app/data/bibliogon.db (+ -wal, -shm).
  • Uploads (cover images, asset files): /app/data/uploads/.
  • Audiobook persistence (post-export MP3s): /app/data/uploads/{book_id}/audiobook/.

The volume survives container rebuilds. To verify it exists:

docker volume ls | grep bibliogon

To inspect contents (ephemeral container):

docker run --rm -v bibliogon-data:/data alpine ls -la /data

To back up the volume:

docker run --rm -v bibliogon-data:/data -v "$PWD":/backup alpine tar czf /backup/bibliogon-data.tar.gz -C /data .

To restore:

docker run --rm -v bibliogon-data:/data -v "$PWD":/backup alpine tar xzf /backup/bibliogon-data.tar.gz -C /data

The .bgb backup format the Bibliogon UI uses is a different, app-level concern — that's a per-book ZIP that survives Docker volume loss. The volume backup above covers everything: every book, every asset, every audiobook MP3, plus state like installed plugins.

Stop / restart / uninstall

./stop.sh                                                # stop containers
./start.sh                                               # start (or restart) containers
docker compose -f docker-compose.prod.yml restart        # restart without rebuild
docker compose -f docker-compose.prod.yml down -v        # stop + DELETE volume (uninstalls Bibliogon and ALL data)

The -v flag removes the named volume. Without it, the data persists across docker compose down and survives image rebuilds. Use -v only when you intend to delete all books, assets, and audiobook MP3s.

Logs

docker compose -f docker-compose.prod.yml logs -f                # all services
docker compose -f docker-compose.prod.yml logs -f backend        # backend only
docker compose -f docker-compose.prod.yml logs -f --tail=100     # last 100 lines, follow

The backend logs every API request (Uvicorn access log) and every plugin lifecycle event. Errors at WARNING/ERROR level include structured context (book_id, plugin name, etc.).

Reverse proxy

If you put Bibliogon behind nginx / Caddy / Traefik / Apache, two things to remember:

  1. Forward /api/* to the same upstream — the frontend container already proxies /api/* internally, so you can either point your reverse proxy at port 7880 (treats Bibliogon as a single black box) or split it (route / to frontend container, /api/* to backend container directly). The single-port approach is simpler and matches the default deployment.
  2. Add your hostname to BIBLIOGON_CORS_ORIGINS. Without this, browsers block the frontend from reaching the backend with a CORS error. Comma-separated list, e.g. BIBLIOGON_CORS_ORIGINS=https://books.example.com,https://localhost:7880.

For HTTPS termination, do it at the reverse proxy. Bibliogon's containers do not handle TLS.

Backups

Two complementary mechanisms:

  • App-level .bgb per book — exported through the Bibliogon UI (Settings → Backup). Covers a single book, its chapters, its assets, optionally its audiobook MP3s. Portable across Bibliogon instances.
  • Volume-level backup — covers the entire Bibliogon state including every book + plugin state + installed plugins. The tar command above. Suitable for nightly cron jobs.

The optional git-sync plugin can also push each book to a separate git repo, giving you per-book version control + an off-site backup target. See the Git Backup help page.

Updates

To update to a new Bibliogon release:

cd ~/bibliogon
git pull origin main
git checkout vX.Y.Z          # the new release tag
./stop.sh
./start.sh                   # rebuilds the images at the new tag

The launcher binary (Windows Launcher, macOS Launcher, Linux Launcher) wraps this lifecycle in a tray-icon UI with auto-update detection. For server deployments, the explicit shell flow above is more predictable.

The lock-step versioning model means there is never a partial upgrade — backend, frontend, and every plugin always ship at the same version. Pull a tag, restart, done.

Data migrations

Bibliogon uses Alembic for schema migrations. The FastAPI lifespan runs alembic upgrade head on startup, so a fresh ./start.sh after pulling a new tag applies any pending migrations. The migrations are idempotent and forward-only.

For the v0.25.0+ filesystem migration (data moved from the project tree to platformdirs / BIBLIOGON_DATA_DIR), the FastAPI lifespan auto-migrates legacy paths on first start and writes a .migrated-YYYY-MM-DD breadcrumb at each old location. Confirm the move before deleting the old files manually.

Troubleshooting

Containers do not start

docker compose -f docker-compose.prod.yml logs backend  | tail -30
docker compose -f docker-compose.prod.yml logs frontend | tail -30

The most common causes are:

  • Port 7880 already taken by another service. Set BIBLIOGON_PORT=7881 (or another free port) in .env.
  • BIBLIOGON_SECRET_KEY missingstart.sh should generate one; if it failed silently, check that .env has both BIBLIOGON_SECRET_KEY= and BIBLIOGON_CREDENTIALS_SECRET= lines with non-empty values.
  • Disk full — the bibliogon-data volume needs space for the SQLite DB and uploads. df -h and docker system df both worth checking.

Backend health check fails

The /api/health endpoint returns 200 when the DB is reachable and the plugin manager has loaded successfully. If health stays unhealthy after 5+ minutes:

docker compose -f docker-compose.prod.yml exec backend python -c "from app.database import engine; print(engine.url)"
docker compose -f docker-compose.prod.yml exec backend ls -la /app/data/

If /app/data/bibliogon.db exists but the DB query fails, the SQLite file may be corrupted. Stop, restore from the most recent volume backup, restart.

Plugin not loading

docker compose -f docker-compose.prod.yml logs backend | grep -i plugin

Plugin discovery uses importlib.metadata.entry_points(). A plugin that fails to import is logged at ERROR with the import error, then skipped. Other plugins continue to load. Common cause: missing dependency in the plugin's pyproject.toml that wasn't bundled in the container.

CORS errors in the browser console

Add the hostname to BIBLIOGON_CORS_ORIGINS. The default http://localhost:7880 only allows the local-machine front-end. Behind a reverse proxy at books.example.com, set BIBLIOGON_CORS_ORIGINS=https://books.example.com.

Last verified for v0.29.0 (2026-05-07).