BookLore on Ubuntu 24.04 on Azure User Guide
Overview
BookLore is an open source, self hosted digital library for people and teams with a serious book collection. Point it at your EPUB, PDF and comic files and it organises them into libraries and smart shelves, fetches cover art and rich metadata, and gives every member of your household or organisation their own account, their own shelves and their own reading progress. A built in browser reader handles EPUB, PDF and comic formats with bookmarks and per user position tracking, and an OPDS catalogue plus Kobo and KOReader sync let you carry the same library onto a dedicated e-reader.
The cloudimg image installs BookLore 2.3.1 natively and assembles the whole stack for you. BookLore ships as a single Spring Boot application with the Angular web console compiled into the same artifact, so one tuned Java service serves both the REST API and the browser interface on 127.0.0.1:6060, with nginx in front on port 80 and MariaDB on the loopback interface holding library, user and reading progress data. Book files, generated covers and the watched auto import folder live on a dedicated data volume mounted at /var/lib/booklore, so the library grows on its own disk and can be expanded or snapshotted independently of the operating system.
The appliance is secure by default, and unusually strongly so. BookLore creates its first administrator through a one time setup call that the API accepts only while no users exist, which means upstream ships no default account at all rather than a vendor password to rotate. Shipping that as is would leave the first run setup screen open to whoever reached the instance first, so this image goes further: on the first boot of every instance a one shot service generates a unique database password and administrator password, claims the administrator through that same one time endpoint before nginx ever serves port 80, clears the inherited token signing key so the instance signs with a key of its own, and then proves the new credentials authenticate, that a forged token is rejected and that the setup endpoint is closed.
What is included:
- BookLore 2.3.1 built from the pinned upstream release tag (
/opt/booklore/src) booklore.service— the Spring Boot application and embedded Angular console on127.0.0.1:6060- MariaDB 10.11 for library, user and reading progress data, bound to the loopback interface
nginx.servicereverse proxy on TCP 80, tuned for BookLore's large headers, streaming uploads and progress websockets, TLS readybooklore-firstboot.servicegenerating every per instance secret and claiming the administrator on first boot- A dedicated 64 GiB data volume mounted at
/var/lib/booklorefor books, covers and the auto import folder - Temurin JDK 25 runtime, with the build only Node.js toolchain removed from the shipped image
- The complete AGPL-3.0 corresponding source shipped on the instance
- Ubuntu 24.04 LTS base, latest patches, unattended security upgrades enabled
- 24/7 cloudimg support, 24h response SLA
Prerequisites
An active Azure subscription, an SSH key, and a VNet with a subnet. Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM). The whole stack is memory tuned to run comfortably in 4 GB with no swap at all — the Java heap is pinned rather than left to claim a percentage of RAM, and MariaDB's buffers are capped to match. Move to a larger size if you plan to import very large collections or serve many concurrent readers.
Step 1: Deploy from the Azure Portal
Search the Azure Marketplace for BookLore, choose the plan, and create the VM. In the networking step attach an NSG that allows inbound TCP 22 (SSH) and TCP 80 (the web console) from your client networks only. Put a TLS reverse proxy in front of port 80 for production. No other port needs to be open: MariaDB and the application's own port 6060 both listen on the loopback interface, so nginx is the only ingress.
Step 2: Deploy from the Azure CLI
RG="booklore-prod"; LOCATION="eastus"; VM_NAME="booklore-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/booklore-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name bl-vnet --address-prefix 10.113.0.0/16 --subnet-name bl-subnet --subnet-prefix 10.113.1.0/24
az network nsg create -g "$RG" --name bl-nsg
az network nsg rule create -g "$RG" --nsg-name bl-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name bl-nsg --name allow-web --priority 110 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name bl-vnet --subnet bl-subnet --nsg bl-nsg --public-ip-sku Standard
The library data disk is part of the image, so it is created and mounted for you automatically. There is no extra disk to attach.
Step 3: Connect via SSH
ssh azureuser@<vm-ip>
All services start automatically on first boot, after booklore-firstboot.service has generated this instance's secrets and claimed the administrator. First boot typically completes within a minute of the VM reporting ready.
Step 4: Verify the Services
sudo systemctl is-active mariadb booklore nginx
sudo test -f /var/lib/cloudimg/booklore-firstboot.done && echo FIRSTBOOT_DONE
sudo ss -tlnH | awk '{print $1, $4}' | sort -u
Expected output — all three units are active, first boot has completed, and only SSH and nginx are on a public address while the application and the database stay on loopback:
active
active
active
FIRSTBOOT_DONE
LISTEN 0.0.0.0:22
LISTEN 0.0.0.0:80
LISTEN 127.0.0.1:3306
LISTEN 127.0.0.53%lo:53
LISTEN 127.0.0.54:53
LISTEN [::]:22
LISTEN [::]:80
LISTEN [::ffff:127.0.0.1]:6060
Only SSH and nginx are on a public address. MariaDB is on 127.0.0.1:3306, the two port 53 entries are the local systemd-resolved DNS stub, and the application itself is on [::ffff:127.0.0.1]:6060 — that is the IPv4 mapped form of 127.0.0.1, so port 6060 is reachable only from the VM itself and nginx is the sole ingress.

