Skip to content

Grimmory Grimmory

Description / nameInput element
Container Registry
Container Configuration Root Path
Global /books Path
Timezone
User ID
Group ID
Grimmory /bookdrop Path
Grimmory /app/data Path

Build Status Last Commit OCI Pulls

Self-hosted digital library — successor to BookLore, with smart shelves, metadata, Kobo/KOReader sync, OPDS support, and a built-in reader.

  • Smart Shelves — Rule-based filtering, tagging, and full-text search
  • Built-in Reader — In-browser reading for PDFs, EPUBs, and comics with annotations and progress tracking
  • Metadata Lookup — Book info from Google Books, Open Library, and Amazon (fully editable)
  • Device Sync — Kobo compatibility, OPDS support, and KOReader synchronization
  • BookDrop — Drop files into a watched folder; Grimmory detects, enriches metadata, and queues for review
  • Multi-User — Individual shelves and preferences with local or OIDC authentication
  • One-Click Sharing — Send books to Kindle, email, or other users

Supported formats: EPUB, MOBI, AZW, AZW3, FB2, PDF, CBZ, CBR, CB7, M4B, M4A, MP3, OPUS

Registry ghcr.io/daemonless/grimmory
Daemonless daemonless/grimmory
Source grimmory-tools/grimmory
Website grimmory.org

Version Tags

Tag Description Best For
latest Upstream Binary. Built from official release. Most users — recommended.

Parts

Service Image Role
grimmory ghcr.io/daemonless/grimmory:latest docs
mariadb ghcr.io/daemonless/mariadb:latest docs

Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions, including the security model for host vs. container privileges.

Deployment

Database Where Grimmory keeps its library. The default runs its own MariaDB.

grimmory:latest · mariadb:latest — data in /containers/grimmory/data, /containers/grimmory/books, /containers/grimmory/bookdrop, /containers/grimmory/mariadb

A MariaDB of its own, on the stack's private network.

1. Save as .env and fill in what is empty:

# Grimmory for FreeBSD (Daemonless)
# Copy to .env and edit.

# Where data lives on the host
DATA_LOCATION=/containers/grimmory/data
EBOOKS_PATH=/containers/grimmory/books
BOOKDROP_PATH=/containers/grimmory/bookdrop
DB_DATA_LOCATION=/containers/grimmory/mariadb

# Database passwords. Change these to random passwords!
DB_PASSWORD=grimmory
DB_ROOT_PASSWORD=grimmory-root

# Timezone (TZ identifier)
TZ=UTC

2. Save as compose.yaml:

name: grimmory

services:
  grimmory:
    image: ghcr.io/daemonless/grimmory:latest
    container_name: grimmory
    network_mode: host
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=${TZ:-Etc/UTC}
      - DATABASE_URL=jdbc:mariadb://${MARIADB_HOSTNAME:-127.0.0.1}:${MARIADB_PORT:-3306}/${MARIADB_DATABASE:-grimmory}
      - DATABASE_USERNAME=${MARIADB_USER:-grimmory}
      - DATABASE_PASSWORD=${DB_PASSWORD}
      - SWAGGER_ENABLED=false
      - FORCE_DISABLE_OIDC=false
    volumes:
      - ${DATA_LOCATION}:/app/data
      - ${EBOOKS_PATH}:/books
      - ${BOOKDROP_PATH}:/bookdrop
    depends_on:
      - mariadb
    restart: unless-stopped

  mariadb:
    image: ghcr.io/daemonless/mariadb:latest
    container_name: grimmory-mariadb
    network_mode: host
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=${TZ:-Etc/UTC}
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
      - MYSQL_DATABASE=grimmory
      - MYSQL_USER=grimmory
      - MYSQL_PASSWORD=${DB_PASSWORD}
    volumes:
      - ${DB_DATA_LOCATION}:/config
    restart: unless-stopped

Then run podman-compose up -d.

Database Where Grimmory keeps its library. The default runs its own MariaDB.

grimmory:latest · mariadb:latest — data in /containers/grimmory/data, /containers/grimmory/books, /containers/grimmory/bookdrop, /containers/grimmory/mariadb

A MariaDB of its own, on the stack's private network.

# .env

DIRECTOR_PROJECT=grimmory
PUID=1000
PGID=1000
TZ=UTC
DATABASE_URL=jdbc:mariadb://${MARIADB_HOSTNAME:-127.0.0.1}:${MARIADB_PORT:-3306}/${MARIADB_DATABASE:-grimmory}
DATABASE_USERNAME=grimmory
DATABASE_PASSWORD=${DB_PASSWORD}
SWAGGER_ENABLED=false
FORCE_DISABLE_OIDC=false
DATA_LOCATION=/containers/grimmory/data
EBOOKS_PATH=/containers/grimmory/books
BOOKDROP_PATH=/containers/grimmory/bookdrop
DB_DATA_LOCATION=/containers/grimmory/mariadb
DB_PASSWORD=grimmory
DB_ROOT_PASSWORD=grimmory-root
# appjail-director.yml

