Files
geointel/deploy/unraid/README.md
T
Jens faeb58ef6d
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
Initial public release
2026-08-31 21:56:53 +02:00

369 lines
14 KiB
Markdown

# GeoIntel Unraid all-in-one container
GeoIntel can run on Unraid as one DockerMan-native container.
Inside that single container:
- embedded PostGIS stores the application database
- Alembic migrations run at startup
- FastAPI runs on internal `127.0.0.1:8000`
- nginx serves the React/MapLibre frontend on container port `80`
- nginx proxies `/api` and `/health` to the internal backend
The browser entrypoint is:
```text
http://<unraid-ip>:${GEOINTEL_FRONTEND_PORT}
```
The app icons are served from the same container:
```text
http://<unraid-ip>:${GEOINTEL_FRONTEND_PORT}/geointel-icon.svg
http://<unraid-ip>:${GEOINTEL_FRONTEND_PORT}/geointel-icon.png
```
## Files
- `docker-compose.unraid.yml`: config validation reference for the all-in-one image.
- `deploy/unraid/Dockerfile.all-in-one`: builds the single container.
- `deploy/unraid/all-in-one-start.sh`: starts embedded PostGIS, backend and nginx.
- `deploy/unraid/run-dockerman-container.sh`: starts/replaces the running container with DockerMan labels and editable Unraid metadata.
- `deploy/unraid/nginx-all-in-one.conf`: frontend and API proxy config for one container.
- `deploy/unraid/geointel.env.example`: copy to `.env` and edit ports/paths.
- `deploy/unraid/geointel-unraid-template.xml`: Unraid/DockerMan metadata for editable fields.
- `deploy/unraid/geointel-icon.svg`: frontend favicon source.
- `deploy/unraid/geointel-icon.png`: DockerMan/Unraid icon source.
- `frontend/public/geointel-icon.svg`: frontend-served SVG icon.
- `frontend/public/geointel-icon-180.png`: frontend PNG icon, also served at
the stable `/geointel-icon.png` compatibility URL by nginx.
The Tower deploy scripts also copy the editable DockerMan template to:
```text
/boot/config/plugins/dockerMan/templates-user/my-geointel.xml
```
and copy the PNG icon to:
```text
/boot/config/plugins/dockerMan/images/geointel-icon.png
```
The template name is `geointel` so it matches the running all-in-one container name. If the Unraid Docker page was already open, refresh it after deploy so DockerMan reloads the template/icon metadata.
`docker-compose.unraid.yml` also applies DockerMan labels to the running container:
```text
net.unraid.docker.managed=dockerman
net.unraid.docker.webui=http://[IP]:[PORT:80]/
net.unraid.docker.icon=/boot/config/plugins/dockerMan/images/geointel-icon.png
```
These labels are required because a plain Compose container can run correctly while still missing the normal Unraid edit/icon controls.
## First setup from the repo
From the Unraid shell:
```bash
cd /mnt/user/appdata
git clone gitea-widefrog:NuklearRabbit/geointel.git geointel
cd /mnt/user/appdata/geointel
cp deploy/unraid/geointel.env.example .env
nano .env
docker compose -f docker-compose.unraid.yml config
bash deploy/unraid/deploy-release.sh
```
Set `GEOINTEL_POSTGRES_PASSWORD` to a unique value before that first start.
Browser login is enabled by default for the packaged runtime. Configure one exact
`GEOINTEL_AUTH_USERNAME`, a `pbkdf2_sha256` password hash and an independent
random `GEOINTEL_AUTH_SESSION_SECRET` of at least 32 characters. The plaintext
password is never stored in the repository or container configuration. Browser
API calls require the signed HttpOnly session cookie; direct loopback calls to
the backend remain available to trusted in-container operator scripts.
Production startup fails before replacing the active container when the
password is empty or one of the documented defaults.
`GEOINTEL_AUTH_REQUIRE_HTTPS=true` is also the packaged default. Configure
`GEOINTEL_PUBLIC_BASE_URL` with the external HTTPS origin and terminate TLS at
the trusted reverse proxy; plaintext operator login is rejected.
Guest access is an explicit opt-in for dedicated public demo installations:
```env
GEOINTEL_GUEST_ACCESS_ENABLED=true
GEOINTEL_GUEST_DISPLAY_NAME=Gast
GEOINTEL_GUEST_SESSION_TTL_SECONDS=7200
```
The packaged default is `false`. Setting it to `true` adds **Als gast
verkennen** to the landing page. The generated guest cookie
is short-lived, project-scoped and limited to the canonical demo workflow.
Operator mutations and access to another project are rejected by the backend,
and the frontend hides management and task-starting controls. The mechanism is
not tenant isolation: never enable it on an instance that contains private,
customer or operational data. Deploy a separate demo container and storage
root for public or recruiter-facing access.
The repository deploy scripts run the same flow automatically. They validate
the Compose reference, preserve the current image under a unique
backup-specific `rollback-predeploy-*` tag, build the immutable production
`<commit-sha>-ai` tag plus `latest`, attest its exact local image ID, generate
an SBOM and enforce the Trivy policy before starting that same ID. An existing
matching tag is reused, never rebuilt. A failed start, live migration smoke or
browser/API smoke automatically attempts the previous image without changing
the configured PostGIS or storage paths.
`scripts/deploy_tower.sh` and `scripts/deploy_tower.ps1` source the remote
`.env` before building the image. Production deployment requires
`GEOINTEL_INSTALL_AI=true`; an explicit false value fails closed before the
image or running container is replaced.
Database credentials are runtime configuration, not image metadata. The
all-in-one image does not bake `GEOINTEL_POSTGRES_PASSWORD` into the Dockerfile;
set it through `.env`, the Unraid template or `docker run -e`.
Production images always include the pinned AI dependencies. Mount models
through `GEOINTEL_MODELS_PATH` and configure `YOLO_ENABLED=true` plus
`YOLO_MODELS_DIR=/app/models` and `YOLO_MODEL_PATH=/app/models/<model>.pt` only
when you have a local model file.
The AI-enabled image installs PyTorch/Ultralytics plus the native OpenCV runtime
libraries needed for Ultralytics imports; it still never downloads model weights.
The documented CPU runtime installs pinned `torch==2.13.0` and
`torchvision==0.28.0` from `https://download.pytorch.org/whl/cpu`, avoiding the
unused CUDA runtime wheels included by the general Linux package index. The
Dockerfile copies dependency metadata before backend source and applies
commit/build metadata only after the heavy file layers. Normal code-only
redeploys therefore reuse the apt, GIS and optional PyTorch dependency layers.
`YOLO_CONFIG_DIR` defaults to `/app/storage/ultralytics`, a writable persistent
path, so Ultralytics settings do not fall back to root user config directories.
To safely configure an existing local YOLO model on Tower, place one supported
model file (`.pt`, `.onnx` or `.engine`) under:
```text
/mnt/user/appdata/geointel/models
```
Then run a dry-run first:
```bash
cd /mnt/user/appdata/geointel
python scripts/configure_yolo_model.py \
--models-dir /mnt/user/appdata/geointel/models \
--env-file .env
```
Apply only after the selected host and container paths are correct:
```bash
python scripts/configure_yolo_model.py \
--models-dir /mnt/user/appdata/geointel/models \
--env-file .env \
--apply
bash deploy/unraid/run-dockerman-container.sh
```
If multiple model files are present, add `--model-file /mnt/user/appdata/geointel/models/<name>.pt`.
The helper does not download weights, does not load a model and does not run
inference; it only updates the env file for the mounted local model.
Validate liveness, dependency readiness and the canonical API:
```bash
curl -fsS "http://192.0.2.10:${GEOINTEL_FRONTEND_PORT:-1202}/health/live"
curl -fsS "http://192.0.2.10:${GEOINTEL_FRONTEND_PORT:-1202}/health/ready"
curl -fsS "http://192.0.2.10:${GEOINTEL_FRONTEND_PORT:-1202}/api/v1/system/capabilities"
curl -fsS "http://192.0.2.10:${GEOINTEL_FRONTEND_PORT:-1202}/api/v1/projects"
curl -I "http://192.0.2.10:${GEOINTEL_FRONTEND_PORT:-1202}/geointel-icon.svg"
curl -I "http://192.0.2.10:${GEOINTEL_FRONTEND_PORT:-1202}/geointel-icon.png"
```
The live migration smoke also checks PostgreSQL database collation metadata.
When reusing a PostGIS volume created by an older Debian/glibc runtime, it may
print `COLLATION_VERSION_MISMATCH`. This is a maintenance warning, not an app
startup failure. Review backups first, then acknowledge the new runtime
collation version inside the running container:
```bash
docker exec -it geointel psql -U "${GEOINTEL_POSTGRES_USER:-geointel}" -d "${GEOINTEL_POSTGRES_DB:-geointel}"
ALTER DATABASE "geointel" REFRESH COLLATION VERSION;
```
If you rely on text indexes with locale-specific ordering, plan a maintenance
window and rebuild the affected indexes before acknowledging the version. The
current GeoIntel V1 spatial workflows primarily use UUIDs, JSON metadata and
PostGIS geometry indexes, but the warning should still be tracked explicitly.
## Change the browser port
Edit `.env`:
```env
GEOINTEL_FRONTEND_PORT=1203
GEOINTEL_CORS_ORIGINS=http://localhost:1203,http://127.0.0.1:1203,http://192.0.2.10:1203
```
Apply:
```bash
bash deploy/unraid/deploy-release.sh
```
## Persistent paths
Recommended Unraid paths:
```env
GEOINTEL_STORAGE_PATH=/mnt/user/appdata/geointel/storage
GEOINTEL_POSTGIS_DATA_PATH=/mnt/user/appdata/geointel/postgres-data
```
`GEOINTEL_STORAGE_PATH` contains uploads, tiles, masks, reports and exports.
`GEOINTEL_POSTGIS_DATA_PATH` contains the embedded PostGIS database files.
## Local Ollama assistant
The repository Compose file, DockerMan template and automatic deployment all
map `host.docker.internal` to the Unraid host and enable the source-grounded
assistant by default. Ollama must already listen on host port `11434`;
GeoIntel does not install or expose Ollama itself.
```env
OLLAMA_ENABLED=true
OLLAMA_BASE_URL=http://host.docker.internal:11434
OLLAMA_DEFAULT_MODEL=qwen3.5:9b
OLLAMA_TIMEOUT_SECONDS=120
OLLAMA_MAX_OUTPUT_TOKENS=1200
OLLAMA_CONTEXT_TOKENS=16384
```
`/health/live` proves only that FastAPI is serving. `/health/ready` returns
HTTP 503 when PostgreSQL, PostGIS, the migration head or persistent storage is
not ready, and is the container healthcheck. The all-in-one startup also marks
work left in `running` by a previous process as failed with
`PROCESS_INTERRUPTED`; synchronous work cannot survive a container restart.
The model dropdown comes from Ollama `/api/tags`, so changing the installed
models requires no frontend rebuild. Verify after deployment with:
```bash
curl http://127.0.0.1:1202/api/v1/assistant/status
curl http://127.0.0.1:1202/api/v1/assistant/models
```
## Update from Gitea
```bash
cd /mnt/user/appdata/geointel
git fetch origin main
git reset --hard origin/main
bash deploy/unraid/deploy-release.sh
```
The equivalent low-level GIS-only build remains available only for local
debugging; it is not a production deployment path:
```bash
docker build --build-arg GEOINTEL_INSTALL_AI=${GEOINTEL_INSTALL_AI:-false} -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
bash deploy/unraid/run-dockerman-container.sh
```
## Release identity, fresh install and rollback
The complete final-release order, including backup/restore, browser journeys,
SBOM, vulnerability policy, SSH-signed manifest and checksums, is in
`docs/RELEASE_RUNBOOK.md`. `VERSION` is the canonical semantic version and is
also written to the image's `org.opencontainers.image.version` label.
Inspect the running immutable revision and retained images:
```bash
docker inspect --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' geointel
docker image ls geointel-all-in-one
```
Run a fresh install in isolated temporary PostGIS, storage and model paths. The
script binds only a random loopback port and removes its own container and
temporary directory:
```bash
bash scripts/verify_release_fresh_install.sh geointel-all-in-one:latest
```
Verify an upgrade against an isolated restore of a checksum-verified backup.
The generated temporary database is removed even when the check fails:
```bash
bash scripts/verify_release_upgrade_smoke.sh \
--backup-dir /mnt/user/appdata/geointel/backups/<verified-backup> \
--confirm-isolated-upgrade
```
Return to the image that was active immediately before the latest deployment:
```bash
bash deploy/unraid/rollback-dockerman-container.sh \
--backup-dir /mnt/user/appdata/geointel/backups/<predeploy-release-id> \
--confirm-production-database-restore
```
For an older retained commit, select its immutable tag explicitly:
```bash
GEOINTEL_ROLLBACK_IMAGE=geointel-all-in-one:<commit-sha>-ai \
bash deploy/unraid/rollback-dockerman-container.sh \
--backup-dir /mnt/user/appdata/geointel/backups/<matching-predeploy-release-id> \
--confirm-production-database-restore
```
Rollback restores and verifies the selected dump in an isolated proof database
before any production replacement. It then swaps database names, retains the
pre-restore production database for operator recovery, reuses the configured
storage mount and never runs an Alembic downgrade or an older app against an
unknown newer schema. Remove the retained recovery database and old backup
directories only in a separately reviewed operator retention step; deployment
never deletes them automatically.
The configured upload limit is shared by FastAPI and the generated nginx
runtime configuration. Values outside `1..2048` MiB are rejected before the
active application is replaced.
## Data operations
`GEOINTEL_BACKUPS_PATH` defaults to
`/mnt/user/appdata/geointel/backups` and is mounted read-only at
`/app/backups`. Explicit cleanup commands can therefore verify a recent
backup without permission to alter it.
These commands are non-mutating:
```bash
docker exec geointel python /app/scripts/audit_data_operations.py
docker exec geointel python /app/scripts/cleanup_storage_artifacts.py
```
The full backup, confirmation, candidate-limit and apply sequence is in
`docs/DATA_OPERATIONS_RUNBOOK.md`. Apply moves bytes to protected,
checksum-bound quarantine rather than deleting them; a separate confirmed
restore command reverses the move. GeoIntel installs no automatic cleanup
schedule.
## Safe cleanup
Safe cache cleanup if Docker build cache fills the Unraid Docker image:
```bash
docker builder prune -af
```
Avoid broad volume pruning unless you explicitly intend to remove persisted PostGIS data or GeoIntel artifacts.
## Multi-container development stack
The root `docker-compose.yml` remains available for development and CI-like validation with separate `db`, `backend` and `frontend` services. For Unraid app-style operation, prefer `docker-compose.unraid.yml`.