A self-hosted manga and comic book reader -- like Jellyfin, but for CBZ files.
OpenPanel scans your CBZ comic/manga library, indexes pages from ZIP archives without extracting them, generates thumbnails, and serves a responsive web reader with continuous-scroll and single-page modes, RTL/LTR support, reading progress tracking, bookmarks, collections, and multi-user support.
- Zero extraction -- pages are streamed directly from CBZ (ZIP) archives
- Automatic scanning -- detects new/changed CBZ files in your library folders
- Thumbnail generation -- WebP thumbnails for books and series
- Reading modes -- continuous scroll or single-page, LTR or RTL, fit-width/fit-height/original
- Reading progress -- tracked per-user, server-side continue-reading
- Bookmarks -- bookmark pages with optional notes, accessible from a slide-out panel
- Collections -- organize series into custom collections
- Multi-user -- username/password authentication, first user becomes admin
- Admin panel -- manage libraries, users, settings, logs, backups, and updates
- AniList integration -- automatic metadata, covers, and descriptions from AniList
- PWA -- installable on mobile and desktop with offline caching
- Security headers -- X-Content-Type-Options, X-Frame-Options, Referrer-Policy
- Responsive -- works on desktop, tablet, and mobile
- Keyboard shortcuts -- arrow keys, space, escape
- Docker ready -- single multi-stage Docker image
- Rust 1.75+ (for the backend)
- Node.js 20+ (for the frontend build)
- CBZ files organized in folders
Organize your files like this:
/path/to/manga/
+-- One Piece/
| +-- Chapter 001.cbz
| +-- Chapter 002.cbz
| +-- ...
+-- Naruto/
| +-- Vol 01.cbz
| +-- ...
+-- Standalone Book.cbz
Each subfolder becomes a series. CBZ files directly in the root become standalone books.
-
Clone the repository:
git clone https://github.com/youruser/openpanel.git cd openpanel -
Install frontend dependencies:
cd ui npm install -
Start the backend:
cd server cargo runThe server starts on
http://localhost:3001. -
Start the frontend dev server:
cd ui npm run devThe dev server starts on
http://localhost:3000and proxies/apicalls to:3001. -
Open the app: Go to
http://localhost:3000 -
First-time setup:
- You will be prompted to create an admin account (username + password)
- Add a library in the Admin panel (shield icon in the sidebar)
- Click Scan Now to index your library
- Go back Home to see your series
The backend is configured through environment variables (or a .env file in the server/ directory):
| Variable | Default | Description |
|---|---|---|
OPENPANEL_PORT |
3001 |
Server port |
OPENPANEL_DATA_DIR |
./data |
Where the SQLite database and thumbnails are stored |
DATABASE_URL |
sqlite://<DATA_DIR>/openpanel.db |
SQLite database URL |
OPENPANEL_LIBRARY_ROOTS |
(empty) | Comma-separated paths to scan on startup (optional, libraries can also be added via admin UI) |
OPENPANEL_DEV_MODE |
false |
Enables CORS for localhost:5173 |
OPENPANEL_LOG_LEVEL |
info |
Tracing log level (debug, info, warn, error) |
OPENPANEL_ZIP_CACHE_SIZE |
200 |
Number of ZIP indexes to keep in the LRU cache |
OPENPANEL_PUBLIC_URL |
http://localhost:3001 |
Public URL (used for CORS in production) |
OPENPANEL_SCAN_ON_STARTUP |
true |
Automatically scan libraries when the server starts |
Example .env:
OPENPANEL_PORT=3001
OPENPANEL_DATA_DIR=./data
OPENPANEL_DEV_MODE=true
OPENPANEL_LIBRARY_ROOTS=/home/user/manga,/home/user/comics
OPENPANEL_LOG_LEVEL=info-
Edit
docker-compose.yml-- update the volume mounts to point to your library folders:volumes: - openpanel-data:/data - /your/manga/folder:/libraries/manga:ro - /your/comics/folder:/libraries/comics:ro
-
Start the stack:
docker compose up -d
-
Access the app:
http://your-server:3001 -
Add libraries via Admin:
- Go to Admin and enter the container paths (e.g.,
/libraries/manga)
- Go to Admin and enter the container paths (e.g.,
- Edit
Caddyfile-- replaceopenpanel.example.comwith your domain - In
docker-compose.yml, uncomment thecaddyservice -
docker compose up -d
- Caddy will automatically get an HTTPS certificate via Let's Encrypt
docker build -t openpanel .
docker run -d \
-p 3001:3001 \
-v openpanel-data:/data \
-v /path/to/manga:/libraries/manga:ro \
--name openpanel \
openpanel-
Build the frontend:
cd ui npm ci npm run buildThis outputs static files to
ui/dist/. -
Build the backend:
cd server cargo build --release -
Run:
cd server OPENPANEL_DATA_DIR=/var/lib/openpanel OPENPANEL_PORT=3001 ./target/release/openpanel-serverThe server serves the frontend from
ui/dist/automatically.
+-------------+ +--------------+ +--------------+
| React SPA |------>| Axum (Rust) |------>| SQLite DB |
| (Vite PWA) | API | REST API | | (WAL mode) |
+-------------+ +------+-------+ +--------------+
|
+-------v-------+
| CBZ Files |
| (ZIP on disk)|
+---------------+
- Backend: Rust + Axum 0.8 + SQLite (via sqlx). Serves both the API and static frontend files.
- Frontend: React 19 + TypeScript + Vite 7, TanStack Router, Zustand, Base UI, Tailwind v4.
- CBZ reading: ZIP central directory is parsed once and cached in an LRU cache. Individual pages are read by seeking to the entry offset -- no full extraction.
- Auth model: Username/password authentication with bcrypt. First registered user is admin. Sessions stored server-side with 1-year expiry. Bearer token in Authorization header.
- Metadata: Cover images and series info fetched from AniList and cached server-side in SQLite.
- PWA: Service worker for offline shell caching, runtime caching for API responses and page images.
All API routes are under /api/. Auth routes are public; most others require a Bearer token.
Full documentation: docs/API.md · OpenAPI spec: docs/openapi.yaml (also served at /api/openapi.yaml)
| Method | Path | Description |
|---|---|---|
| GET | /api/health |
Health check |
| POST | /api/auth/register |
Register a new user |
| POST | /api/auth/login |
Login (returns token) |
| POST | /api/auth/logout |
Logout (invalidates token) |
| GET | /api/auth/me |
Current user info |
| GET | /api/auth/status |
Setup status (needs first user?) |
| GET | /api/libraries |
List all libraries |
| GET | /api/libraries/:id/series |
List series in a library (paginated) |
| GET | /api/series |
List all series (sort, genre, status) |
| GET | /api/series/:id/books |
List books in a series |
| GET | /api/series/:id/chapters |
List detected chapters in a series |
| GET | /api/series/:id/metadata |
Get/set/clear AniList metadata |
| GET | /api/genres |
List all available genres |
| GET | /api/books/:id |
Book details |
| GET | /api/books/:id/pages/:num |
Stream a page image |
| GET | /api/books/:id/thumbnail |
Book thumbnail (WebP) |
| GET | /api/series/:id/thumbnail |
Series thumbnail (WebP) |
| GET/PUT | /api/progress |
Get/update reading progress |
| GET | /api/progress/batch |
Batch get progress for multiple books |
| GET | /api/continue-reading |
Continue reading list (server-side) |
| GET/POST | /api/bookmarks |
List/create bookmarks |
| DELETE | /api/bookmarks/:id |
Delete a bookmark |
| GET/POST | /api/collections |
List/create collections |
| GET/DEL | /api/collections/:id |
Get/delete a collection |
| POST | /api/collections/:id/items |
Add series to collection |
| DELETE | /api/collections/:id/items/:series_id |
Remove series from collection |
| GET/PUT | /api/preferences |
Get/update user preferences |
| GET | /api/version |
Server version info |
| GET/PUT | /api/admin/settings |
App settings (admin only) |
| POST | /api/admin/scan |
Trigger library scan |
| GET | /api/admin/scan/status |
Scan progress |
| POST | /api/admin/libraries |
Add a library |
| PUT/DEL | /api/admin/libraries/:id |
Update/remove a library |
| GET | /api/admin/libraries/browse |
Browse server directories |
| GET/POST | /api/admin/profiles |
List/create users |
| DELETE | /api/admin/profiles/:id |
Delete a user |
| PUT | /api/admin/password |
Change password |
| POST | /api/admin/update |
Trigger server update |
| GET | /api/admin/check-update |
Check for updates |
| GET | /api/admin/logs |
View admin logs |
| POST | /api/admin/backup |
Create database backup |
| GET | /api/admin/backups |
List backups |
OpenPanel is a full PWA — you can install it as an app on any device:
- Open your OpenPanel URL in the browser (Safari on iOS, Chrome on Android)
- Tap the Share button (iOS) or the three-dot menu (Android)
- Select Add to Home Screen
- The app will launch in standalone mode — no browser chrome, feels like a native app
- Open your OpenPanel URL
- Click the install icon in the address bar (or go to ⋮ → "Install OpenPanel")
- The app opens in its own window
- Offline shell — the app shell (HTML/CSS/JS) is cached by the service worker, so the UI loads instantly even on slow connections
- API caching — series lists and metadata are cached with a Network-First strategy (5-minute expiry), so browsing your library works even briefly offline
- Page image caching — manga pages you've read are cached locally (Cache-First, up to 500 pages, 7-day expiry), so re-reading is instant
- Auto-updates — the service worker updates automatically when a new version is deployed
MIT