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, and the upstream choice of tile-delivery engine — a raster caching proxy versus a vector-tile server — is weighed in MapProxy vs TileServer GL for Tile Delivery. - 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. Which of those two templating models suits the GeoNode stack — and when to combine them — is worked through in Helm vs Kustomize for GeoNode Deployments. 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; the scheduled terraform plan job that surfaces out-of-band changes to cloud resources is built in Detecting Terraform Drift in GeoNode Infrastructure. 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.
Statefulness Is the Axis Every Other Decision Hangs From
Orchestrating a geospatial platform is easier to reason about once you stop treating it as one system and start sorting its components by how much durable state each holds. That single property decides how a component is deployed, how it is upgraded, how it fails, how it is backed up, and how quickly it can be scaled — and components that look similar in an architecture diagram behave completely differently once their state is accounted for. A tile renderer and a spatial database are both “services in the cluster”; one can be replaced in seconds by a scheduler that has never heard of it, and one cannot be moved without a plan.
Four bands cover almost everything in a portal. Stateless services hold nothing between requests: renderers, proxies, and the portal’s own web workers, provided sessions live elsewhere. Cache-holding services hold data that is expensive to rebuild but not authoritative — a seeded tile cache is the clear case, and losing it costs render time rather than information. Coordination state is small, hot, and consequential: task queues, locks, and session stores, where losing the data means losing in-flight work rather than published records. Authoritative state is the spatial database and the object store behind it, where loss is unrecoverable by any means except restore.
Reading the table top to bottom explains most of the operational advice in this section. Stateless components can be autoscaled aggressively and deployed with rolling updates that assume any pod may vanish, which is why the container-image work in containerizing TileServer GL for high availability spends its effort on start-up time and read-only asset mounts rather than on graceful state handoff. Authoritative components need stable network identity, ordered rollout, and a rehearsed restore, which is why they belong in a StatefulSet with the deliberate treatment given in Kubernetes StatefulSets for PostGIS databases rather than in a Deployment with a volume attached.
The band that causes the most trouble is the second, because it is the one teams misclassify. A seeded tile cache feels authoritative — it is large, it took a long time to build, and losing it is painful — so it accumulates the ceremony of a database: backups, replication, careful upgrades. But it is derivable, and treating it as authoritative has a real cost, because it discourages the invalidation and reseeding that keep it correct. The healthier stance is to make rebuilding cheap and routine rather than to protect the cache from ever needing it: keep the seeding pipeline exercised, know the wall-clock cost of a full rebuild, and let cache loss be an inconvenience rather than an incident.
The third band deserves the opposite correction. Queues and lock stores are small, they run happily on a single node in staging, and they are therefore easy to under-engineer — until an ingestion run that has been going for six hours loses its broker and nobody can say which tasks completed. The state is tiny and the consequence is not proportional to its size. Give coordination components the same durability treatment as the database even though they hold a thousandth of the data, and make the queue’s own persistence and acknowledgement semantics an explicit decision rather than a default inherited from a quickstart guide.
Finally, the bands set the order of operations for any migration or upgrade. Move stateless components first, because they are reversible and prove the surrounding plumbing — networking, secrets, ingress — without risking anything. Move cache-holding components next, accepting a rebuild rather than attempting to copy state. Move coordination state with a drain: stop producers, let consumers finish, then cut over. Move authoritative state last, with a rehearsed restore and a tested rollback, and only after everything above it has been running on the new platform long enough to expose surprises. A migration that starts with the database because it is the hardest part has taken its largest risk while it still knows the least.
One more consequence is worth stating because it contradicts a common instinct. Backups are usually discussed as a property of the database alone, but the bands above show why a database backup by itself does not constitute a recoverable platform. Restoring authoritative data into an environment whose stateless tier has drifted — a different renderer version, a different projection library build, a different set of styles — produces a portal that runs and serves the wrong pixels. The recoverable unit is the pair: the data at a point in time, and the declarative description of everything else at that same point in time. That is the practical argument for keeping infrastructure and application configuration in version control alongside a timestamped data snapshot, and for tagging both with the same identifier so a restore can name one version rather than reconstructing a combination from memory.
The corollary is that a restore rehearsal must rebuild the stateless tier from its manifests rather than restoring a snapshot of running machines. Anything else tests the storage layer and leaves the more likely failure — a configuration that only exists on the current cluster because someone applied it by hand two years ago — completely unexamined. Rehearse into an empty namespace, from the repository, with only the data restored from backup, and the exercise measures what an actual disaster would ask of you.
Sizing that pair correctly also settles a recurring argument about how much of the platform belongs in the cluster at all. Components in the bottom band gain the least from orchestration and pay the most for it: a managed spatial database removes the storage, failover and upgrade problems that consume most of the operational budget, at the cost of less control over extensions and version timing — which matters more for PostGIS than for a general-purpose database, because a portal’s behaviour depends on specific extension versions. Components in the top band gain the most and pay almost nothing. A defensible default for a small team is therefore to orchestrate the stateless and cache-holding bands enthusiastically and to buy the authoritative band, revisiting that decision only when a concrete requirement — an extension the managed service will not carry, a data-residency rule, a cost ceiling — forces it.
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; metrics, logs, and endpoint SLAs are wired into the stack described in Monitoring and Observability for Geospatial Portals; 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.