Kavita Book Server: Host Your Own Ebook, PDF, and Manga Library with Docker

Deploy Kavita digital library using Docker Compose. Read ebooks, PDFs, and manga directly through a modern web interface.

Kavita Book Server: Host Your Own Ebook, PDF, and Manga Library with Docker

Folder Organization and Permissions

Deploying Kavita successfully requires establishing a strict directory structure. Kavita relies on predictable folder hierarchies to automatically group series, parse volumes/chapters, and extract metadata.

Prepare a dedicated workspace directory on the VPS (typically /opt/kavita):

mkdir -p /opt/kavita/{config,data/{books,manga,comics}}

This yields the following directory structure:

/opt/kavita/
├── docker-compose.yml
├── config/
└── data/
    ├── books/
    ├── manga/
    └── comics/

Library Folder Hierarchy Conventions

To ensure Kavita correctly groups media without manual intervention, organize files according to their specific type before importing:

  • Ebooks (EPUB, PDF): data/books/ └── Author Name/ ├── Series Name/ │ ├── Book 1 - Title.epub │ └── Book 2 - Title.epub └── Standalone Book.epub
  • Manga & Comics (CBZ, CBR): data/manga/ └── Series Name/ ├── Series Name - v01.cbz ├── Series Name - v02.cbz └── Specials/ └── Series Name - Special 01.cbz

Permissions Configuration

Ensure the host user running Docker (typically UID 1000) has read/write permissions for both the configuration and library folders:

chown -R 1000:1000 /opt/kavita
chmod -R 775 /opt/kavita

Docker Compose Configuration

The following docker-compose.yml configures Kavita to run as a non-root container, binds host storage volumes, and exposes the required port.

Save this file directly to /opt/kavita/docker-compose.yml:

version: '3.8'

services:
  kavita:
    image: kavitareader/kavita:latest
    container_name: kavita
    restart: unless-stopped
    ports:
      - "5000:5000"
    environment:
      - TZ=Etc/UTC
      - PUID=1000
      - PGID=1000
    volumes:
      - ./config:/kavita/config
      - ./data/books:/books:ro
      - ./data/manga:/manga:ro
      - ./data/comics:/comics:ro

Configuration Details

  • Image: Uses the official kavitareader/kavita:latest image.
  • Ports: Map internal container port 5000 to the host port 5000. If port 5000 is already in use on the host, modify the left side of the colon (e.g., 8080:5000).
  • Volumes:
    • ./config: Binds the local config/ directory to /kavita/config. This retains user databases, covers, configuration preferences, and system logs.
    • ./data/*: Binds the media libraries as read-only (:ro) to protect the source library files from accidental modification or deletion by the container processes.
  • PUID/PGID: Runs the internal container process with standard system user privileges, preventing file ownership conflicts.

SQLite Database Engine Setup

Kavita relies on an embedded SQLite database engine. SQLite handles all user data, library indexes, metadata stores, and reading progression markers.

Write-Ahead Logging (WAL) Mode

By default, Kavita initiates SQLite in Write-Ahead Logging (WAL) mode. WAL mode significantly boosts concurrent write performance and reduces database lock operations when the server is scanning large libraries while users are reading.

To prevent corruption, adhere to the following database maintenance guidelines:

  1. Storage Type: Avoid placing the SQLite file (/kavita/config/kavita.db) on NFS, SMB, or other network-attached filesystems. SQLite relies on POSIX advisory locks, which are often unstable over network shares. Always use local SSD or NVMe storage.
  2. Backups: Copying kavita.db while the container is actively writing can result in partial or corrupt backup files. Implement an automated backup script that uses the SQLite .backup API or temporarily pauses the container: bash # Safe SQLite Backup Script docker stop kavita cp /opt/kavita/config/kavita.db /opt/kavita/config/backups/kavita_$(date +%F).db docker start kavita

Metadata and OPDS Configuration

Kavita automates library discovery by leveraging embedded metadata files and directory scanner logic.

Metadata Specifications

Ensure your libraries conform to standard metadata schemes to enable Kavita's powerful filter and search capabilities:

Format Recommended Metadata Standard Integration Notes
Ebooks (EPUB) OPF metadata (embedded) Ensure title, creator (author), and series tags are present in the internal OPF file.
Manga / Comics ComicInfo.xml (embedded inside .cbz or .cbr) Use ComicTagger or Calibre to write ComicInfo.xml schemas directly inside the archives.
PDF File names and folders Since PDFs lack standardized internal metadata, Kavita uses folder layout parsing to extract series and book titles.

OPDS Feed Configuration

The Open Publication Distribution System (OPDS) protocol allows external client e-readers (such as Panels, Chunky, Moon+ Reader, or Mihon/Tachiyomi) to parse and download books directly from your VPS-hosted library.

To expose the OPDS endpoint:

  1. Access the Kavita Web UI (http://<your-vps-ip>:5000).
  2. Navigate to Settings (gear icon) -> Users.
  3. Locate your admin profile, and copy the unique OPDS Query URL. The URL adheres to the following pattern: http://<your-vps-ip>:5000/api/opds/<your-unique-api-key>
  4. Open your preferred reader app on your mobile device or tablet.
  5. Add a new OPDS catalog/feed, and paste the copied URL.

Secure Reverse Proxy Configuration

Exposing port 5000 directly over the public internet exposes your Kavita instance to brute-force attacks and eavesdropping. Implementing a reverse proxy with SSL termination via Let's Encrypt secures user authentication and data transfers.

Caddy automatically handles SSL certificate acquisition and renewal. Add the following to your /etc/caddy/Caddyfile:

kavita.yourdomain.com {
    reverse_proxy 127.0.0.1:5000
}

Option 2: Nginx

If running Nginx, configure a virtual host block to manage proxy routing and security headers:

server {
    listen 80;
    listen [::]:80;
    server_name kavita.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name kavita.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/kavita.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/kavita.yourdomain.com/privkey.pem;

    # Security Headers
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";
    add_header Referrer-Policy "no-referrer-when-downgrade";

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSockets support (required for Kavita reader communication)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 86400;
    }
}