Every deployment file advised setting PB_BOOTSTRAP=false "once the database
is established". That was harmless while the schema was static. It stopped
being harmless in 9bd5c52, which moved the plugin settings into a new
app_settings collection: a stack upgraded with the bootstrap off never gets
that collection, and a missing collection is deliberately read as "the
database is not ready" rather than "no plugins configured" - so the plugin
panel answers 503 indefinitely and the background retry spins forever.
Fixing the advice rather than the reading: treating a missing collection as
empty would let the first save write a fresh document over settings the
server had simply failed to find, which is the failure this whole line of
work exists to prevent.
So all four compose files, all four .env examples, both stack READMEs and
the AIO Dockerfile now say to leave the bootstrap on, including across
upgrades, and name the symptom an operator would otherwise have to guess
at. Turning it off is still supported, but framed as something to do only
for a database known to match the running release.
Compose files still parse as YAML; go build, go vet and go test ./... pass
(untouched by this commit - it is comments and docs only). Note that this
is guidance, not a guard: an operator who sets PB_BOOTSTRAP=false anyway
still ends up in the same place, and the server would have to re-run the
bootstrap when it finds the collection missing to make that impossible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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 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
cd "Docker-AIO"
cp .env.example .env # then edit — PB_ADMIN_* have no safe defaults
docker compose up -d --build
Production, from the registry:
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/:
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:
- PocketBase upserts its superuser from
PB_ADMIN_EMAIL/PB_ADMIN_PASSWORD. - The API Server waits for PocketBase to report healthy, then creates any
missing collections, reconciles existing ones, and creates the first app
superadminfromDRIVERVAULT_SUPERADMIN_EMAIL/_PASSWORD.
Leave
PB_BOOTSTRAPattrue, including across upgrades. A release can add a collection the server needs —app_settings, which holds the plugin settings, is one — and a stack that skipped the bootstrap never gets it. The plugin panel then answers503indefinitely, because a missing collection is read as "the database is not ready yet", never as "no plugins configured". 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 |
/data |
the .env the API Server panel rewrites when a superadmin retargets the PocketBase connection (plugin settings live in /pb/pb_data, with everything else) |
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
appuser; only nginx stays root, because it binds port 80. A crash ofsupervisordstill 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_VERSIONis 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 psrather than looking up.