name: drivervault-aio # Production all-in-one with SeaweedFS split into its roles — and, unlike the # other prod files, built into ONE image here: Dockerfile.seaweedfs.split bakes # master, volume, filer, S3 gateway and the admin UI into the same container as # PocketBase, the API Server and the Web App. One service, one container, three # volumes. Self-contained: one file, no overlays. Everything an operator needs # to set lives in .env. # # 1. cp .env.prod.seaweedfs.split.example .env (then edit it) # 2. docker compose -f docker-compose.prod.seaweedfs.split.yml build # 3. docker compose -f docker-compose.prod.seaweedfs.split.yml up -d # # The build context is the repo root, so this has to run on a host that has the # checkout. To deploy elsewhere, `push` the built image to the registry named in # AIO_IMAGE and `pull` it there — `image:` and `build:` are both set, so the # same file does either. # # What the split buys inside one image: per-role restart under supervisord, # per-role metrics ports, and the SeaweedFS admin UI (weed admin) — a cluster # view, and Object Store → Users, where S3 identities are created and revoked # without touching a file. What it costs against the six-container shape it # replaces: a new SeaweedFS is a rebuild (SEAWEED_VERSION below), not a tag # change, and the roles cannot be spread over hosts. If the object store should # stay outside the app image, use docker-compose.prod.seaweedfs.yml — the S3 # behaviour is identical. # # The on-disk layout is the same as every other SeaweedFS shape here: master, # volume and filer share one data mount, exactly as `weed server -dir` lays it # out (master raft state, volume .dat/.idx, the filer's filerldb2/ — no filename # overlap). So a SEAWEED_DATA volume from docker-compose.prod.seaweedfs.yml # mounts here unchanged, and vice versa, with no migration either way. # # Inside the container every SeaweedFS role listens on loopback except the S3 # gateway and the admin UI, and those two are published on loopback by default, # the way the other SeaweedFS files publish them. The volume server serves file # content by id with no authentication at all, so it is never reachable from # outside; the admin UI shows what its port would. # # Before turning this on for a stack that already has uploads: PocketBase does # NOT copy existing files into the bucket. See README.md. # # On first boot the S3 gateway's program creates the bucket and seeds # PocketBase's identity into the filer's IAM store, PocketBase upserts the # superuser from PB_ADMIN_*, and the API Server creates any missing collections, # the DriverVault super-admin from DRIVERVAULT_SUPERADMIN_*, and points # PocketBase's file storage at the bucket. Every step is idempotent. services: drivervault: build: # Project root (one level up from this compose file), so the Dockerfile # can reach both "API Server/" and "Web App/". context: .. dockerfile: Docker-AIO/Dockerfile.seaweedfs.split args: # Empty -> bundle uses same-origin "/api", proxied internally by nginx. - VITE_API_BASE=${VITE_API_BASE:-} # Bare names = pass through only when set in the environment, so an # unset PB_VERSION / SEAWEED_VERSION leaves the Dockerfile pin in place # instead of overriding it with an empty string (which for PB_VERSION # would resolve "latest" at build time, and for SEAWEED_VERSION would # not build at all). - PB_VERSION - SEAWEED_VERSION # Tagged for the registry so `docker compose push` lands it where `pull` # on the deploy host expects it. image: "${AIO_IMAGE:-10.2.1.10:5500/admin/drivervault-aio-seaweedfs-split:latest}" container_name: drivervault-aio restart: unless-stopped 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 -------------------------------------------------- # The image fixes the rest (PB_S3_ENABLED, the loopback endpoint, path # style, the region): the gateway is inside, at an address that cannot # change. 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 credentials do double duty: the gateway's program seeds them into # the filer's IAM store as the identity named "drivervault" (updating it # in place on every boot — that is how a rotated secret lands) *and* they # are what PocketBase authenticates with. No safe defaults; the gateway # refuses to start without them, and with it the whole container. PB_S3_BUCKET: "${PB_S3_BUCKET:-drivervault}" 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}" # --- SeaweedFS admin UI -------------------------------------------- # An unauthenticated panel that can mint credentials for the bucket *is* # the bucket, so the password is required rather than defaulted — weed # leaves auth off entirely when it is empty, and the image refuses to # start the panel at all in that case. Read from WEED_ADMIN_* rather # than a flag, which keeps it off the process command line. WEED_ADMIN_USER: "${SEAWEED_ADMIN_USER:-admin}" WEED_ADMIN_PASSWORD: "${SEAWEED_ADMIN_PASSWORD:?set SEAWEED_ADMIN_PASSWORD in .env}" # Optional view-only login. weed ignores it unless the admin password # above is set, which it is. WEED_ADMIN_READONLY_USER: "${SEAWEED_ADMIN_READONLY_USER:-}" WEED_ADMIN_READONLY_PASSWORD: "${SEAWEED_ADMIN_READONLY_PASSWORD:-}" 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} # The S3 gateway, loopback only: PocketBase reaches it inside the # container, so this is here for `aws s3 ls --endpoint-url # http://127.0.0.1:8333` and nothing else. Set SEAWEED_S3_BIND=0.0.0.0 to # expose it, and mean it. - "${SEAWEED_S3_BIND:-127.0.0.1}:${SEAWEED_S3_PORT:-8333}:8333" # The admin UI, loopback for the same reason: storage plumbing, not one # of the app's own panels. On a remote host that means unreachable, so # set SEAWEED_ADMIN_BIND=0.0.0.0 and put it behind a reverse proxy. - "${SEAWEED_ADMIN_BIND:-127.0.0.1}:${SEAWEED_ADMIN_PORT:-23646}:23646" volumes: # Named volumes by default; set any of them to a host path in .env for a # bind mount. The API Server keeps no state on disk, so everything it # owns (plugin settings included) is in PB_DATA. Back up PB_DATA and # SEAWEED_DATA together — from here on the attachments live in the # second, not the first. - "${PB_DATA:-pb_data}:/pb/pb_data" - "${SEAWEED_DATA:-seaweed_data}:/seaweed/data" # The admin UI's own small volume: session key and maintenance state, no # object data. - "${SEAWEED_ADMIN_DATA:-seaweed_admin}:/seaweed/admin" healthcheck: # Every process must answer — the five SeaweedFS roles and the three app # processes. Declared here as well as in the image so the check is # visible, and works against an older pulled image. start_period covers # the SeaweedFS chain plus the first-boot schema bootstrap. test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9333/cluster/status >/dev/null && wget -qO- http://127.0.0.1:8081/healthz >/dev/null && wget -qO- http://127.0.0.1:8888/healthz >/dev/null && wget -qO- http://127.0.0.1:8333/healthz >/dev/null && wget -qO- http://127.0.0.1:23646/health >/dev/null && 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: 10s retries: 3 start_period: 90s volumes: pb_data: # Master, volume and filer share it — the same layout `weed server -dir` # writes, so this file and docker-compose.prod.seaweedfs.yml can swap places # on it. seaweed_data: # The admin UI's own session key and maintenance-task state. Small, and no # part of the object store. seaweed_admin: