# 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.
