Overview
GIS Server Setup — Active Project
🟢 All Systems Running Updated Jun 2026

Project Overview

GIS Server — Photogrammetry & Spatial Data Delivery Platform

🛰️
Active Projects
2
1 testing · 1 client ready
🐳
Services Running
7
WebODM · GeoServer · Nginx
GPU
RTX
2060 Mobile · CUDA 13.2
☁️
Tunnel
4
Connections · KUL + SIN
Installation Progress
7-phase server setup
71%
NVIDIA + Docker
Docker Stack (WebODM + GeoServer)
Cloudflare Tunnel + Zero Trust
Cloudflare Pages WebsiteIn Progress
First Survey Project PublishedPending
Server Specifications
sodc · 192.168.14.133
CPU Intel i7-8750H · 6C/12T · 2.2GHz
RAM 32 GB DDR4
GPU RTX 2060 Mobile · 6GB VRAM · CUDA 13.2
Storage 238GB NVMe · 931GB HDD (future SSD)
OS Linux Mint 22.3 (Ubuntu Noble)
Network Gigabit Ethernet · 192.168.14.133
Driver NVIDIA 595.71.05

Installation Progress

7-phase GIS server setup · Started June 2026

✓ Done Phase 0 — Pre-flight & Ethernet
Static IP assignment, Ethernet AER fix (alx driver), Secure Boot disabled for NVIDIA driver loading.
✓ Done Phase 1 — NVIDIA Drivers + Docker
nvidia-driver-595-open installed. Docker 29.6.0 + NVIDIA Container Toolkit 1.19.1. GPU verified inside containers.
✓ Done Phase 2 — Directory Structure
/opt/gis-data/ with incoming, webodm, geoserver, www subdirectories. TrueNAS SMB mount point reserved.
✓ Done Phase 3 — Docker Stack (WebODM + GeoServer + Nginx)
7 containers running: WebODM webapp, worker, NodeODM GPU node, Redis, Postgres, GeoServer 2.25.0, Nginx.
✓ Done Phase 4 — QGIS Desktop
QGIS 3.34.4 Prizren installed on server desktop. Used for DWG → Shapefile/GeoPackage conversion step.
✓ Done Phase 5 — Cloudflare Tunnel
cloudflared 2026.6.1 installed. Tunnel sosurvey-gis active with 4 connections (KUL + SIN). DNS records created for maps, webodm, geoserver subdomains.
✓ Done Phase 6 — Zero Trust Access Policies
Access apps created for WebODM (staff email OTP), GeoServer (service token), Maps viewer (staff + client OTP). Both @sosurvey.com.my and @d-alammaya.link allowed.
⟳ In Progress Phase 7 — Cloudflare Pages & Dev Portal
Dev portal (this site) deployed to dev.sosurvey.com.my. Company microsite for sosurvey.com.my pending.

Tasks

Server setup · First project workflow · Future improvements

Completed
Install NVIDIA driver 595-open
Phase 1 Secure Boot disabled
Install Docker + NVIDIA Container Toolkit
Phase 1 GPU passthrough verified
Create /opt/gis-data directory structure
Phase 2 TrueNAS path reserved
Deploy WebODM with GPU node
Phase 3 7 containers running
Deploy GeoServer 2.25.0
Phase 3 Port 8600
Install QGIS 3.34 on server desktop
Phase 4 DWG conversion ready
Configure Cloudflare Tunnel + DNS
Phase 5 3 subdomains live
Set up Zero Trust Access policies
Phase 6 Email OTP + service token
In Progress
Deploy Dev Portal to Cloudflare Pages
Phase 7 dev.sosurvey.com.my
Change default passwords (WebODM, GeoServer)
Security Before go-live
Fix Ethernet permanent static IP after reboot
Network alx driver config
Upcoming
Process first test site in WebODM
Workflow Drone images → orthophoto
Import ZWCAD DWG → QGIS → GeoServer
Workflow First client project
Build first client Leaflet map page
Delivery maps.sosurvey.com.my/{slug}
Add client email to Zero Trust policy
Access Per-client OTP setup
Replace HDD with SSD
Hardware Remount /opt/gis-data
Mount TrueNAS SMB share
Future /opt/gis-data/incoming

