Grimmory Self Hosted Library
Expert knowledge for setting up, configuring, and extending Grimmory — a self-hosted book library manager supporting EPUBs, PDFs, comics, Kobo sync, OPDS, and multi-user management.
- Skill ID
- reason-machines/trending-skills/grimmory-self-hosted-library
- Publisher
- reason-machines
- Repository
- trending-skills
- Installs
- 0
- Files
- 1
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
reason-machines/trending-skills/grimmory-self-hosted-libraryInstalls these files- SKILL.md
What this skill tells the agent
Grimmory Self-Hosted Library Manager
Skill by ara.so — Daily 2026 Skills collection.
Grimmory is a self-hosted application (successor to BookLore) for managing your entire book collection. It supports EPUBs, PDFs, MOBIs, AZW/AZW3, and comics (CBZ/CBR/CB7), with a built-in browser reader, annotations, Kobo/OPDS sync, KOReader progress sync, metadata enrichment, and multi-user support.
Installation
Requirements
- Docker and Docker Compose
Step 1: Create .env
# Application
APP_USER_ID=1000
APP_GROUP_ID=1000
TZ=Etc/UTC
# Database
DATABASE_URL=jdbc:mariadb://mariadb:3306/grimmory
DB_USER=grimmory
DB_PASSWORD=${DB_PASSWORD}
# Storage: LOCAL (default) or NETWORK
DISK_TYPE=LOCAL
# MariaDB
DB_USER_ID=1000
DB_GROUP_ID=1000
MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE=grimmoryStep 2: Create docker-compose.yml
services:
grimmory:
image: grimmory/grimmory:latest
# Alternative registry: ghcr.io/grimmory-tools/grimmory:latest
container_name: grimmory
environment:
- USER_ID=${APP_USER_ID}
- GROUP_ID=${APP_GROUP_ID}
- TZ=${TZ}
- DATABASE_URL=${DATABASE_URL}
- DATABASE_USERNAME=${DB_USER}
- DATABASE_PASSWORD=${DB_PASSWORD}
- DISK_TYPE=${DISK_TYPE}
depends_on:
mariadb:
condition: service_healthy
ports:
- "6060:6060"
volumes:
- ./data:/app/data
- ./books:/books
- ./bookdrop:/bookdrop
healthcheck:
test: wget -q -O - http://localhost:6060/api/v1/healthcheck
interval: 60s
retries: 5
start_period: 60s
timeout: 10s
restart: unless-stopped
mariadb:
image: lscr.io/linuxserver/mariadb:11.4.5
container_name: mariadb
environment:
- PUID=${DB_USER_ID}
- PGID=${DB_GROUP_ID}
- TZ=${TZ}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${DB_USER}
- MYSQL_PASSWORD=${DB_PASSWORD}
volumes:
- ./mariadb/config:/config
restart: unless-stopped
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10Step 3: Launch
docker compose up -d
# View logs
docker compose logs -f grimmory
# Check health
curl http://localhost:6060/api/v1/healthcheckOpen http://localhost:6060 and create your admin account.
Volume Layout
./data/ # App data, thumbnails, user config
./books/ # Your book files (mounted at /books)
./bookdrop/ # Drop-zone for auto-import (mounted at /bookdrop)
./mariadb/ # MariaDB dataEnvironment Variables Reference
| Variable | Description | Default |
|---|---|---|
USER_ID | UID for the app process | 1000 |
GROUP_ID | GID for the app process | 1000 |
TZ | Timezone string | Etc/UTC |
DATABASE_URL | JDBC connection string | required |
DATABASE_USERNAME | DB username | required |
DATABASE_PASSWORD | DB password | required |
DISK_TYPE | LOCAL or NETWORK | LOCAL |
Supported Book Formats
| Category | Formats |
|---|---|
| eBooks | EPUB, MOBI, AZW, AZW3 |
| Documents | |
| Comics | CBZ, CBR, CB7 |
BookDrop (Auto-Import)
Drop files into ./bookdrop/ on your host. Grimmory watches the folder, extracts metadata from Google Books and Open Library, and queues books for review.
./bookdrop/
my-novel.epub ← dropped here
another-book.pdf ← dropped hereFlow:
- Watch — Grimmory monitors
/bookdropcontinuously - Detect — New files are picked up and parsed
- Enrich — Metadata fetched from Google Books / Open Library
- Import — Review in UI, adjust if needed, confirm import
Volume mapping required in docker-compose.yml:
volumes:
- ./bookdrop:/bookdropNetwork Storage Mode
For NFS, SMB, or other network-mounted filesystems, set DISK_TYPE=NETWORK. This disables destructive UI operations (delete, move, rename) to protect shared mounts while keeping reading, metadata, and sync fully functional.
# .env
DISK_TYPE=NETWORKJava Backend — Key Patterns
Grimmory is a Java application (Spring Boot + MariaDB). When contributing or extending:
Project Structure (typical Spring Boot layout)
src/main/java/
com/grimmory/
config/ # Spring configuration classes
controller/ # REST API controllers
service/ # Business logic
repository/ # JPA repositories
model/ # JPA entities
dto/ # Data transfer objectsREST API — Base Path
All endpoints are under /api/v1/:
# Health check
GET http://localhost:6060/api/v1/healthcheck
# Books
GET http://localhost:6060/api/v1/books
GET http://localhost:6060/api/v1/books/{id}
POST http://localhost:6060/api/v1/books
PUT http://localhost:6060/api/v1/books/{id}
DELETE http://localhost:6060/api/v1/books/{id}
# Shelves
GET http://localhost:6060/api/v1/shelves
POST http://localhost:6060/api/v1/shelves
# OPDS catalog (for compatible reader apps)
GET http://localhost:6060/opdsExample: Querying the API with Java (OkHttp)
import okhttp3.*;
import com.fasterxml.jackson.databind.ObjectMapper;
public class GrimmoryClient {
private final OkHttpClient http = new OkHttpClient();
private final ObjectMapper mapper = new ObjectMapper();
private final String baseUrl;
private final String token;
public GrimmoryClient(String baseUrl, String token) {
this.baseUrl = baseUrl;
this.token = token;
}
public String getBooks() throws Exception {
Request request = new Request.Builder()
.url(baseUrl + "/api/v1/books")
.header("Authorization", "Bearer " + token)
.build();
try (Response response = http.newCall(request).execute()) {
return response.body().string();
}
}
}Example: Spring Boot Controller Pattern
@RestController
@RequestMapping("/api/v1/books")
@RequiredArgsConstructor
public class BookController {
private final BookService bookService;
@GetMapping
public ResponseEntity<Page<BookDto>> getAllBooks(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String search) {
return ResponseEntity.ok(bookService.findAll(page, size, search));
}
@GetMapping("/{id}")
public ResponseEntity<BookDto> getBook(@PathVariable Long id) {
return ResponseEntity.ok(bookService.findById(id));
}
@PostMapping
public ResponseEntity<BookDto> createBook(@RequestBody @Valid CreateBookRequest request) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(bookService.create(request));
}
@PutMapping("/{id}/metadata")
public ResponseEntity<BookDto> updateMetadata(
@PathVariable Long id,
@RequestBody @Valid UpdateMetadataRequest request) {
return ResponseEntity.ok(bookService.updateMetadata(id, request));
}
}Example: JPA Entity Pattern
@Entity
@Table(name = "books")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
private String author;
private String isbn;
private String format; // EPUB, PDF, CBZ, etc.
@Column(name = "file_path")
private String filePath;
@Column(name = "cover_path")
private String coverPath;
@Column(name = "reading_progress")
private Double readingProgress;
@ManyToMany
@JoinTable(
name = "book_shelf",
joinColumns = @JoinColumn(name = "book_id"),
inverseJoinColumns = @JoinColumn(name = "shelf_id")
)More from reason-machines
Use this skill in a project
Open any RiverX project, search the Skills panel for reason-machines/trending-skills/grimmory-self-hosted-library, and install it in one click.
