c173ca3 gave the API Server image a root entrypoint that takes ownership of
/data and then drops to app via su-exec, which left the Volumes note in
Docker/README.md telling operators to do by hand something the image now
does for them.
The replacement says what actually happens, and keeps the two cases the
entrypoint cannot cover: a container forced to another user (`user:` in
compose, `docker run --user`) has no privileges to chown with, so its host
directory still needs preparing. It also points at the new boot warning as
the thing to look for, since the alternative symptom - plugins that enable
normally in the panel and come back disabled after a redeploy - gives no
hint about permissions.
Also states that PocketBase runs as root, so PB_DATA never had this
problem; the old note read as though it applied to both volumes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
4.5 KiB
Markdown
102 lines
4.5 KiB
Markdown
# 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.
|