# DriverVault — Docker (multi-container stack) Three containers — **PocketBase**, **API Server**, **Web App** — on one compose network. This is the deployment to use unless you specifically want everything in a single image; for that see [`../Docker-AIO`](../Docker-AIO). ``` Browser ─► Web App BFF (:8090) ──/api/*──► API Server (:8080) ─► PocketBase (:8070) ``` Only the Web App port is meant to be public. The API Server and the PocketBase admin UI are published for convenience and, in the prod file, bound to `127.0.0.1` by default. | File | Use | |---|---| | `docker-compose.yml` | **builds from source** in this repo — for development and local testing | | `docker-compose.prod.yml` | **pulls prebuilt images** from the registry — for deployment | | `.env.example` / `.env.prod.example` | copy to `.env` for the matching compose file | | `pocketbase/` | the PocketBase image (official release binary on alpine) | ## Run it ```bash cd Docker cp .env.example .env # then edit — PB_ADMIN_* have no safe defaults docker compose up -d --build ``` Production, from the registry: ```bash cp .env.prod.example .env # then edit docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d ``` Then: web app on `http://host:8090/`, the API Server's superadmin panel on `http://host:8080/`, PocketBase admin on `http://host:8070/_/`. ## First boot Both steps are idempotent, so restarts and upgrades are safe: 1. **PocketBase** upserts its superuser from `PB_ADMIN_EMAIL` / `PB_ADMIN_PASSWORD`. This is the only way to create the first superuser — the REST API cannot bootstrap it. The API Server then authenticates with the same credentials. 2. **The API Server** creates any missing collections and reconciles existing ones, then creates the first app `superadmin` from `DRIVERVAULT_SUPERADMIN_EMAIL` / `_PASSWORD` if no such user exists. > Leave `PB_BOOTSTRAP` at `true`, including across upgrades. A release can add > collections or fields the server needs, and a stack that skipped the bootstrap > never gets them. The API Server self-heals exactly one thing — `app_settings`, > the collection holding the plugin settings, which it creates on demand because > it cannot serve the plugin panel without it. Every other schema change still > depends on this flag, so turn it off only for a database you know already > matches the release you are running. No manual `setup-pocketbase.mjs` step is needed here — the server runs the same schema reconcile itself. ## Volumes | Volume | Holds | |---|---| | `pb_data` | the PocketBase SQLite database and uploaded files — everything that persists | One volume, because the API Server keeps no state on disk. Plugin enable-state and global config live in the database like the rest of the settings, so backing up `pb_data` backs up the whole stack. It defaults to a Docker-managed named volume; set `PB_DATA` to an absolute host path in the prod file for a bind mount instead. PocketBase runs as root, so a root-owned host directory is fine. > One thing does **not** persist: the API Server panel's *Settings → PocketBase* > and *Settings → Web App* screens apply immediately but only for the life of the > container. Set `POCKETBASE_URL`, `PB_ADMIN_EMAIL` / `PB_ADMIN_PASSWORD`, > `WEBAPP_URL` and `CORS_ALLOW_ORIGINS` in `.env` to change them permanently — > in this stack the compose environment wins over anything the panel writes. ## Charger control (OCPP) Chargers in own/proxy mode dial in to `/ocpp/{serial}` **on the API Server port**, authenticating with a per-charger control token in an OCPP Basic-auth header. A plaintext `ws://` would put that token on the wire in the clear, so `OCPP_REQUIRE_TLS` defaults to `true` and non-TLS connections are rejected. This stack serves plain HTTP, so to actually use charger control you need to terminate TLS in a reverse proxy in front of it and set `OCPP_PUBLIC_URL` to the public `wss://` base (behind a proxy, deriving it from request headers is unreliable). `OCPP_REQUIRE_TLS=false` is for trusted networks only. You will also need `API_BIND` set so the proxy can reach the port. ## Notes - `docker-compose.yml` builds the API Server and Web App from `../API Server` and `../Web App`, so run it from this directory with the repo checked out. - The Web App's Vue bundle is built with an empty `VITE_API_BASE`, so the browser uses same-origin `/api` and the BFF proxies it — no CORS in play. - `CORS_ALLOW_ORIGINS` therefore only matters if a browser calls the API Server directly. Native mobile apps are not subject to CORS at all.