options:
  - alias:
  - ip4_inherit:
services:
  grimmory:
    name: grimmory
    options:
      - container: 'args:--pull'
    oci:
      user: root
      environment:
        - PUID: !ENV '${PUID}'
        - PGID: !ENV '${PGID}'
        - TZ: !ENV '${TZ}'
        - DATABASE_URL: !ENV '${DATABASE_URL}'
        - DATABASE_USERNAME: !ENV '${DATABASE_USERNAME}'
        - DATABASE_PASSWORD: !ENV '${DATABASE_PASSWORD}'
        - SWAGGER_ENABLED: !ENV '${SWAGGER_ENABLED}'
        - FORCE_DISABLE_OIDC: !ENV '${FORCE_DISABLE_OIDC}'
        - DATA_LOCATION: !ENV '${DATA_LOCATION}'
        - EBOOKS_PATH: !ENV '${EBOOKS_PATH}'
        - BOOKDROP_PATH: !ENV '${BOOKDROP_PATH}'
        - DB_DATA_LOCATION: !ENV '${DB_DATA_LOCATION}'
        - DB_PASSWORD: !ENV '${DB_PASSWORD}'
        - DB_ROOT_PASSWORD: !ENV '${DB_ROOT_PASSWORD}'
    volumes:
      - GRIMMORY_DATA_PATH: /app/data
      - BOOKS_PATH: /books
      - GRIMMORY_BOOKDROP_PATH: /bookdrop
  mariadb:
    name: mariadb
    options:
      - from: ghcr.io/daemonless/mariadb:latest
      - template: !ENV '${PWD}/template.conf'
    oci:
      environment:
        - PUID: "1000"
        - PGID: "1000"
        - TZ: !ENV '${TZ}'
        - MYSQL_ROOT_PASSWORD: !ENV '${DB_ROOT_PASSWORD}'
        - MYSQL_DATABASE: "grimmory"
        - MYSQL_USER: "grimmory"
        - MYSQL_PASSWORD: !ENV '${DB_PASSWORD}'
    volumes:
      - db_data: /config
volumes:
  GRIMMORY_DATA_PATH:
    device: '/containers/grimmory/data'
  BOOKS_PATH:
    device: '/path/to/books'
  GRIMMORY_BOOKDROP_PATH:
    device: '/containers/grimmory/bookdrop'
  db_data:
    device: !ENV '${DB_DATA_LOCATION}'
1
2
3
4
5
6
7
# Makejail

ARG tag=latest

OPTION container=boot
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/grimmory:${tag}

Save the files above, then run appjail-director up.

Interactive Configuration

Parameters

Environment Variables

Variable Default Description
PUID 1000
PGID 1000
TZ Etc/UTC Timezone
DATABASE_URL jdbc:mariadb://${MARIADB_HOSTNAME:-127.0.0.1}:${MARIADB_PORT:-3306}/${MARIADB_DATABASE:-grimmory} MariaDB JDBC URL (e.g., jdbc:mariadb://127.0.0.1:3306/grimmory)
DATABASE_USERNAME grimmory Database username
DATABASE_PASSWORD grimmory Database password
SWAGGER_ENABLED false Enable Swagger UI (default: false)
FORCE_DISABLE_OIDC false Force-disable OIDC authentication (default: false)
DATA_LOCATION /containers/grimmory/data Path to store Grimmory configuration and application data
EBOOKS_PATH /containers/grimmory/books Path to your ebook library
BOOKDROP_PATH /containers/grimmory/bookdrop Drop folder watched for automatic imports
DB_DATA_LOCATION /containers/grimmory/mariadb Path to store MariaDB data files
MARIADB_HOSTNAME `` Where Grimmory finds MariaDB (fjord sets this for the built-in one; 127.0.0.1 on host networking)
MARIADB_PORT `` MariaDB port (your own MariaDB)
MARIADB_DATABASE `` Database name (your own MariaDB)
MARIADB_USER `` Database user (your own MariaDB)
DB_PASSWORD grimmory Database password (grimmory user)
DB_ROOT_PASSWORD grimmory-root MariaDB root password (built-in MariaDB only)
JAVA_TOOL_OPTIONS `` Additional JVM options (e.g., -Xmx256m)

Volumes

Path Description
/app/data Configuration and application data
/books Book library directory
/bookdrop Drop folder for automatic imports

Implementation Details

  • Architectures: amd64
  • User: bsd (UID/GID set via PUID/PGID). Defaults to 1000:1000.
  • Base: Built on ghcr.io/daemonless/base (FreeBSD 15.1).

Need help? Join our Discord community.