Project Journal

Chronological log of decisions, discoveries, and milestones

24 June 2026 — Session 1
GIS Server Infrastructure — Full Stack Deployed
Completed full server setup in a single session. Server is a repurposed gaming laptop (MSI GL63 8SE) running Linux Mint 22.3. Hardware included RTX 2060 Mobile with no drivers, no Docker, no cloudflared — all installed from scratch.

Key discoveries: Secure Boot was blocking NVIDIA driver kernel module loading (alx driver PCIe AER errors also caused Ethernet DHCP failures). Both resolved — Secure Boot disabled, Ethernet now stable on Gigabit NIC at 192.168.14.133.

Stack deployed: WebODM (GPU-accelerated, 5 containers) + GeoServer 2.25.0 + Nginx serving Map Viewer. Cloudflare Tunnel with Zero Trust Access policies live for all three subdomains. QGIS 3.34 installed for DWG → Shapefile conversion.
NVIDIA Docker WebODM GeoServer Cloudflare Zero Trust Milestone
24 June 2026 — Architecture Decision
Workflow Defined: Field → WebODM → QGIS → GeoServer → Leaflet
Finalised end-to-end delivery workflow for licensed land survey data. Drone imagery arrives via USB, processed in WebODM with GPU acceleration. ZWCAD DWG files converted in QGIS (on server desktop) to Shapefile/GeoPackage, loaded into GeoServer. Per-client isolated views served via Leaflet at maps.sosurvey.com.my/{client-slug}/ behind Cloudflare Zero Trust email OTP.

Customer data isolation enforced at two levels: GeoServer layer groups (per client) and Cloudflare Access policy (per client email). Clients cannot see each other's survey data.
Architecture Workflow Decision

Infrastructure Diagram

GIS Server stack · sosurvey.com.my · June 2026

