Files
geointel/deploy/unraid
Jens 2d5fbc81cf
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s
GeoIntel release gates / AI image, SBOM and container scan (push) Canceled after 0s
security: disable guest fallback in DockerMan runtime
2026-08-27 00:00:58 +02:00
..

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:

http://<unraid-ip>:${GEOINTEL_FRONTEND_PORT}

The app icons are served from the same container:

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:

/boot/config/plugins/dockerMan/templates-user/my-geointel.xml

and copy the PNG icon to:

/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:

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:

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.

For a browser login, set GEOINTEL_AUTH_ENABLED=true, 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.

Guest access is disabled by default and must be explicitly enabled only for a dedicated demo instance:

GEOINTEL_GUEST_ACCESS_ENABLED=false
GEOINTEL_GUEST_DISPLAY_NAME=Gast
GEOINTEL_GUEST_SESSION_TTL_SECONDS=7200

Set GEOINTEL_GUEST_ACCESS_ENABLED=true only on a dedicated demo deployment to enable the guest button and guest endpoint. This 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 as geointel-all-in-one:previous, build an immutable <commit-sha>-ai or <commit-sha>-gis tag plus latest, install the DockerMan metadata and start the immutable image. 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. That means GEOINTEL_INSTALL_AI=true in /mnt/user/appdata/geointel/.env is enough for the automatic deploy to build the AI-enabled image. Set GEOINTEL_INSTALL_AI in the local shell or pass -InstallAi true/false to the PowerShell wrapper only when you intentionally want to override the remote .env for that deploy.

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.

AI dependencies are opt-in. Leave GEOINTEL_INSTALL_AI=false for the default GIS-only image. Set GEOINTEL_INSTALL_AI=true, 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:

/mnt/user/appdata/geointel/models

Then run a dry-run first:

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:

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:

curl -fsS "http://192.168.10.150:${GEOINTEL_FRONTEND_PORT:-1202}/health/live"
curl -fsS "http://192.168.10.150:${GEOINTEL_FRONTEND_PORT:-1202}/health/ready"
curl -fsS "http://192.168.10.150:${GEOINTEL_FRONTEND_PORT:-1202}/api/v1/system/capabilities"
curl -fsS "http://192.168.10.150:${GEOINTEL_FRONTEND_PORT:-1202}/api/v1/projects"
curl -I "http://192.168.10.150:${GEOINTEL_FRONTEND_PORT:-1202}/geointel-icon.svg"
curl -I "http://192.168.10.150:${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:

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:

GEOINTEL_FRONTEND_PORT=1203
GEOINTEL_CORS_ORIGINS=http://localhost:1203,http://127.0.0.1:1203,http://192.168.10.150:1203

Apply:

bash deploy/unraid/deploy-release.sh

Persistent paths

Recommended Unraid paths:

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.

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:

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

cd /mnt/user/appdata/geointel
git fetch origin main
git reset --hard origin/main
bash deploy/unraid/deploy-release.sh

The equivalent low-level build remains available for debugging:

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:

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 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 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 deploy/unraid/rollback-dockerman-container.sh

For an older retained commit, select its immutable tag explicitly:

GEOINTEL_ROLLBACK_IMAGE=geointel-all-in-one:<commit-sha>-ai \
  bash deploy/unraid/rollback-dockerman-container.sh

Rollback reuses the configured PostGIS and storage mounts and never runs an Alembic downgrade. If a future release has a backward-incompatible migration, restore its verified pre-release backup instead of forcing an older app against a newer schema.

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:

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. GeoIntel installs no automatic cleanup schedule.

Safe cleanup

Safe cache cleanup if Docker build cache fills the Unraid Docker image:

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.