Step 5: Retrieve Your Administrator Credentials
Every instance generates its own administrator password and database password on first boot and writes them to a root only file:
sudo ls -l /root/booklore-credentials.txt
sudo cat /root/booklore-credentials.txt
The file is mode 600 owned by root, and contains the console URL, the administrator username and password, and the database credentials. Sign in with those values, then change the administrator e-mail address and password from Settings → Users.

Step 6: Confirm There Is No Default Login
BookLore creates its first administrator through a one time setup endpoint that the API accepts only while no users exist. This image claims that administrator during first boot, before nginx starts, so nobody can reach your instance and claim it first. Confirm the setup endpoint is closed:
curl -s http://127.0.0.1/api/v1/setup/status
Expected output — setup has already been completed on this instance:
{"data":true,"message":"Initial setup has already been completed.","status":200,"timestamp":"..."}
A protected endpoint must refuse an anonymous caller and a forged token:
curl -s -o /dev/null -w 'anonymous: %{http_code}\n' http://127.0.0.1/api/v1/users/me
curl -s -o /dev/null -w 'forged token: %{http_code}\n' http://127.0.0.1/api/v1/users/me \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJub2JvZHkifQ.not-a-valid-signature'
Expected output:
anonymous: 403
forged token: 401
Step 7: Check the Application and the Library Volume
curl -s http://127.0.0.1/api/v1/healthcheck
df -h /var/lib/booklore
free -m
swapon --show
The health endpoint reports the running version, the dedicated 64 GiB library volume is mounted, the whole stack sits comfortably inside 4 GB, and swapon --show prints nothing because the image ships with no swap at all.

Step 8: Sign in to the Console
Browse to http://<vm-ip>/ and sign in with the administrator username and password from the credentials file.

