GeoServer Infrastructure & Development Guide
This guide provides comprehensive information for developers and operators working with GeoServer in the SDL platform, covering infrastructure architecture, configuration management, backup strategies, and development workflows.
Overview
GeoServer in SDL is deployed as a containerized service with extensive customizations for security, persistence, and integration with other platform components. The deployment leverages:
-
JDBC Configuration Storage - PostgreSQL-based configuration management
-
Automated Security Backups - Continuous backup of critical security files
-
Persistent Volume Management - Selective persistence for non-database data
-
PostGIS Integration - Native spatial database support
-
MinIO S3 Storage - Distributed tile caching
-
Keycloak OIDC - Enterprise authentication and authorization
Architecture
Component Overview
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Keycloak │────▶│ GeoServer │────▶│ PostgreSQL │
│ (Auth/OIDC) │ │ │ │ (JDBC Config) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ MinIO │ │ PostGIS │
│ (Tile Cache) │ │ (Spatial Data) │
└─────────────────┘ └─────────────────┘
Key Components
-
GeoServer Container (
df-geoserver)-
Based on Kartoza GeoServer image with custom extensions
-
ABAC (Attribute-Based Access Control) support
-
OIDC/Keycloak integration
-
JDBC Configuration module
-
-
Security Backup Service
-
Runs as separate deployment
-
Backs up security files every 10 seconds
-
Stores in PostgreSQL tables
-
-
Init Containers
-
Database state checker
-
Configuration injector
-
Security restoration
-
Configuration Management
JDBC Configuration
Most GeoServer configurations are stored in PostgreSQL using the JDBC Config community module. This provides:
-
Centralized configuration storage
-
Easy backup and restore
-
Support for multiple GeoServer instances
-
Configuration versioning
Database Schema
-- Main configuration database
Database: geoserver_configs
-- Key tables created by JDBC Config:
- object -- Main configuration objects
- property -- Object properties
- property_type -- Property type definitions
- default_object -- Default configurations
Dynamic Initialization
The system automatically detects database state on startup:
# Check if JDBC config tables exist
TABLE_EXISTS=$(psql ... -c "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'object');")
if [ "$TABLE_EXISTS" = "t" ]; then
# Tables exist - check for data
CONFIG_COUNT=$(psql ... -c "SELECT COUNT(*) FROM object;")
if [ "$CONFIG_COUNT" -gt "0" ]; then
# Existing config - don't reinitialize
initdb=false
import=false
else
# Empty tables - import from filesystem
initdb=false
import=true
fi
else
# No tables - full initialization
initdb=true
import=true
fi
Security Backup System
Critical security files are continuously backed up to prevent data loss during pod restarts or failures.
Backed Up Directories
/opt/geoserver/data_dir/security/
├── usergroup/default/ # User and group definitions
├── role/default/ # Default role mappings
└── role/keycloak_service/ # Keycloak role mappings
Backup Tables
-- Security file backups
CREATE TABLE geoserver_security_backup (
id SERIAL PRIMARY KEY,
file_path VARCHAR(255) NOT NULL UNIQUE,
file_content TEXT NOT NULL, -- Base64 encoded
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Keystore backup
CREATE TABLE geoserver_keystore (
id SERIAL PRIMARY KEY,
jceks BYTEA, -- Binary keystore data
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Persistent Volumes
For data that cannot be stored in JDBC Config, selective PVC mounting is used:
persistence:
enabled: true
size: 1Gi
directories:
- gwc # GeoWebCache configuration
- gwc-layers # Layer-specific cache settings
- user_projections # Custom CRS definitions
- styles # SLD/CSS style files
- logs # Application logs
- monitoring # Performance data
Development Workflows
Local Development Setup
Prerequisites
-
Docker and Docker Compose
-
Access to SDL container registry
-
PostgreSQL client tools
-
kubectl and Helm
Running Locally
# Clone repositories
git clone https://github.com/your-org/df-geoserver.git
git clone https://github.com/your-org/sdl.git
# Build custom GeoServer image
cd df-geoserver
docker build -t df-geoserver:local .
# Deploy with Helm (from SDL repo)
cd ../sdl
helm install geoserver kubernetes/helm/charts/geoserver \
--set image.geoserver.tag=local \
--set global.namespace=default
Adding Extensions
The Kartoza GeoServer image provides multiple ways to add extensions:
Stable Extensions
Enable stable extensions using the STABLE_EXTENSIONS environment variable:
env:
- name: STABLE_EXTENSIONS
value: "charts-plugin,db2-plugin,mongodb-plugin"
Use the plugin name without the full filename. For example, use wps-plugin instead of geoserver-2.27.1-wps-plugin.zip.
|
Community Extensions
Enable community extensions that are still in development:
env:
- name: COMMUNITY_EXTENSIONS
value: "gwc-sqlite-plugin,ogr-datastore-plugin,sec-keycloak-plugin"
- name: FORCE_DOWNLOAD_COMMUNITY_EXTENSIONS
value: "true" # Force download of latest versions
Default Extensions
By default, these extensions are activated:
- vectortiles-plugin - Vector tiles support
- wps-plugin - Web Processing Service
- libjpeg-turbo-plugin - Optimized JPEG encoding
- control-flow-plugin - Request flow control
- pyramid-plugin - Image pyramid support
- gdal-plugin - GDAL raster formats
- monitor-plugin - Request monitoring
- inspire-plugin - INSPIRE compliance
- csw-plugin - Catalog Service for Web
To customize which default extensions are active:
env:
- name: ACTIVE_EXTENSIONS
value: "control-flow-plugin,csw-plugin,wps-plugin"
# This will ONLY activate these three, skipping other defaults
Custom JAR Extensions
For custom extensions not available in the repositories:
-
Build-time approach - Add to Dockerfile:
# In df-geoserver Dockerfile FROM kartoza/geoserver:${GS_VERSION} # Add custom extension JARs COPY --chown=geoserveruser:geoserverusers \ ./extensions/custom-extension-*.jar \ /usr/local/tomcat/webapps/geoserver/WEB-INF/lib/ # For {sdl-product-acronym} ABAC extension COPY --chown=geoserveruser:geoserverusers \ ./df-geoserver-abac/target/df-geoserver-abac.jar \ /tmp/df-geoserver-abac.jar -
Runtime approach - Configure in bootstrap.sh:
# Enable custom extensions conditionally if [[ "$GEOSERVER_ABAC" =~ ^[Tt][Rr][Uu][Ee]$ ]]; then echo "Support for GeoServer ABAC has been enabled." cp /tmp/df-geoserver-abac.jar \ /usr/local/tomcat/webapps/geoserver/WEB-INF/lib/df-geoserver-abac.jar # Set proper ownership chown geoserveruser:geoserverusers \ /usr/local/tomcat/webapps/geoserver/WEB-INF/lib/df-geoserver-abac.jar fi -
Volume mount approach - For development:
volumes: - name: custom-extensions hostPath: path: /path/to/extensions type: Directory volumeMounts: - name: custom-extensions mountPath: /opt/geoserver/data_dir/extensions env: - name: GEOSERVER_DATA_DIR_EXTENSIONS value: "/opt/geoserver/data_dir/extensions"
Extension Configuration Example
Complete example combining multiple extension types:
# In values.yaml or deployment.yaml
env:
# Stable extensions
- name: STABLE_EXTENSIONS
value: "monitor-plugin,importer-plugin,web-resource-plugin"
# Community extensions
- name: COMMUNITY_EXTENSIONS
value: "sec-keycloak-plugin,jdbcconfig-plugin"
# Force latest community versions
- name: FORCE_DOWNLOAD_COMMUNITY_EXTENSIONS
value: "true"
# Custom extensions
- name: GEOSERVER_ABAC
value: "true"
# Override default active extensions
- name: ACTIVE_EXTENSIONS
value: "control-flow-plugin,monitor-plugin,wps-plugin,csw-plugin"
Creating Custom Datastores
PostGIS Datastore Example
<dataStore>
<name>df-postgis</name>
<type>PostGIS</type>
<enabled>true</enabled>
<connectionParameters>
<host>df-postgres</host>
<port>5432</port>
<database>spatial_data</database>
<user>datahub</user>
<passwd>${POSTGRES_PASSWORD}</passwd>
<dbtype>postgis</dbtype>
<schema>public</schema>
<Loose bbox="">true</Loose>
<Estimated extends="">true</Estimated>
<validate connections="">true</validate>
<Connection timeout="">20</Connection>
<min connections="">1</min>
<max connections="">10</max>
</connectionParameters>
</dataStore>
Implementing Custom Security
ABAC Extension
The custom df-geoserver-abac extension provides attribute-based access control:
// Example ABAC rule evaluation
public boolean evaluate(Authentication auth, Resource resource) {
// Get user attributes from JWT
Map<String, Object> attributes = extractAttributes(auth);
// Check against resource requirements
return checkAccess(attributes, resource.getRequiredAttributes());
}
Integration Examples
Accessing GeoServer from Applications
Python Example
import requests
from requests.auth import HTTPBasicAuth
class GeoServerClient:
def __init__(self, base_url, token):
self.base_url = base_url
self.headers = {'Authorization': f'Bearer {token}'}
def get_layers(self):
response = requests.get(
f"{self.base_url}/rest/layers",
headers=self.headers
)
return response.json()
def get_wms_image(self, layers, bbox, width=800, height=600):
params = {
'service': 'WMS',
'version': '1.1.0',
'request': 'GetMap',
'layers': layers,
'bbox': bbox,
'width': width,
'height': height,
'srs': 'EPSG:4326',
'format': 'image/png'
}
response = requests.get(
f"{self.base_url}/wms",
params=params,
headers=self.headers
)
return response.content
JavaScript/OpenLayers Example
// Initialize map with GeoServer WMS layer
const wmsLayer = new ol.layer.Tile({
source: new ol.source.TileWMS({
url: 'https://map.example.com/geoserver/wms',
params: {
'LAYERS': 'nasa:blue_marble',
'TILED': true
},
serverType: 'geoserver',
crossOrigin: 'anonymous'
})
});
// Add authentication header
wmsLayer.getSource().setTileLoadFunction((tile, src) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', src);
xhr.setRequestHeader('Authorization', `Bearer ${authToken}`);
xhr.responseType = 'blob';
xhr.onload = () => {
tile.getImage().src = URL.createObjectURL(xhr.response);
};
xhr.send();
});
Federation
Multi-Instance Deployment
For high availability, deploy multiple GeoServer instances sharing the same JDBC config:
# In values.yaml
replicaCount: 3
# Ensure all instances use same database
env:
- name: POSTGRES_HOST
value: df-postgres-primary # Use primary for writes
Federation with Other SDL Nodes
Configure cascading WMS/WFS to federate layers from other nodes:
<remoteStore>
<name>remote-sdl-node</name>
<type>WMS</type>
<capabilitiesURL>
https://remote.sdl.example.com/geoserver/wms?request=GetCapabilities
</capabilitiesURL>
<username>federation-user</username>
<password>${FEDERATION_PASSWORD}</password>
</remoteStore>