Get started
Requirements
Bookplate runs as two Docker containers — the app and its Postgres database. You need:
- Docker with Compose. On a Mac or Windows PC, install Docker Desktop. On Linux, install Docker Engine and the Compose plugin.
- A 64-bit machine — Intel/AMD or ARM, so a Mac, a PC, a Raspberry Pi 4 or 5 (with a 64-bit OS), most NAS boxes, or a cloud server all work.
- About 300 MB of free memory while it runs, and about 750 MB of disk for the two images plus room for your covers and pasted images.
Check Docker is ready with docker compose version. It should print a version number.
Install
Make a folder for Bookplate
This folder holds its two config files. Your library itself lives in Docker volumes.
$ mkdir bookplate && cd bookplateDownload the compose file and the settings template
$ curl -fsSLO https://raw.githubusercontent.com/LeoPhh/bookplate/main/docker-compose.yml$ curl -fsSL https://raw.githubusercontent.com/LeoPhh/bookplate/main/.env.example -o .envFill in your settings
Open
.envin any text editor. Two values are required, and both should be long random strings. Generate them with:# paste this after AUTH_SECRET=$ openssl rand -base64 32# and this after DB_PASSWORD=$ openssl rand -hex 24Then set the address you’ll open Bookplate at. Your finished file looks like this:
AUTH_SECRET=q3F…your first random string…DB_PASSWORD=8c1e…your second random string…PUBLIC_URL=http://localhost:3000BOOKPLATE_PORT=3000REGISTRATION=closedKeep this file private, and keep it with your backups. You never type these values anywhere — they’re not your login.
Start it
The first start downloads the images, which takes a minute or two.
$ docker compose up -dCheck both containers are running — after a few seconds, each should say
healthy:$ docker compose psOpen it
Go to your
PUBLIC_URL—http://localhost:3000if you kept the default — and carry on with the first run.
First run
The first visit shows a Set up your library screen. Enter your name, an email address and a password of at least 8 characters, and choose Create account. This is the account that owns this Bookplate — after it exists, nobody else can sign up.
Use a real email address. Without email set up it’s only your username, but if you set up email later, it’s where password-reset links go.
Configuration
All settings live in .env. After changing one, run docker compose up -d to apply it.
| Setting | Default | What it does |
|---|---|---|
AUTH_SECRET | — | Signs your login cookie. Required. Changing it just signs everyone out. |
DB_PASSWORD | — | Password between the app and its database. Required. Set it once and never change it. |
PUBLIC_URL | http://localhost:3000 | The address you open Bookplate at. With https://, sign-in cookies are marked secure. |
BOOKPLATE_PORT | 3000 | The port on the host machine. |
REGISTRATION | closed | closed: only the first account can be created. open: anyone who can reach the server can create their own, separate library, from Create an account on the sign-in page. With email set up, new accounts confirm their address first. See Letting others sign up. |
SMTP_HOST | — | Your mail server. Setting it switches email on — see Email. |
SMTP_PORT | 587 | 587 for most providers; 465 for an encrypted-from-the-start connection. |
SMTP_SECURE | follows the port | true or false, only if your provider says to set it. |
SMTP_USER, SMTP_PASSWORD | — | The mail server’s login, if it needs one. |
SMTP_FROM | Bookplate <bookplate@localhost> | Who emails come from, e.g. Bookplate <books@example.com>. Use an address your provider lets you send from. |
STORAGE | local | s3 keeps images in an S3-compatible bucket, with the S3_* settings. See Storing images in S3. |
LOG_LEVEL | info | debug for more detail while chasing a problem; warn or error for less. See Troubleshooting. |
METRICS_TOKEN | — | Switches on /api/metrics: totals (accounts, active people, books by status…) for Prometheus or Grafana Alloy, read with this token. Only totals, nothing about anyone. |
BOOKPLATE_VERSION | latest | Pin a release, e.g. 0.2.0 or 0.2. See Updating. |
BOOKPLATE_IMAGE | leophh/bookplate | Set to ghcr.io/leophh/bookplate to pull from GitHub’s registry instead of Docker Hub. |
Bring in an existing library
From Goodreads or StoryGraph
Export your library
In Goodreads: My Books → Import and export → Export Library, then download the file once it’s ready. In StoryGraph: Manage Account → Export StoryGraph Library. Either way you get a
.csvfile.Preview it
In Bookplate, go to Settings → Import → From Goodreads or StoryGraph → Choose a CSV… and pick the file. Bookplate works out which service the file came from and shows what it found — how many books are read, reading and to read, which are new, and which are already in your library — before anything is saved.
Import
Choose Import. Titles, authors, ISBNs, page counts, statuses, ratings, finish dates and owned copies come across, and reviews and private notes become each book’s notes page.
Find covers (optional)
Exports don’t include covers, so Bookplate offers to look them up — on Open Library by ISBN, then iTunes. It fetches one every few seconds to stay within Open Library’s limits and tells you roughly how long it will take; keep the page open while it runs, or press Stop and carry on later. Books it can’t find a cover for keep their cloth cover.
Importing is safe to repeat. Each book is matched to one already in your library — by an earlier import, its ISBN, or its title and author — and updated rather than added twice. Its reading details come from the export, while its cover, colour, genre and existing notes stay as they are.
From another Bookplate
Go to Settings → Import → Choose a zip… and pick either a Bookplate export (from Export library on any Bookplate) or a zip of the data folder from the original, git-based version of the app.
Books, covers, notes, pasted images and vocabulary all come across. Books and words with the same ids are updated; nothing already in your library is removed, so importing the same file twice is harmless.
Run it
Updating
From your Bookplate folder:
$ docker compose pull$ docker compose up -d
The first command downloads the newest release; the second replaces the app container with it, keeping your data and settings. Database changes are applied automatically when the new version starts. The version you’re on is shown at the bottom of Settings.
To upgrade on your own schedule, pin a version in .env: BOOKPLATE_VERSION=0.2 gets bug fixes for 0.2 but never jumps to 0.3. What changed in each release is listed on GitHub.
Backups and restoring
The easy way: an export
Settings → Export library downloads a zip of your whole library — books, notes, pasted images, covers and vocabulary — as plain JSON, Markdown and image files. Settings → Import reads it back into any Bookplate. It doesn’t include your account, password or profile photo.
A full server backup
This saves everything, every account included. From your Bookplate folder:
$ docker compose exec -T postgres pg_dump -U bookplate bookplate > bookplate-db.sql$ docker compose cp bookplate:/data/uploads ./bookplate-uploads
Keep bookplate-db.sql, the bookplate-uploads folder and your .env together, somewhere other than the same machine.
With images in S3, skip the second command (and the image step when restoring): the images are in your bucket. Back it up with your provider’s tools, or turn on its versioning.
Restoring a full backup
On a fresh install that uses the same .env, with the backup files in the Bookplate folder, and before starting the app:
$ docker compose up -d --wait postgres$ docker compose exec -T postgres psql -q -U bookplate -d bookplate < bookplate-db.sql$ docker compose run --rm --no-deps --user root -v "$PWD/bookplate-uploads:/backup:ro" bookplate sh -c 'cp -a /backup/. /data/uploads/ && chown -R node:node /data/uploads'$ docker compose up -d
That starts only the database, loads your data into it, copies your images back into place, then starts Bookplate.
Email and forgotten passwords
Email is optional. Without it, Bookplate works exactly the same, and forgotten passwords are reset on the server. With it, people reset their own passwords from the sign-in page.
Resetting a forgotten password
On the server — always works, email or not. From your Bookplate folder:
$ docker compose exec bookplate reset-password you@example.com
It asks for the new password twice (without showing it) and signs out every device. Only someone with access to the server can run it.
By email — with email set up, choose Forgot password? on the sign-in page and enter your address. The link in the email works once, expires after an hour, and signs out every device when used. Without email, the same page explains the command above.
Setting up email
Bookplate sends email through SMTP, which almost every email service offers. Add your provider’s details to .env and run docker compose up -d:
SMTP_HOST=smtp.example.comSMTP_PORT=587SMTP_USER=you@example.comSMTP_PASSWORD=an app password from your providerSMTP_FROM=Bookplate <you@example.com>
| Provider | Host and port | Login |
|---|---|---|
| Gmail | smtp.gmail.com, 587 | Your Gmail address and an app password (needs 2-step verification on the account) |
| Fastmail | smtp.fastmail.com, 465 | Your address and an app password |
| Resend | smtp.resend.com, 587 | User resend, password = an API key; send from a domain you’ve verified there |
Your provider’s help pages have the exact values. Then go to Settings → Email and choose Send a test email: it either arrives, or Bookplate shows what the mail server said.
Bookplate sends at most three emails of each kind to an address every 15 minutes, and the reset form never reveals whether an address has an account.
Storing images in S3
Covers, pasted note images and profile photos are kept on disk, in Docker’s uploads volume. That’s all most people need. If you’d rather keep them in object storage — Scaleway, Cloudflare R2, Backblaze B2, AWS S3, or a NAS running Garage or RustFS — create a bucket and an access key that can read and write it, then add to .env and run docker compose up -d:
STORAGE=s3S3_ENDPOINT=https://s3.fr-par.scw.cloudS3_REGION=fr-parS3_BUCKET=bookplate-imagesS3_ACCESS_KEY_ID=your access keyS3_SECRET_ACCESS_KEY=its secret
| Variable | Default | What it does |
|---|---|---|
S3_ENDPOINT | AWS | Your provider’s S3 address. Leave it out for AWS. |
S3_REGION | us-east-1 | The bucket’s region, e.g. fr-par. R2 uses auto. |
S3_BUCKET | — | The bucket’s name. Create it first; Bookplate won’t. |
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY | — | The access key. |
S3_FORCE_PATH_STYLE | false | Set to true for servers you run yourself (Garage, RustFS, SeaweedFS). |
S3_PREFIX | — | A folder inside the bucket, if it’s shared with other things. |
The bucket stays private: Bookplate fetches the images itself and only shows them to whoever owns them. If a setting is wrong, Bookplate won’t start, and docker compose logs bookplate says which part — the bucket name, the key, or the address.
Letting others sign up
With REGISTRATION=open, anyone who can reach your Bookplate can make an account. To keep that manageable, Bookplate:
- Checks sign-ups invisibly. While someone fills in the sign-up form, their browser solves a small puzzle — a moment’s computing for a person, but slow and costly for a script creating accounts in bulk. There are no picture puzzles, and nothing is sent to anyone else.
- Allows five new accounts an hour from one address.
- Has new accounts confirm their email, if email is set up, and removes accounts that never confirm after 7 days.
- Can limit each account, so nobody fills your disk or bucket on their own.
| Variable | Default | What it does |
|---|---|---|
LIMIT_BOOKS, LIMIT_WORDS | no limit | The most books, and vocabulary words, one account may have. |
LIMIT_STORAGE_MB | no limit | The most image space one account may use: covers, pasted images and profile photo together. |
LIMIT_UPLOAD_MB | 8 | The largest single image. |
LIMIT_IMPORT_MB | 1024 | The largest export zip or CSV file that can be imported. |
TRUSTED_PROXIES | 1 | How many reverse proxies stand in front of Bookplate. Set 2 if there are two, e.g. Cloudflare in front of Caddy, so the per-address limit sees each visitor’s real address. |
SIGNUP_BOT_CHECK | true | false turns the invisible check off. |
CONTACT_EMAIL | — | Your address, sent to Open Library with book searches. They allow servers that give a contact three times as many searches, which helps when many people use yours. |
Using it from other devices
On your home network
Open http://<your-computer’s-IP>:3000 from a phone or another computer on the same network. On a Mac, the IP is under System Settings → Wi-Fi → Details. Set PUBLIC_URL to that address if you’ll mostly use it from there.
From anywhere, privately
Tailscale (free for personal use) puts your devices on a private network, so you can reach Bookplate from anywhere without exposing it to the internet. Install it on the machine running Bookplate and on your phone, then use the machine’s Tailscale address.
On the public internet
Put Bookplate behind a reverse proxy that provides HTTPS. With Caddy and a domain pointing at your server, this is the whole config:
books.example.com {reverse_proxy localhost:3000}
Then set PUBLIC_URL=https://books.example.com and run docker compose up -d.
Keeping it running
Both containers restart automatically whenever Docker is running — after a crash, or after the machine reboots. On a Mac or PC that means Docker Desktop has to be open: turn on Settings → General → Start Docker Desktop when you sign in. While a laptop sleeps, Bookplate is paused and picks up again on wake.
For a library you can reach at any hour, run it on something that’s always on — a Raspberry Pi, a NAS or a small cloud server.
Useful commands, from your Bookplate folder:
# is it running, and healthy?$ docker compose ps# the app's live logs (Ctrl+C to leave)$ docker compose logs -f bookplate# memory and CPU use$ docker stats# stop it, and start it again$ docker compose stop$ docker compose start
Docker Desktop shows the same under Containers → bookplate, with logs and live stats for each container.
Troubleshooting
- Signing in fails with “Invalid origin”
PUBLIC_URLdoesn’t match the address in your browser — often the case behind a reverse proxy. Make them match, then rundocker compose up -d.- “Port is already allocated” when starting
- Another program uses that port. Change
BOOKPLATE_PORTandPUBLIC_URLas in Install. - The app keeps restarting or shows as unhealthy
- Look at
docker compose logs bookplate: when Bookplate can’t start, its last line says why (“Bookplate couldn’t start: …”). A database connection error almost always meansDB_PASSWORDchanged after the first start — put the original back. - With S3 storage, the app won’t start
docker compose logs bookplatenames the problem: the bucket doesn’t exist, the key was refused, or the storage server couldn’t be reached. Check theS3_*settings — self-run servers usually also needS3_FORCE_PATH_STYLE=true.- I forgot my password
- Run
docker compose exec bookplate reset-password you@example.comon the server, or — with email set up — use Forgot password? on the sign-in page. See Email and forgotten passwords. - Emails don’t arrive
- Use Settings → Email → Send a test email: it shows the mail server’s error if sending fails. Check your spam folder, and that
SMTP_FROMis an address your provider lets you send from. Reset and confirmation emails never show errors on screen (that would reveal which addresses have accounts), butdocker compose logs bookplatedoes. - Book search finds nothing, or covers don’t load
- Search and covers come from Open Library (with iTunes as a fallback for covers), fetched by your server. If your server can’t reach them, add the book by hand and upload or paste a cover instead.
- Something else
- Open an issue on GitHub with what you did, what happened, and the output of
docker compose logs bookplate. If the app showed a reference (e.g. “reference 7f3a9c12”), include it: every log line from that request carries it.
Use it
Adding books
Choose + Add book. Start typing a title or author in the search box: results come from the Open Library catalogue. Picking one fills in the title, author and page count, fetches the cover, and opens it in the cropper.
- Covers can also be uploaded, or pasted from the clipboard with ⌘ V (Ctrl V) anywhere in the dialog. Every cover goes through the cropper, which keeps book proportions, and is compressed before it’s saved.
- No cover? The book gets a cloth-bound cover in one of twelve colours instead.
- Each book also has a genre, a source (book store, Kindle, audiobook, borrowed, second hand, gifted or library), a status (Read, Reading or TBR), whether you own a copy at home, a rating and the date you finished it.
- Everything can be typed in by hand too — the catalogue is optional.
Browsing your library
- Covers shows a wall of covers; Ledger shows a table with status, rating and dates. Both show the most recently finished books first.
- The search box matches titles, authors, genres and notes. The All · Read · Reading · TBR chips filter by status.
- Show notes puts a bookmark ribbon on every book that has notes.
- Click a book to see its details. The pencil icon edits it; Delete removes it along with its notes, pasted images and cover.
Finishing a book
For a book you’re reading, open it and choose Finished Reading. Pick the date you finished and a rating, then Mark as read. It moves to Read and counts towards your statistics.
Notes
Every book has its own notes page — open a book and choose Notes, then Edit. The editor turns Markdown into formatting as you type:
| Type | To get |
|---|---|
#, ##, ### and a space | Headings |
**bold**, *italic*, ~~struck~~, `code` | Inline styles |
- or 1. and a space | Bulleted or numbered list |
> and a space | Quote |
The toolbar does the same, plus links, callout boxes and code blocks. Paste or drop images straight into the text; large ones are downscaled first.
Save keeps your changes; Cancel throws them away, including any images pasted since the last save. Images you delete from a note are cleaned up on the next save.
Vocabulary
The Vocabulary tab keeps the words you learn while reading. Type a word and Bookplate looks it up on Wiktionary, listing each meaning with its part of speech and an example. Add the meaning you were after — or, if the dictionary has nothing, write your own definition.
Tag a word with the book you found it in, and it also appears on that book’s notes page. The list can be searched, sorted and filtered by book.
Statistics
The Statistics tab counts up your reading: books read, read this year, pages turned and average rating, with charts of books per year, ratings, genres and sources.
Everything drills down: click a year, genre, rating or source to see the books behind it, highest rated first, and click any of those books to open it in your library.
Your account
Your profile picture sits in the top-right corner of every page. Click it for Settings and Sign out. In Settings you can:
- Profile — add, crop or remove a profile photo.
- Export and Import your library — see Backups.
- Password — change it. Every other device you’re signed in on is signed out. Forgot it? See Email and forgotten passwords.
- Email — whether this server can send email, with a button to send yourself a test.
- Danger zone — delete your account and everything in it, after confirming your password. This can’t be undone, so export first. If it was the only account, the next visit shows the setup screen again.
Reference
Privacy and your data
- Your library lives only on the machine running Bookplate, in its Postgres database and uploads volume — or, if you choose, with images in your own S3 bucket.
- Everything is behind your login — covers and images included.
- Bookplate sends no analytics or telemetry. The only outside services it contacts are Open Library and iTunes (book search and covers) and Wiktionary (definitions), and only when you use those features — through your server, so your browser never talks to them directly. None need an account or API key.
- You can take everything with you at any time with Export library.
FAQ
- Can my family use it too?
- Yes — set
REGISTRATION=open, rundocker compose up -d, and each person chooses Create an account on the sign-in page for their own, separate library. With email set up, they confirm their address first. Anyone who can reach the server can sign up while it’s open, so switch it back toclosedafterwards — or see Letting others sign up to keep it open safely. - Can I bring my books over from Goodreads or StoryGraph?
- Yes — export your library there and import the file under Settings → Import. See Bring in an existing library.
- Is there a phone app?
- Bookplate works in your phone’s browser, with tabs along the bottom. Add it to your home screen from the browser’s share menu and it opens like an app.
- Is there a hosted version?
- Not at the moment — Bookplate is self-hosted only, so your library stays yours.
- What does it cost?
- Nothing. Bookplate is open source under the Apache 2.0 license. The code is on GitHub.