# 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. Set `PB_BOOTSTRAP=false` to skip once the database is established. 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 | | `api_data` | the API Server's `plugins.json`, and the `.env` the panel rewrites when a superadmin retargets the PocketBase connection | Both default to Docker-managed named volumes. In the prod file, set `PB_DATA` / `API_DATA` to absolute host paths for bind mounts instead. > The API Server serves as the unprivileged `app` user, so `/data` has to be > writable by it. Its entrypoint arranges that itself: it starts as root, takes > ownership of `/data` if `app` does not already hold it, then drops privileges. > So a **bind mount** to a root-owned host path needs no manual `chown`, and > neither does a **named volume** left over from an image that ran as root. The > one case it cannot fix is a container forced to another user (`user:` in > compose, `docker run --user`), where the entrypoint has no privileges to > `chown` with — prepare the host directory yourself there. > > If `/data` is still unwritable the server says so at boot, with > `WARNING: plugin changes will NOT survive a restart`. That warning is worth > watching for: the alternative symptom is plugins that enable normally in the > panel and come back disabled after the next redeploy. PocketBase runs as > root, so `PB_DATA` is unaffected either way. ## 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.