Skip to content

Custom QGIS HTTP wrapper for QField project creation

Context and Problem Statement

Field-TM supports QField as an alternative to ODK for field data collection. Creating a QField project requires converting an XLSForm + GeoJSON into a QGIS project file (.qgz), then uploading it to QFieldCloud.

We built a lightweight HTTP wrapper around QGIS (src/qfield/project_gen_svc.py) running in a Docker container to handle this conversion. The question arose whether we could instead use QFieldCloud's built-in jobs system to defer this responsibility to the QField team and reduce our maintenance burden. User creation is tracked separately; we are working upstream to add this to the QFieldCloud API.

Considered Options

  • Use QFieldCloud built-in jobs (via API/SDK)
  • Keep the custom QGIS HTTP wrapper as a k8s pod sidecar
  • Keep the custom QGIS HTTP wrapper as a separate k8s Deployment

Decision Outcome

Keep the custom QGIS HTTP wrapper as a separate Kubernetes Deployment (not a sidecar).

Why not QFieldCloud built-in jobs

QFieldCloud jobs (process_projectfile, package, delta_apply) operate on an existing QGIS project. They do not create a project from XLSForm + GeoJSON or support our plugin requirements. We still need our wrapper to generate the .qgz; after upload, QFieldCloud handles its normal validation/packaging jobs.

Why not a sidecar

Sidecar deployment coupled QGIS scaling to backend scaling and created a multi-process pod. Running the wrapper as its own Deployment avoids both.

The backend calls the wrapper via QFIELDCLOUD_QGIS_URL. Input and output files are exchanged via the shared PostgreSQL database (see 0006-db-shared-state-qgis-jobs).

Concurrency within each QGIS replica

QGIS processing is not thread-safe. The service accepts concurrent requests, but each replica serializes QGIS work with a threading.Lock. Parallelism comes from replica count (qfieldQgis.replicaCount, default 2).

Consequences

  • Good, because the wrapper is minimal (single file, ~745 lines) on a stable base image (ghcr.io/opengisch/qfieldcloud-qgis).
  • Good, because QFieldCloud's auto-triggered jobs already handle validation and packaging after file upload, so we get that for free.
  • Good: QGIS generation scales independently of backend replicas.
  • Good, because it works with remote QFieldCloud instances (SDK handles auth/upload; wrapper runs locally).
  • Bad, we maintain a custom Docker container with the XLSFormConverter plugin installed.
  • Bad, we maintain custom wrapper code/images.

Future Work

  • Move QGIS generation to per-request Kubernetes Jobs to remove persistent QGIS pods.
  • Contribute upstream support for Kubernetes-backed workers in QFieldCloud.
  • Continue upstream collaboration on user-creation API support and adopt it in Field-TM when available in released QFieldCloud versions.