Infrastructure Orchestration & Config Management for Open-Source Geospatial Portals
Deploying and scaling an open-source geospatial portal demands a declarative, infrastructure-as-code discipline that governs every tier from the public edge down to the spatial data store. This guide gives GIS administrators, open-source maintainers, platform engineers, and government technology teams a production baseline for orchestrating stateless rendering, stateful spatial databases, and OGC service routing as version-controlled, continuously reconciled software rather than hand-tended servers.
Orchestration is the day-two operational surface of the wider portal program: this guide owns provisioning, configuration convergence, and drift control, while runtime topology and trust boundaries live in Core Portal Architecture & Security Boundaries and discoverability lives in Metadata Catalog Automation & Ingestion Workflows. The principle that unifies all three is the same: nothing reaches a runtime cluster except through a reviewed commit, and that cluster is expected to converge on the commit without manual intervention.
The control loop below shows how a GitOps reconciler turns version-controlled manifests into live cluster state across the stateless and stateful tiers, continuously correcting drift.
Architectural Foundations & Service Decomposition
A production geospatial platform is not one deployable but a set of independently provisioned units with sharply different scaling and durability profiles. The orchestration model must reflect that decomposition rather than flatten it. Three classes of workload dominate:
- Stateless rendering and API tiers — vector and raster tile generation,
WMS/WMTScache fronting, metadata catalog APIs, and OGC gateways. These are ephemeral, horizontally scalable, and safe to replace at any time, so they belong in Deployments governed by horizontal pod autoscaling (HPA). The high-availability patterns for the tile path specifically are covered in Containerizing TileServer GL for High Availability. - Stateful spatial data stores — PostgreSQL with the PostGIS extension, holding spatial tables, indexes, and write-ahead logs. These require stable identity, ordered startup, and durable volumes, which maps to StatefulSets rather than Deployments.
- Edge routing and OGC exposure — reverse proxies and API gateways that terminate TLS, normalise headers, enforce rate limits, and route
WMS/WFS/WCS/OGC API traffic to the correct backend.
The central composition decision is how much rendering and caching to fold into the application server versus a dedicated proxy layer. A tightly integrated deployment is simpler to reason about but couples cache invalidation to application releases; a distributed render-and-proxy topology adds moving parts but isolates latency-sensitive tile paths from slow analytical writes. Those trade-offs decide request latency, cache-warming behaviour, and horizontal scaling headroom, so align component selection against throughput targets before writing a single manifest. The design baseline for that decomposition is the Core Portal Architecture & Security Boundaries reference; this guide encodes the resulting topology as code.
The decomposition is only durable if it is parameterised cleanly. Environment-specific values — resource quotas, storage classes, ingress domains, replica counts — must be injected at deploy time while the core manifests stay byte-identical across staging and production. That discipline is what makes Environment Parity in Geospatial CI Pipelines achievable: spatial transformations, render tests, and load validation all execute against the same infrastructure profile regardless of stage.
Security Boundary Mapping & Network Segmentation
Orchestration encodes trust zones as enforceable network policy, not documentation. Every namespace should start from default-deny and open only the specific flows the topology requires: the edge zone may reach the application zone, the application zone may reach the data zone, and the data zone initiates nothing outbound except backups to an explicitly listed endpoint. TLS terminates at the edge proxy; service-to-service traffic inside the Kubernetes cluster is secured with mTLS or an internal CA so that a compromised pod cannot pivot laterally across zones.
A baseline default-deny policy for the data zone, with one explicit ingress rule, makes the boundary auditable:
# Deny all traffic to the data zone, then allow only the application tier on 5432.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: data-zone-default-deny
namespace: data-zone
spec:
podSelector: {} # applies to every pod in the namespace
policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-app-to-postgis
namespace: data-zone
spec:
podSelector:
matchLabels:
app: postgis
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels:
zone: application
ports:
- protocol: TCP
port: 5432
OGC endpoints carry their own boundary concerns: GetCapabilities must be filtered to authorised layers, and large geometry payloads must traverse the proxy without truncation. The protocol-level rules for what each service must accept and reject are the OGC committee’s, and platform teams encoding them at the edge should align with Security Boundary Mapping for OGC Services and the canonical OGC Web Service Standards. Non-compliant manifests should never reach the Kubernetes cluster at all — admission controllers such as OPA Gatekeeper or Kyverno reject privileged containers, missing network policies, or unpinned images at apply time.
Identity, Access Control & IdP Integration
Authentication belongs at the gateway, before any OGC backend executes a request. The edge validates a JWT or OIDC token — checking iss, aud, expiry, and signature against the identity provider’s JWKS — and only then forwards the request with a verified identity header to GeoServer or the portal. Federating to a dedicated IdP (Keycloak, Dex, or Azure AD) keeps credentials out of the application database and centralises session expiry and rotation.
Authorisation is enforced twice: once at the gateway for coarse routing by tenant and role claim, and again at the data layer where row-level security and layer ACLs prevent cross-tenant reads even if a request slips past the edge. The multi-tenant model that ties JWT claims to PostGIS row-level security and GeoServer layer permissions is detailed in Implementing RBAC for Multi-Tenant GIS Portals. From an orchestration standpoint, the IdP client secrets, JWKS endpoints, and role-mapping config are themselves managed objects: they live in a secrets manager referenced by the manifests, are mounted as projected volumes rather than baked into images, and rotate on a schedule enforced by the reconciler.
Resilience, Routing & Health Probes
Stateless tiers earn their availability through replication and honest health signalling. Liveness and readiness probes must be distinct: liveness restarts a wedged process, while readiness gates traffic until a pod can actually serve tiles. Conflating them causes restart storms under load. Probes should reflect real telemetry — a render pod is not ready until its tile cache directory is writable and its upstream PostGIS connection succeeds.
# Distinct probes: readiness gates traffic, liveness recovers a hung process.
readinessProbe:
httpGet:
path: /health/ready # checks cache mount + upstream DB reachability
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /health/live # process-only check, never touches the DB
port: 8080
initialDelaySeconds: 30
periodSeconds: 15
failureThreshold: 5
Routing resilience layers on top: the reverse proxy needs circuit breakers that shed load from an unhealthy render backend, upstream timeouts aligned with the slowest legitimate GetMap request, and a fallback path when the primary tile source degrades. The buffer-sizing, timeout, and header-forwarding specifics for OGC traffic are covered in Reverse Proxy Configuration for WMS/WFS, and the load-balancing layer that distributes WMS requests across render replicas is detailed in Configuring HAProxy for WMS Load Balancing. When a tile origin fails outright, requests should degrade gracefully rather than error — the strategies for that are in Fallback Routing Strategies for Tile Servers.
The stateful tier resists the same failover patterns because losing write ordering corrupts spatial indexes and replication streams. StatefulSets provide stable network identity, ordered rollout, and per-pod volume claim templates, but read/write routing and replica promotion must be configured explicitly per the official Kubernetes guidance on StatefulSet controllers. The end-to-end operational pattern — connection pooling, backup scheduling, and replica synchronisation — is the subject of Kubernetes StatefulSets for PostGIS Databases, with the volume-provisioning specifics in Deploying PostGIS on Kubernetes with Persistent Volumes.
Configuration-as-Code: Terraform, Helm & GitOps
Every cluster definition, network policy, and service manifest belongs in an immutable Git repository, enabling peer review, cryptographic audit trails, and automated synchronisation. The layering that works in practice keeps tools in their lane: cloud resources (VPCs, managed disks, IAM roles) in Terraform, Kubernetes workloads in Helm charts or Kustomize overlays, and node-level configuration in Ansible roles. A GitOps controller — Argo CD or Flux — then continuously reconciles desired state against runtime, eliminating ad-hoc SSH interventions and making the repository the single source of truth for portal topology.
A Terraform module boundary for the spatial data tier shows the parameterisation discipline that keeps environments identical apart from their injected variables:
# modules/postgis/main.tf — one module, environment values injected by the caller.
variable "environment" { type = string }
variable "storage_class" { type = string }
variable "volume_size_gb" { type = number }
variable "replica_count" { type = number }
resource "kubernetes_stateful_set" "postgis" {
metadata {
name = "postgis"
namespace = "data-zone"
labels = { app = "postgis", env = var.environment }
}
spec {
service_name = "postgis"
replicas = var.replica_count
# volume_claim_template + container spec elided for brevity
}
}
The reconciliation loop is only trustworthy if drift is actively hunted rather than assumed absent. The controller compares live cluster state against the committed baseline on every sync, flags unauthorised changes, deprecated API versions, and misaligned policies, and either alerts or auto-remediates depending on the namespace’s risk tier. Promotion becomes a merge and rollback a revert. The CI gates that protect this loop — terraform plan review, schema validation, and synthetic OGC tests — are detailed in Environment Parity in Geospatial CI Pipelines, and the workspace-level replication of GeoNode infrastructure across environments is implemented in Syncing GeoNode Environments with Terraform.
Operational Troubleshooting
When orchestration fails, the diagnostic question is always the same: did the desired state never apply, or did the runtime drift away from it? A systematic workflow correlates the symptom against the reconciler status and the tier logs before any manual change. Start at the GitOps controller — an OutOfSync or Degraded status localises the problem to a specific manifest — then follow the failing object inward to its pod and node logs.
| Symptom | Likely cause | Where to look | First fix |
|---|---|---|---|
App OutOfSync but never converges |
Failed admission or invalid manifest | Argo CD app events, kubectl describe on the object |
Check Gatekeeper/Kyverno denials; fix the rejected field and re-sync |
CrashLoopBackOff on render pods |
Bad image or unreachable upstream | kubectl logs --previous, readiness probe events |
Pin image digest, verify PostGIS DNS and NetworkPolicy |
502 Bad Gateway under load |
Edge → render routing / unready pods | Nginx error.log, HPA + readiness status |
Raise upstream timeout, add render replicas, inspect circuit breaker |
504 Gateway Timeout on GetMap |
Render or cache tier saturation | MapProxy log, GeoServer request log | Warm cache, cap heavy BBOX requests, scale render tier |
FATAL: remaining connection slots |
Data tier pool exhaustion | PostgreSQL postgresql.log, PgBouncer SHOW POOLS |
Lower per-tenant pool cap, enforce statement_timeout |
StatefulSet pod stuck Pending |
Unbound PVC / no matching storage class | kubectl describe pvc, CSI controller logs |
Confirm storageClassName and available capacity in the zone |
| Drift reappears after every sync | A controller or human writing outside Git | reconciler diff history, audit log | Remove the out-of-band writer; set sync policy to self-heal |
A quick reconciler-to-pod trace makes the failing tier obvious:
# Localise a failing rollout from the GitOps controller down to the pod.
argocd app get geospatial-portal --refresh | grep -E "Sync|Health"
kubectl -n application get pods -l app=geonode -o wide
kubectl -n application logs deploy/geoserver --tail=100 | grep -i error
kubectl -n data-zone exec deploy/pgbouncer -- \
psql -p 6432 pgbouncer -c "SHOW POOLS;"
Custom GeoNode apps and GeoServer extensions are where regressions sneak back in: they must run inside the same guardrails as the core — pinned dependencies, explicit resource limits, and traffic routed through the same gateway validation — or they reopen boundaries the platform just closed.
Operational Maturity Checklist
Maturity is the property that the patterns above stay enforced without heroics. A reviewer should be able to confirm every line below from version-controlled config or a dashboard, not from tribal knowledge.
- Service decomposition — Stateless render/API tiers run as autoscaled Deployments; the spatial store runs as a StatefulSet with volume claim templates; every component is independently deployable from the manifest repo.
- Segmentation — A default-deny
NetworkPolicygoverns each namespace; TLS terminates at the edge; mTLS or an internal CA secures service-to-service calls; data-zone egress is explicitly listed. - Identity — JWTs/OIDC tokens are validated at the gateway before any OGC backend; IdP federation is the only credential source; client secrets live in a secrets manager and rotate on schedule.
- Access control — RBAC is enforced at both gateway and data layers;
GetCapabilitiesis filtered to authorised layers; cross-tenant access is tested, not assumed. - Resilience — Liveness and readiness probes are distinct and telemetry-driven; circuit breakers and fallback tile routing are configured; PgBouncer caps connections per tenant with enforced timeouts.
- Configuration-as-code — Cloud resources in Terraform, workloads in Helm/Kustomize, nodes in Ansible; a GitOps reconciler converges the Kubernetes cluster on Git and self-heals; promotion is a merge and rollback a revert.
- Drift & admission — Admission controllers reject non-compliant manifests at apply time; the reconciler alerts on every diff; out-of-band writers are eliminated.
- Parity & observability — Staging and production share byte-identical manifests with injected variables; request IDs propagate edge-to-data; per-tier logs are aggregated, and capacity and CVE reviews are scheduled, not ad hoc.
Holding these lines — clean service decomposition, zero-trust segmentation, and declarative configuration converged by GitOps — is what lets an open-source geospatial portal scale predictably while meeting the rigorous compliance bars of enterprise and government spatial data ecosystems. As architectures evolve toward distributed edge caching, real-time analytics, and multi-tenant data sharing, treating infrastructure as immutable, reconciled code remains the constant that sustains both reliability and engineering velocity.