How to Self-Host Calibre-Web with Docker Compose & Cloudflare (Zero to Production)
How to Self-Host Calibre-Web with Docker Compose & Cloudflare (Zero to Production)

Syncing reading progress across devices shouldn't depend on proprietary reader apps or copyright-sensitive cloud lockers. This guide walks you through deploying Calibre-Web on a VPS using Docker Compose, mapping a custom domain using Cloudflare Origin Rules, and avoiding common permission traps.
Prerequisites
- A Linux VPS (Ubuntu recommended) with Docker and Docker Compose installed.
- A domain managed through Cloudflare.
Step 1: Prepare the Docker Compose Setup
Using Docker Named Volumes avoids permission issues (such as sqlite3.OperationalError: unable to open database file) often caused by host filesystem permissions or copy-on-write mechanisms.
Create a project directory and open docker-compose.yml:
mkdir -p ~/calibre-web && cd ~/calibre-web
nano docker-compose.yml
Paste the following configuration:
services:
calibre-web:
image: lscr.io/linuxserver/calibre-web:latest
container_name: calibre-web
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Ho_Chi_Minh
- DOCKER_MODS=linuxserver/mods:universal-calibre
volumes:
- calibre_config:/config
- calibre_books:/books
ports:
- "8083:8083"
restart: unless-stopped
volumes:
calibre_config:
calibre_books:
Note: The universal-calibre mod handles in-container format conversions. The initial launch takes an extra 1–2 minutes to download dependencies.
Step 2: Initialize Database and Start the Container
Calibre-Web requires a starter SQLite template (metadata.db) inside the library path before it can boot its internal configuration database (app.db).
1. Launch the container:
docker compose up -d
2. Inject the starter database into the named volume:
curl -L -o /tmp/metadata.db https://raw.githubusercontent.com/janeczku/calibre-web/master/library/metadata.db
docker cp /tmp/metadata.db calibre-web:/books/metadata.db
docker exec -it calibre-web chown 1000:1000 /books/metadata.db
3. Verify the logs:
docker logs -f calibre-web
Verify Direct Access (Deployment Success)
At this point, Calibre-Web is fully up and running. We cam access directly via IP by open browser and navigate to:
http://YOUR_VPS_IP:8083
Step 3: Attach Custom Domain with Cloudflare Origin Rules
To use https://books.yourdomain.com without exposing port 8083 or setting up an Nginx reverse proxy, use Cloudflare Origin Rules to route HTTPS traffic directly to port 8083.
1. Create a DNS Record:
- Go to Cloudflare Dashboard > DNS > Records.
- Add an A Record: books pointing to YOUR_VPS_IP.
- Enable proxy status (🟠 Proxied).
2. Add an Origin Rule:
- Navigate to Rules > Origin Rules > Create rule.
- Rule name: Calibre Port Rewrite
- Filter: Custom filter expression
- Field: Hostname | Operator: equals | Value: books.yourdomain.com
- Set origin port to: Select Rewrite to... and enter 8083.
- Click Deploy.
Step 4: First-Time Web Configuration
Open https://books.yourdomain.com (or your direct IP) in your browser.
1. Set Database Location:
- In Location of Calibre database, enter: /books
- Click Submit.
2. Login
- Username: admin
- Password: admin123 (change this immediately under Admin -> Edit Users)
3. Fix "Kepubify binary not found" Error:
- If you encounter this validation error when saving settings:
- Go to Admin > Edit Basic Configuration > External Binaries.
- Completely clear the Path to Kepubify E-Book Converter field (leave it empty).
- Click Save.
4. Enable Uploads:
- Under Admin > Edit Basic Configuration > Feature Configuration, check Enable Uploads and click Save.
- Go to Admin > Users > click your user account > check Allow Upload > click Save.
5. Enable Dark Mode (Optional):
- Go to Admin > Edit UI Configuration.
- Set Theme to Dark and click Save.
Reading & Progress Syncing
Click the Upload button in the navigation bar to add .epub or .pdf files. Open any book with Read in Browser.
Because progress is saved to your user database on your custom domain, closing the tab on your work PC and opening it on another machine will resume reading from the exact same page.