graph TB subgraph CF["☁️ Cloudflare Edge"] DNS["DNS
sosurvey.com.my"] ZT["Zero Trust
Access Policies"] PAGES["Pages
dev.sosurvey.com.my"] TUNNEL["Tunnel
sosurvey-gis
KUL + SIN"] end subgraph CLIENT["👤 Users"] STAFF["Staff
@sosurvey.com.my
@d-alammaya.link"] CUSTOMER["Customer
Email OTP"] end subgraph SERVER["🖥️ GIS Server — 192.168.14.133"] CLOUDFLARED["cloudflared
daemon"] subgraph DOCKER["Docker Containers"] WEBODM["WebODM
:8000"] GEOSERVER["GeoServer
:8600"] NGINX["Nginx
:8080"] NODE["NodeODM
GPU Node"] REDIS["Redis"] DB["Postgres"] end QGIS["QGIS Desktop
DWG → Shapefile"] GPU["RTX 2060
CUDA 13.2"] STORAGE["/opt/gis-data"] end STAFF -->|"Email OTP"| ZT CUSTOMER -->|"Email OTP"| ZT ZT --> TUNNEL TUNNEL --> CLOUDFLARED CLOUDFLARED --> WEBODM CLOUDFLARED --> GEOSERVER CLOUDFLARED --> NGINX WEBODM --> NODE NODE --> GPU WEBODM --> DB WEBODM --> REDIS NGINX -->|"WMS proxy"| GEOSERVER QGIS -->|"Shapefile"| STORAGE STORAGE --> GEOSERVER STORAGE --> WEBODM style CF fill:#f0f4ff,stroke:#2563eb style SERVER fill:#f0fdf4,stroke:#16a34a style DOCKER fill:#f8fafc,stroke:#94a3b8 style CLIENT fill:#fffbeb,stroke:#d97706
WebODM
localhost:8000
GeoServer
localhost:8600
Nginx (Maps)
localhost:8080
Tunnel ID
7f33ba7d-ee03-46f5
Data Root
/opt/gis-data/
GPU Driver
NVIDIA 595.71.05

Wiki & Documentation

Workflow guides, runbooks, and reference docs

Getting Started
End-to-End Workflow
WebODM Processing
QGIS — DWG to GeoServer
Client Delivery
Onboard a New Client
Build a Map Page
Infrastructure
Manage Docker Services
SSD Upgrade Guide
TrueNAS SMB Setup

End-to-End Survey Workflow

Last updated: Jun 2026 · SO Survey GIS Team

1. Field Data Collection

  • Conduct drone flight, capture raw JPEG/TIF images + GCP coordinates
  • Total station / GNSS survey for utilities, boundaries, polygons
  • Draw final CAD layers in ZWCAD on office PC → export DWG

2. Import to Server

Copy via USB to the server:

# Drone images
/opt/gis-data/incoming/drone-images/{project-name}/

# ZWCAD DWG export
/opt/gis-data/incoming/dwg/{client-slug}/

3. Process Drone Imagery

  • Login to webodm.sosurvey.com.my (email OTP)
  • Create new project → Upload images from /opt/gis-data/incoming/drone-images/{project}/
  • Select processing preset → High Resolution with GPU enabled
  • Download output: orthophoto .tif, point cloud .las
  • Save orthophoto to /opt/gis-data/geoserver/data/{client-slug}/

4. Convert CAD to GeoServer

  • Open QGIS on server desktop
  • Open DWG from /opt/gis-data/incoming/dwg/{client-slug}/
  • Set correct CRS (GDM2000 / Peninsula RSO or WGS84)
  • Export layers as Shapefile → /opt/gis-data/geoserver/data/{client-slug}/

5. Publish to GeoServer

  • Access GeoServer at localhost:8600/geoserver (from server desktop)
  • Create workspace → {client-slug}
  • Add stores → orthophoto GeoTIFF + Shapefile
  • Publish layers → create layer group {client-slug}_all

6. Deliver to Client

  • Create /opt/gis-data/www/{client-slug}/index.html (Leaflet viewer)
  • Add client email to Zero Trust policy for maps.sosurvey.com.my
  • Send client link: maps.sosurvey.com.my/{client-slug}/
  • Client receives email OTP on first access
The entire workflow from USB import to client link takes approximately 2–4 hours depending on project size and drone image count.

WebODM Processing Guide

GPU-accelerated drone photogrammetry

Access

Navigate to webodm.sosurvey.com.my — authenticate with your @sosurvey.com.my or @d-alammaya.link email via OTP.

Recommended Settings

SettingValueNotes
Processing Nodenode-odx-1 (GPU)Always use GPU node
PresetHigh ResolutionFor survey-grade output
Feature QualityHighBalance speed vs quality
PC QualityHighPoint cloud density
GCP FileUpload if availableImproves georeferencing

Output Files

  • odm_orthophoto/odm_orthophoto.tif — GeoTIFF for GeoServer
  • odm_pointcloud/cloud.copc.laz — Point cloud
  • odm_dem/dsm.tif — Digital Surface Model
WebODM stores projects in /opt/gis-data/webodm/projects/. Archive completed projects to free NVMe space before SSD upgrade.

QGIS — DWG to GeoServer

Converting ZWCAD exports for web delivery

Open DWG File

QGIS → Layer → Add Layer → Add Vector Layer
Format: DXF/DWG
File: /opt/gis-data/incoming/dwg/{client-slug}/survey.dwg

Set CRS

Right-click layer → Set CRS. For Peninsular Malaysia:

  • EPSG:4742 — GDM2000 (recommended for new surveys)
  • EPSG:3375 — GDM2000 / Peninsula RSO (projected)
  • EPSG:4326 — WGS84 (if GPS coordinates)

Export to Shapefile

Right-click layer → Export → Save Features As
Format: ESRI Shapefile (or GeoPackage)
CRS: EPSG:4326 (reproject to WGS84 for web use)
Output: /opt/gis-data/geoserver/data/{client-slug}/{layername}.shp
Always reproject to WGS84 (EPSG:4326) when saving for GeoServer web delivery. Leaflet expects WGS84 coordinates.

Onboard a New Client

5-minute checklist

Checklist

  • ☐ Create GeoServer workspace: {client-slug}
  • ☐ Upload orthophoto + Shapefile layers
  • ☐ Publish layer group: {client-slug}_all
  • ☐ Create map viewer: /opt/gis-data/www/{client-slug}/index.html
  • ☐ Add client email to Cloudflare Zero Trust → Maps policy
  • ☐ Test login as client (use incognito browser)
  • ☐ Send client their URL: maps.sosurvey.com.my/{client-slug}/

Add Email to Zero Trust

Cloudflare Dashboard → Zero Trust → Access → Applications → SO Survey Maps → Edit → Policies → Staff Access → Add email rule.

Client receives an email OTP every session. No account or password required on their side.

Build a Client Map Page

Leaflet viewer template

Create /opt/gis-data/www/{client-slug}/index.html using this template:

<!DOCTYPE html>
<html><head>
  <title>{Client Name} — Survey Map</title>
  <link rel="stylesheet"
    href="https://unpkg.com/leaflet/dist/leaflet.css"/>
  <style>#map{height:100vh;width:100%}</style>
</head><body>
  <div id="map"></div>
  <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
  <script>
    const map = L.map('map').setView([3.1390, 101.6869], 16);

    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
      {attribution:'© OpenStreetMap'}).addTo(map);

    L.tileLayer.wms('https://geoserver.sosurvey.com.my/geoserver/wms',{
      layers: '{client-slug}_all',
      format: 'image/png',
      transparent: true,
      version: '1.1.0',
      headers: {
        'CF-Access-Client-Id': '{service-token-id}',
        'CF-Access-Client-Secret': '{service-token-secret}'
      }
    }).addTo(map);
  </script>
</body></html>

Replace {client-slug} with the GeoServer workspace name. Service token credentials are in /opt/gis-data/.geoserver-token.

Manage Docker Services

Common runbook commands

Check Status

docker ps --format 'table {{.Names}}\t{{.Status}}'

WebODM

# Start/Stop/Restart
cd /opt/gis-data/webodm-app
./webodm.sh start --gpu
./webodm.sh stop
./webodm.sh restart

# View logs
docker logs webapp --tail 50 -f

GeoServer + Nginx

cd /opt/gis-data
docker compose up -d        # start
docker compose down         # stop
docker compose restart      # restart
docker compose logs -f      # logs

Cloudflare Tunnel

sudo systemctl status cloudflared
sudo systemctl restart cloudflared
sudo journalctl -u cloudflared -f

SSD Upgrade Guide

Replacing /dev/sda with new SSD — zero Docker config changes

Steps

# 1. Stop all services
cd /opt/gis-data && docker compose down
cd /opt/gis-data/webodm-app && ./webodm.sh stop

# 2. Physically swap HDD → SSD

# 3. Format new SSD
sudo mkfs.ext4 /dev/sda1

# 4. Mount temporarily and copy data
sudo mount /dev/sda1 /mnt/newssd
sudo cp -a /opt/gis-data/. /mnt/newssd/
sudo umount /mnt/newssd

# 5. Update fstab for permanent mount
sudo blkid /dev/sda1  # get UUID
echo "UUID=xxx /opt/gis-data ext4 defaults 0 2" | sudo tee -a /etc/fstab

# 6. Remount and verify
sudo mount -a
ls /opt/gis-data

# 7. Restart services
cd /opt/gis-data && docker compose up -d
cd /opt/gis-data/webodm-app && ./webodm.sh start --gpu
Because all Docker volumes and compose files are under /opt/gis-data, the SSD remount requires zero changes to Docker configuration.

TrueNAS SMB Setup

Auto-sync field data to server incoming folder

Mount TrueNAS Share

# Install CIFS utilities
sudo apt install -y cifs-utils

# Create credentials file
sudo tee /etc/smb-creds <<EOF
username=survey-sync
password=YOUR_TRUENAS_PASSWORD
domain=WORKGROUP
EOF
sudo chmod 600 /etc/smb-creds

# Add to /etc/fstab
//TRUENAS-IP/survey-sync /opt/gis-data/incoming \
  cifs credentials=/etc/smb-creds,uid=sodc,gid=sodc,\
  _netdev,iocharset=utf8 0 0

# Mount
sudo mount -a

Auto-Sync from Field Laptop

On the field laptop (Windows), map the TrueNAS share as a network drive and copy drone images there. The server's /opt/gis-data/incoming/drone-images/ folder will update automatically.

TrueNAS must be on the same LAN as the server. Sync only works when the laptop is on the office network.