Step 9: Add Your Books
Create a library from the Libraries section of the sidebar and point it at a folder on the data volume, for example /var/lib/booklore/books. Copy your EPUB, PDF or CBZ files there and BookLore scans them, extracts titles, authors and covers, and shelves them.
sudo install -d -o booklore -g booklore -m 0750 /var/lib/booklore/books
ls -ld /var/lib/booklore/books /var/lib/booklore/bookdrop
Then copy your own files in and hand them to the booklore user, replacing the source path with your own:
sudo cp ~/my-collection/*.epub /var/lib/booklore/books/
sudo chown -R booklore:booklore /var/lib/booklore/books
You can also drop files into the watched auto import folder at /var/lib/booklore/bookdrop, or upload them straight from the browser with the upload button in the toolbar. The All Books view shows the shelved collection with covers, and the filter panel lets you narrow by author, format, read status, publisher or year.

Step 10: Read in the Browser
Click any book to open it in the built in reader. EPUB, PDF and comic formats all render in the browser, with per user reading position, bookmarks and a progress indicator, so you can stop on one device and resume on another.

Step 11: Settings, Users and Sync
Settings exposes reader preferences per format, metadata provider configuration, user administration, e-mail delivery, OIDC single sign on and the task manager. Create an account for each reader under Settings → Users and give them only the permissions they need; each user keeps their own shelves and reading progress.
BookLore also serves an OPDS catalogue and supports Kobo and KOReader sync, so the same library is reachable from a dedicated e-reader. Metadata fetching from providers such as Amazon, GoodReads, Google and Open Library requires outbound internet access from the VM; it is optional and disabled until you configure it.

Step 12: Server Components
| Component | Detail |
|---|---|
| Application | booklore.service, Spring Boot on Temurin JDK 25, 127.0.0.1:6060 |
| Web console | Angular 21, compiled into the same artifact and served by the application |
| Database | MariaDB 10.11, 127.0.0.1:3306, database and role booklore |
| Reverse proxy | nginx.service on TCP 80 |
| Library volume | 64 GiB disk mounted at /var/lib/booklore (books/, data/, bookdrop/) |
| Application jar | /opt/booklore/app.jar |
| Environment file | /etc/booklore/booklore.env (mode 640, generated at first boot) |
| Credentials | /root/booklore-credentials.txt (mode 600) |
| Corresponding source | /opt/booklore/src and /usr/share/booklore/source/ |
The Java heap is pinned at 1 GB and MariaDB's buffer pool at 192 MB so the two never contend on a 2 vCPU instance. If you move to a larger VM and want to raise the heap, edit ExecStart in /etc/systemd/system/booklore.service, then sudo systemctl daemon-reload && sudo systemctl restart booklore.
Step 13: The AGPL-3.0 Source Offer
BookLore is licensed under the GNU Affero General Public License version 3. Section 13 requires that anyone interacting with the program over a network is offered the corresponding source of the running version, and this instance satisfies that on disk with no network access required:
cat /usr/share/doc/booklore/SOURCE-OFFER.txt
cd /usr/share/booklore/source && sha256sum -c booklore-2.3.1-source.tar.gz.sha256
The complete unmodified v2.3.1 source tree that produced the running jar is at /opt/booklore/src, a pristine release tarball is at /usr/share/booklore/source/, the licence text is at /usr/share/doc/booklore/LICENSE, and the written offer names all of them.
cloudimg makes no modifications to BookLore itself. This image supplies only configuration, systemd units, an nginx vhost and the first boot credential bootstrap. One packaging decision is worth noting: the upstream container vendors a prebuilt unrar binary whose licence forbids reuse of its code, so this image deliberately omits it. BookLore detects its absence and falls back to its bundled pure Java RAR reader, so EPUB, PDF and CBZ are unaffected. If you need RAR5 era CBR files, install a RAR reader yourself and point BOOKLORE_UNRAR_BIN at it.

Step 14: Configuration and Logs
A quick health check of the whole stack:
sudo systemctl is-active mariadb booklore nginx
sudo nginx -t
For deeper investigation, read the service journals. These print a lot of output, so run them on their own:
sudo systemctl status booklore
sudo journalctl -u booklore -n 100 --no-pager
sudo journalctl -u booklore-firstboot --no-pager
The application reads its environment from /etc/booklore/booklore.env. After editing it, restart the service with sudo systemctl restart booklore.
Database maintenance reads this instance's password straight out of the credentials file, so there is nothing to type or paste:
sudo mariadb -u booklore \
-p"$(sudo sed -n 's/^booklore\.db\.pass=//p' /root/booklore-credentials.txt)" \
booklore -e "SELECT COUNT(*) AS books FROM book;"
To back up the library, snapshot the data volume and dump the database the same way:
sudo mariadb-dump -u booklore \
-p"$(sudo sed -n 's/^booklore\.db\.pass=//p' /root/booklore-credentials.txt)" \
booklore > ~/booklore-backup.sql
Troubleshooting
The console does not load. Check that booklore.service is active and that first boot completed: sudo systemctl is-active booklore && sudo journalctl -u booklore-firstboot --no-pager. The application waits for MariaDB and applies its schema migrations on first start, which takes around thirty seconds on a Standard_B2s.
A sign in attempt returns "A data conflict occurred". BookLore issues a refresh token whose validity timestamps have one second resolution, and stores it under a uniqueness constraint, so two sign ins for the same account inside the same second collide. Wait a second and sign in again.
Sign in returns "Too many failed login attempts". BookLore rate limits five failed attempts per source address and per username within fifteen minutes. Wait for the window to pass, or retrieve the current password from /root/booklore-credentials.txt.
Uploads or large books fail. The nginx vhost allows request bodies up to 4 GB and streams them straight through. If you front the instance with another proxy or a load balancer, raise the equivalent limits there too.
Metadata lookups return nothing. Metadata providers are third party services reached over the internet. Confirm the VM has outbound access and that the provider is enabled under Settings → Metadata.
Support
24/7 technical support is included with this image. Contact support@cloudimg.co.uk for help with deployment, library and shelf organisation, metadata providers, OPDS, Kobo and KOReader sync, bulk imports, user administration, TLS and custom domains, version upgrades, performance tuning and MariaDB administration.