Bookhoarder ships as a single container. Object storage is the only persistence layer — no database, no schema, nothing to migrate — so deploying is really just: pick where the books live, then start the container.
Pick a storage backend
Local disk needs nothing extra; S3 or Cloudflare R2 need a bucket and credentials.
Run the container
docker run or docker compose up, with STORAGE_DRIVER and that
backend’s env vars set.
Open Bookhoarder
Visit http://localhost:3000 and create your first profile — it
becomes the admin.
Prerequisites
- Docker Engine 24+ (or Docker Desktop), with the Compose plugin if you’re
using
docker compose - A storage backend — local disk needs nothing extra; S3 or Cloudflare R2 need a bucket and a set of credentials
Run the container
Every backend runs through the same image — only the environment variables
change. STORAGE_DRIVER selects which one: local (default), or s3 for
any S3-compatible store, including Cloudflare R2.
docker run -d \
--name bookhoard \
-p 3000:3000 \
-v bookhoard_data:/app/.data \
-e STORAGE_DRIVER=local \
ghcr.io/bookhoard/bookhoarder:latestThe command above uses the local driver — see Local disk
for the volume-mount details. For S3 or
Cloudflare R2, swap the -e STORAGE_DRIVER=local
and -v flags for that backend’s environment variables; the rest of the
docker run stays the same.
Or use Docker Compose
For anything past a one-off docker run, put the environment in a
docker-compose.yml instead:
services:
app:
image: ghcr.io/bookhoard/bookhoarder:latest
ports:
- "3000:3000"
environment:
STORAGE_DRIVER: s3
S3_ENDPOINT: https://<ACCOUNT_ID>.r2.cloudflarestorage.com
S3_BUCKET: bookhoard
S3_ACCESS_KEY: <YOUR_ACCESS_KEY>
S3_SECRET_KEY: <YOUR_SECRET_KEY>
restart: unless-stoppedSwap the environment: block for whichever backend you picked — add a
volumes: entry instead if you’re using the local driver.
docker compose up -dHealth checks
The container exposes GET /api/health, which checks connectivity to the
configured storage backend and returns 200 {"status":"ok"} or
503 {"status":"error", "message": "..."}. Wire it into Compose or your
orchestrator:
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3Open Bookhoarder
Visit http://localhost:3000. The first profile you create becomes an
admin; additional profiles can be added from settings, with optional
per-profile passwords.
Environment reference
| Variable | Default | Notes |
|---|---|---|
STORAGE_DRIVER |
local |
local or s3 — see Storage for backend-specific vars |
SMTP_URL |
— | Enables send-to-e-reader |
OPENLIBRARY_CONTACT |
— | Email or contact URL for metadata/cover lookups |