# DriverVault — Docker AIO (all-in-one image) **PocketBase + API Server + Web App in a single container**, supervised by `supervisord` with nginx serving the SPA and proxying `/api/` to the API Server on localhost. One image, one volume set, no compose network — the simplest way to stand DriverVault up on a single host. Prefer the three-container stack in [`../Docker`](../Docker) when you want to scale, upgrade or restart the pieces independently. ``` :80 nginx ─► SPA, and /api/ ─► API Server on 127.0.0.1:8080 ─► PocketBase on :8070 ``` | File | Use | |---|---| | `Dockerfile` | the all-in-one image (build context must be the **repo root**) | | `docker-compose.yml` | **builds from source** — for development and local testing | | `docker-compose.prod.yml` | **pulls the prebuilt image** from the registry | | `.env.example` / `.env.prod.example` | copy to `.env` for the matching compose file | ## Run it ```bash cd "Docker-AIO" 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/_/`. Note the port mapping: inside the container the web app is on **80**, published as `WEB_PORT` (8090 by default) to line up with the other deployment. ## Building by hand The build context **must be the repo root** so the Dockerfile can reach both `API Server/` and `Web App/`: ```bash docker build -f "Docker-AIO/Dockerfile" -t drivervault-aio . ``` Build args: `VITE_API_BASE` (leave empty so the bundle uses same-origin `/api`) and `PB_VERSION`, which the Dockerfile already pins. Override it to build a different PocketBase; set it to an empty string to resolve the latest release at build time instead. ## First boot Identical to the multi-container stack, and idempotent: 1. PocketBase upserts its superuser from `PB_ADMIN_EMAIL` / `PB_ADMIN_PASSWORD`. 2. The API Server waits for PocketBase to report healthy, then creates any missing collections, reconciles existing ones, and creates the first app `superadmin` from `DRIVERVAULT_SUPERADMIN_EMAIL` / `_PASSWORD`. > 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. ## Volumes | Volume | Holds | |---|---| | `/pb/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 this one path backs up the whole image. It defaults to a Docker-managed named volume; set `PB_DATA` to an absolute host path in the prod file for a bind mount. > 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 the corresponding environment variables to change them > permanently. ## Charger control (OCPP) Chargers in own/proxy mode dial in to `/ocpp/{serial}` on the **API Server port (8080)** — not through nginx — authenticating with a per-charger control token in an OCPP Basic-auth header. Because a plaintext `ws://` would expose that token, `OCPP_REQUIRE_TLS` defaults to `true`. This image serves plain HTTP, so charger control needs TLS terminated in front of it, with `OCPP_PUBLIC_URL` set to the public `wss://` base. Only drop `OCPP_REQUIRE_TLS` on a trusted network. ## Caveats - PocketBase and the API Server run as the unprivileged `app` user; only nginx stays root, because it binds port 80. A crash of `supervisord` still takes all three services down together — that is the trade for the simplicity. - Logs from all three processes are interleaved on the container's stdout/stderr (`docker logs drivervault-aio`). - `PB_VERSION` is pinned in the Dockerfile so two builds of the same source agree. Setting it to an empty string restores the old behaviour of resolving the latest release **at build time**, which also costs an unauthenticated GitHub API call and is subject to that 60/hour per-IP rate limit. - The container reports health once all three processes answer their probes, so a wedged component shows up in `docker ps` rather than looking up.