Files
DriverVault/Docker-AIO/README.md
T
tajniak81andClaude Opus 5 f08849e50c Docker: a build context that isn't 3.6GB, and health you can see
The all-in-one image builds from the project root, and Docker only reads
.dockerignore from the context root — so the ones under "API Server" and
"Web App" never applied to it and every AIO build shipped the whole tree,
"Phone App/build" included. A root .dockerignore allow-lists the paths that
build actually copies.

The dev split stack passed neither PB_BOOTSTRAP nor the SUPERADMIN vars, so
it created the schema and then no user to log in with. It passes them now,
and .env.example says so.

WEBAPP_URL was never set anywhere, leaving the panel status page probing
localhost:8090 — itself — and always reporting the Web App as down. Each
compose file now points it at wherever the Web App really is, and the BFF
grew a real /healthz instead of letting the SPA fallback answer probes with
index.html and look healthy no matter what.

In the AIO, PocketBase and the API Server drop to an unprivileged user;
only nginx stays root to bind :80. The entrypoint takes ownership of the
two volumes first, so data written by the old root-only image stays
writable. All three images carry a HEALTHCHECK, every compose file declares
one too (so depends_on still gates against an older pulled image), and
web-app waits for the API Server to be serving rather than merely started.

Also: pinned alpine/golang/node and PocketBase 0.39.11, so a rebuild months
from now produces the same image; nginx forwards WebSocket upgrades instead
of stripping them, with the map in http.d where Alpine actually reads it;
and a .gitattributes keeps entrypoint.sh on LF, because a CRLF shebang from
a Windows clone fails at container start with "no such file or directory".

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

102 lines
4.0 KiB
Markdown

# 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`. Set
`PB_BOOTSTRAP=false` to skip once the database is established.
## Volumes
| Volume | Holds |
|---|---|
| `/pb/pb_data` | the PocketBase SQLite database and uploaded files |
| `/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; set `PB_DATA` / `API_DATA` to
absolute host paths in the prod file for bind mounts.
## 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.