name: drivervault-aio # Single all-in-one container, with external S3: PocketBase + API Server + Web # App (nginx) in one image, storing files in an S3 endpoint that already exists # somewhere else. Self-contained — one file, nothing to layer. # # cp .env.s3.example .env (then edit it — PB_S3_* especially) # docker compose -f docker-compose.s3.yml up -d --build # # The build context is the project root so the Dockerfile can reach both # "API Server/" and "Web App/". # # This is docker-compose.yml plus storage: PocketBase keeps its record files — # document scans, service and refill receipts, workshop invoices, part photos — # in that bucket instead of on the pb_data volume next to the database. The # database and PocketBase's own backups stay on pb_data. Clients cannot tell the # difference: an attachment has always been fetched through the API Server, # never from a storage URL. # # The bucket must already exist, and nothing here runs the gateway. For a # SeaweedFS that comes up with the container, use docker-compose.seaweedfs.yml. # # Before turning this on for a stack that already has uploads: PocketBase does # NOT copy existing files into the bucket. See README.md. services: drivervault: build: # Project root (one level up from this compose file). context: .. dockerfile: Docker-AIO/Dockerfile args: # Empty -> bundle uses same-origin "/api", proxied internally by nginx. - VITE_API_BASE=${VITE_API_BASE:-} # Bare name = pass through only when set in the environment, so an unset # PB_VERSION leaves the Dockerfile pin in place instead of overriding it # with an empty string (which would resolve "latest" at build time). - PB_VERSION image: drivervault-aio container_name: drivervault-aio restart: unless-stopped extra_hosts: # Lets PB_S3_ENDPOINT name the Docker host as host.docker.internal. # Harmless when the endpoint is somewhere else entirely. - "host.docker.internal:host-gateway" environment: # Superuser (also used by the API Server to authenticate to PocketBase). PB_ADMIN_EMAIL: "${PB_ADMIN_EMAIL:?set PB_ADMIN_EMAIL in .env}" PB_ADMIN_PASSWORD: "${PB_ADMIN_PASSWORD:?set PB_ADMIN_PASSWORD in .env}" # Match CORS to the web origin (only used if a browser calls the API directly). CORS_ALLOW_ORIGINS: "${CORS_ALLOW_ORIGINS:-http://localhost:8090}" # Probed by the panel status page. nginx serves the Web App on port 80 # inside this container, so plain localhost:8090 would never answer. # Override WEBAPP_URL in .env to make a change from the panel's Web App # screen permanent; the panel alone only holds it for the container's life. WEBAPP_URL: "${WEBAPP_URL:-http://127.0.0.1:80}" # Schema + super-admin bootstrap (idempotent). Leave this ON: a release can # add collections or fields the server needs, and a stack that skips 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. Turn it off only # for a database you know already matches the release. PB_BOOTSTRAP: "${PB_BOOTSTRAP:-true}" DRIVERVAULT_SUPERADMIN_EMAIL: "${DRIVERVAULT_SUPERADMIN_EMAIL:-}" DRIVERVAULT_SUPERADMIN_PASSWORD: "${DRIVERVAULT_SUPERADMIN_PASSWORD:-}" DRIVERVAULT_SUPERADMIN_NAME: "${DRIVERVAULT_SUPERADMIN_NAME:-Administrator}" # OCPP charger control (Anker Solix). This image serves plain HTTP, so a # charger can only connect when TLS is terminated in front of it (set # OCPP_PUBLIC_URL to the public wss:// base) — or, on a trusted network, # with OCPP_REQUIRE_TLS=false. OCPP_REQUIRE_TLS: "${OCPP_REQUIRE_TLS:-true}" OCPP_PUBLIC_URL: "${OCPP_PUBLIC_URL:-}" # --- File storage -------------------------------------------------- # supervisord passes these through to the API Server, whose bootstrap # writes them into PocketBase's # settings on every boot, idempotently. Only record files move — scans, # receipts, invoices, part photos. The database and PocketBase's own # backups stay on pb_data. The bucket must already exist: nothing here # creates it. PB_S3_ENABLED: "true" PB_S3_BUCKET: "${PB_S3_BUCKET:-drivervault}" PB_S3_ENDPOINT: "${PB_S3_ENDPOINT:?set PB_S3_ENDPOINT in .env}" PB_S3_REGION: "${PB_S3_REGION:-us-east-1}" PB_S3_ACCESS_KEY: "${PB_S3_ACCESS_KEY:?set PB_S3_ACCESS_KEY in .env}" PB_S3_SECRET: "${PB_S3_SECRET:?set PB_S3_SECRET in .env}" # true for SeaweedFS and MinIO, false for AWS S3 proper. PB_S3_FORCE_PATH_STYLE: "${PB_S3_FORCE_PATH_STYLE:-true}" ports: - "${WEB_PORT:-8090}:80" # Web App - "${PB_PORT:-8070}:8070" # PocketBase admin UI / API - "${API_PORT:-8080}:8080" # API Server + panel (root /) + /ocpp/{serial} volumes: # The only volume: the API Server keeps no state on disk, so everything # it owns — plugin settings included — lives in the database. - pb_data:/pb/pb_data healthcheck: # All three processes must answer. Declared here as well as in the image so # the check is visible, and works against an older pulled image. test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8070/api/health >/dev/null && wget -qO- http://127.0.0.1:8080/healthz >/dev/null && wget -qO- http://127.0.0.1:80/healthz >/dev/null || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 60s volumes: pb_data: