# Deploying Bookshelf's PocketBase server This assumes a spare always-on machine at home (a mini PC, NUC, Raspberry Pi, or an old laptop) running Linux. Pick **one** of the two run methods below — systemd or Docker — not both. ## 0. Get the files onto the server Copy the whole `server/` directory (minus `.dev-credentials`, which is gitignored and dev-only) to the target machine, e.g.: ```sh rsync -av --exclude .dev-credentials ~/bookshelf/server/ youruser@homeserver:/opt/bookshelf/ ``` The `pocketbase` binary is architecture-specific — if your home server isn't the same CPU architecture as wherever you built/downloaded it, grab the matching build from https://github.com/pocketbase/pocketbase/releases (this project is pinned to **v0.40.2**) and drop it in as `/opt/bookshelf/pocketbase`. ## 1. Run it — Option A: systemd (recommended for a bare-metal/VM host) 1. Create a dedicated unprivileged user: ```sh sudo useradd --system --home /opt/bookshelf --shell /usr/sbin/nologin bookshelf sudo chown -R bookshelf:bookshelf /opt/bookshelf ``` 2. Install the unit: ```sh sudo cp deploy/bookshelf.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now bookshelf ``` 3. Check it's up: `systemctl status bookshelf` and `curl http://127.0.0.1:8090/api/health`. 4. Logs: `journalctl -u bookshelf -f`. Note the unit binds PocketBase to `127.0.0.1:8090` only — it is **not** reachable from other machines yet. That's intentional; see step 4. ## 2. Run it — Option B: Docker ```sh cd deploy docker compose up -d --build docker compose logs -f ``` This also only publishes to `127.0.0.1:8090` on the host, for the same reason. Data persists in the `bookshelf_pb_data` named volume regardless of container restarts/rebuilds. ## 3. Provision the schema and accounts Once the server is up and answering on `127.0.0.1:8090`, run the setup scripts from `server/` (one directory up from here): ```sh cd /opt/bookshelf # If this is a brand-new PocketBase data dir, it prints a one-time setup URL # on first launch (see `journalctl -u bookshelf` or `docker compose logs`) — # open that in a browser first to create your superuser account. ./setup-schema.sh http://127.0.0.1:8090 # Then create the household accounts — there is no public sign-up: ./create-user.sh owner@example.com "a strong password" "Owner Name" ./create-user.sh spouse@example.com "a different strong password" "Spouse Name" ``` `setup-schema.sh` is idempotent — safe to re-run any time (e.g. after pulling an updated `server/` if the schema ever changes). ## 4. Reach it from outside your home network The app needs a URL it can hit from anywhere your phone goes — not just your home wifi. Two real options: ### Tailscale (recommended) Install Tailscale on the home server and on your phone, join both to the same tailnet. The server gets a stable `100.x.y.z` address (or a MagicDNS name like `homeserver.your-tailnet.ts.net`) reachable from anywhere, encrypted end-to-end, with **no ports opened on your router**. - **Pros:** essentially zero attack surface (nothing is exposed to the public internet at all — not even a login page), no TLS cert management, works behind CGNAT, five-minute setup. - **Cons:** both devices need the Tailscale app installed and signed in; if Tailscale's coordination service has an outage, *new* connections may be briefly unable to establish (existing ones keep working) — acceptable for a two-person home library, not for something needing five-nines. - Point the app's server-URL field at `http://100.x.y.z:8090` (Tailscale's encryption makes plain HTTP tolerable *inside the tailnet*, but see the HTTPS note below — using `https://` via Tailscale Serve, next, is easy enough to just do). - Even better: use [`tailscale serve`](https://tailscale.com/kb/1312/serve) to get automatic HTTPS with a real cert on your tailnet domain, so the app can just always use `https://`: ```sh sudo tailscale serve --bg 8090 ``` ### Port-forward + Caddy (reverse proxy with TLS) Forward a port on your router to the home server, and run [Caddy](https://caddyserver.com/) in front of PocketBase to terminate TLS with an automatic Let's Encrypt certificate. - **Pros:** works with any client, no extra app/agent needed on the phone, a real public HTTPS URL. - **Cons:** you're now running an internet-facing service from your home — bugs in PocketBase, Caddy, or your router's firmware are now a real attack surface; you need a domain name (or dynamic-DNS if your ISP gives you a changing IP) for Let's Encrypt's HTTP-01/TLS-ALPN-01 challenge to work; residential ISPs sometimes block inbound 80/443 or use CGNAT, which breaks this approach entirely (Tailscale sidesteps that). Minimal Caddyfile: ``` bookshelf.yourdomain.com { reverse_proxy 127.0.0.1:8090 } ``` Caddy handles the certificate automatically. Point your router's port forward at the Caddy host's 443, and the app's server-URL field at `https://bookshelf.yourdomain.com`. ### HTTPS is not optional The app sends the account password on every login. **Never** point the app at a plain `http://` URL that crosses the public internet or an untrusted network — only plain HTTP over Tailscale (which is already end-to-end-encrypted at the network layer) is acceptable, and even there, prefer `tailscale serve` for a real cert. If you go the port-forward route, Caddy above gets you HTTPS for free — don't skip it. ## 5. Back up regularly ```sh ./deploy/backup.sh ``` See the comments at the top of `backup.sh` for what it does (SQLite-safe snapshot + uploaded covers, tarball, prune old ones) and the restore procedure. Wire it into cron: ```sh crontab -e # add: 0 3 * * * /opt/bookshelf/deploy/backup.sh >> /var/log/bookshelf-backup.log 2>&1 ``` Consider also copying the resulting tarballs off-box (another machine, a USB drive, cloud storage) — a backup that lives on the same disk as the data it's backing up doesn't protect against disk failure.