Files
DriverVault/Docker/README.md
T
tajniak81andClaude Opus 5 9487de84b0 Docker AIO: a folder name without a space in it
The all-in-one folder is now Docker-AIO, so -f Docker-AIO/Dockerfile
resolves without quoting. Every path that pointed at the old name
follows it: the compose build stanza, the documented build commands,
and the links from the two READMEs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 20:01:19 +02:00

93 lines
3.9 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 container runs as an unprivileged user, and a **named** volume
> inherits that ownership from the image. A **bind mount** does not — the host
> directory's ownership wins, so `chown` it to the container's `app` user (or
> `chmod` it writable) before setting `API_DATA` to a host path, otherwise the
> server cannot write `plugins.json`.
## 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.