murerkinn/bookshelf: a self-hosted ebook library on object storage
Self-hosted ebook library that runs on object storage - a Cloudflare Worker over R2, or a Node server over a directory. No database.
At a glance
- What is it?
- Bookshelf is a self-hosted EPUB and PDF library that runs as a Cloudflare Worker over R2 or as a Node server over a directory, with no database. Its README is unusually blunt about what it does not have: no authentication, no encryption, and last-write-wins profiles.
- Who is it for?
- Bookshelf fits someone with a folder of EPUBs and PDFs who wants a browser shelf on a trusted network, or a Cloudflare account and an R2 bucket, and who accepts that anyone reaching the URL can download everything. It does not fit a public-facing library, a household where two people read the same profile on two devices, or a Windows machine, since the README says the sync tool finds its image tools with which.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What murerkinn/bookshelf solves, and who it is for
The project targets a narrow case: you already own EPUBs and PDFs, they sit in a folder, and you want to read them in a browser without running Calibre's server or a database. The README states the pitch directly: put your books in a folder, publish them, and read them in any browser, or on a Kobo through the OPDS catalog. There is no database in the stack. The library is a tree of files in object storage, which is why the same code can serve from Cloudflare R2 or from a directory on your own machine.
That design choice sets the audience. Someone with a few hundred books and a VPS or a Cloudflare account gets a shelf with covers, a searchable list, a profile switcher, and a Continue button on the book being read. Someone who wants user accounts, per-user lending, or a public catalog should look elsewhere, because the README says there is no authentication at all.
The repository is a TypeScript monorepo with npm workspaces under apps/* and packages/*, and it requires Node 24 or newer. Windows is not supported, and the README gives the reason: the sync tool looks for its image tools with which.
How the library is built: sync, storage providers, and a tree of files
The mechanism is a sync tool that reads a books/ directory, extracts metadata and covers, and writes a published library into a storage provider. Two providers ship in the repository: provider-fs for a directory, and provider-r2 for Cloudflare R2. The README describes storage providers as the extension point, and notes that a provider does not have to live in this repository: a package published by anyone can be installed and named in the config. That is the most interesting architectural decision here, because it means the library format is the contract, not the server.
Configuration is split between bookshelf.config.json and environment variables. The README's table lists BOOKSHELF_READ_ONLY, BOOKSHELF_PROVIDER, BOOKSHELF_DIRECTORY and BOOKSHELF_SITE_URL. The first is the one that matters operationally: set it to 1 and storage keeps serving but stops accepting. Profiles cannot be added, renamed or deleted, and reading positions stay in the browser.
The README is explicit that object keys are slugified titles, so a listing of your storage names your shelf. That is a real consequence of the no-database design: there is nothing to query, and nothing to index beyond the files themselves.
Installing Bookshelf with Docker and publishing a first book
The README calls Docker the shortest route, and the compose file confirms why: the image installs poppler-utils and webp so the sync tool can generate covers. Without them, the Dockerfile says the sync tool degrades quietly, producing full-size covers and no covers for PDFs.
Start by putting a book where the sync container can read it. The compose file mounts ./books read-only and a named volume called library at /data.
mkdir books && cp ~/Downloads/*.epub books/
docker compose run --rm sync --create
docker compose up -dThe first command creates the folder and copies your EPUBs in. The second runs the one-shot sync container, which is not started by docker compose up because it sits behind the sync profile. The third starts the app, and the README says the shelf is then on http://localhost:3000. Arguments after docker compose run --rm sync are appended to the entrypoint, so --force and --dry-run work as they do locally.
If you bind-mount a host directory instead of using the named volume, chown it first:
chown -R 1000:1000 /srv/bookshelfFor a local run without Docker, the README gives a filesystem config and three commands. The checked-in bookshelf.config.json points at R2, so it has to be switched before npm run sync goes anywhere local.
// bookshelf.config.json
{ "storage": { "provider": "fs", "directory": "shelf-data" } }npm run sync -- --create
npm run build
npm start -w @bookshelf/appThere is also npm run demo, which the README says writes nine generated public-domain books into books/, eight EPUBs and a PDF, and downloads nothing. It needs Node 24 or newer.
No authentication, no encryption, and last-write-wins profiles
The README devotes a section to what to know before committing a library, and it is worth reading twice. Anyone who can reach your shelf can download every book in it, and pick any profile while doing it. The OPDS catalog makes the library machine-enumerable as well. The stated mitigation is to put it on a network you trust or behind something that asks who is calling, plus BOOKSHELF_READ_ONLY=1 on anything strangers can reach.
Nothing is encrypted. The library is stored in the clear, and keys are slugified titles. The README points to encrypting your library at rest for the filesystem provider only. On R2, that guidance does not apply, since R2 is not a volume you control at the filesystem level.
The third limitation is concurrency. Two devices reading one profile at once is last-write-wins, which means reading positions can be overwritten rather than merged. For a single reader switching between a laptop and a phone, that is usually invisible. For two people sharing a profile, it is a data-loss path with no warning.
The README also links to a roadmap document for the full list of what is missing, so the limitations are documented rather than discovered.
Where Bookshelf is the wrong tool, and what to use instead
Bookshelf is the wrong tool if you need accounts, per-user shelves, or a library reachable from the open internet without an authenticating proxy in front. The project says so itself. It is also the wrong tool on Windows, and for anyone who wants the sync tool to fetch metadata from an online source, since the README describes a local publish step over files you already have.
Calibre and its content server are the obvious alternative, and the difference is architectural rather than cosmetic. Calibre stores the library in a managed database with its own metadata model, and its server can enforce user accounts and access levels. Bookshelf has no database at all: the published tree is the library, and the app is a reader over object storage. That makes Bookshelf easier to back up and cheaper to host on R2, and it makes Calibre the better answer when you need the metadata editing, conversion, and multi-user controls that a database-backed tool provides.
Kavita is another comparison point in the self-hosted reading space, but the repository does not describe it, so the honest comparison is with Calibre's server, which the project's own package keywords name alongside r2 and calibre.
Maintenance, licence, and what a backup actually covers
The repository is MIT licensed. The README notes that this covers the code in the repository, and that the app and the Docker image also ship other people's software under their own terms, listed in THIRD-PARTY-NOTICES.md. It also states plainly that none of it says anything about the books you put in a library built with it. That is a factual boundary, not a legal opinion, and it is the correct one to draw.
The last push to the default branch was on 2026-09-11. There are no retrieved releases, and the root package.json carries version 0.1.0, so this is pre-1.0 software where the library format and the config keys are still moving. Upgrading means rebuilding and re-syncing rather than running a migration, because there is no database to migrate.
Operationally, the compose file names a library volume that holds your published books and your reading positions. That volume is the backup target, and the README says so. The books/ folder you sync from is not the library. If you lose the volume, you lose the published tree and every profile's position, and the recovery path is re-running sync against your source books.
Editorial conclusion
Bookshelf fits someone with a folder of EPUBs and PDFs who wants a browser shelf on a trusted network, or a Cloudflare account and an R2 bucket, and who accepts that anyone reaching the URL can download everything. It does not fit a public-facing library, a household where two people read the same profile on two devices, or a Windows machine, since the README says the sync tool finds its image tools with which. Before committing a library, verify three things: that bookshelf.config.json and apps/bookshelf/wrangler.jsonc name the same bucket and Worker, that the library volume is in your backup set, and that BOOKSHELF_READ_ONLY is set to 1 on anything strangers can reach.
Frequently asked questions
How do I install murerkinn/bookshelf?
The README gives three routes: Docker via docker compose run --rm sync --create followed by docker compose up -d, a local Node install with npm run sync -- --create, npm run build and npm start -w @bookshelf/app, or a Cloudflare Worker over R2 with npx wrangler login, npm run sync -- --create and npm run deploy. All need Node 24 or newer, and the README states Windows is not supported.
Does murerkinn/bookshelf have a login?
No. The README states there is no authentication, and that anyone who can reach the shelf can download every book in it and pick any profile while doing it. It suggests putting the shelf on a network you trust or behind something that asks who is calling, and setting BOOKSHELF_READ_ONLY=1 on anything strangers can reach.
How do I use murerkinn/bookshelf with a Kobo or other OPDS client?
The README points to an OPDS catalog document, docs/opds.md, for reading on a Kobo, a Kindle, or any OPDS client. The same section notes that the OPDS catalog makes the library machine-enumerable, which is part of why the project warns against exposing it.
Where does murerkinn/bookshelf store my library, and what should I back up?
It stores the published tree in a storage provider: a directory with the fs provider, or an R2 bucket with the r2 provider. With the Docker setup the README says to back up the library volume, which holds your published books and your reading positions.
Is murerkinn/bookshelf encrypted?
No. The README states that nothing is encrypted and the library is stored in the clear, with object keys that are slugified titles, so a listing of your storage names your shelf. It notes that with the filesystem provider you can keep the library on an encrypted volume today.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/murerkinn-bookshelf)