FOUNDATION WORKSHOP · GIS

GeoServer 3 on Kubernetes
a simple, durable geospatial platform

Live demo  meteo-gis.sladoire.dev/geoserver/web
Full documentation  meteo-gis-docs.sladoire.dev
Everything shown here is running and tested — not a paper design.

The big picture — identical replicas, shared data

Web app / UI QGIS, tools Mobile OGC standards WMS · WFS · WPS · tiles Ingress load balancer any number of replicas — scale with one command GeoServer replica 1 GeoServer replica 2 GeoServer replica N PostgreSQL · PostGIS · Timescale GeoServer configuration (source of truth) image indexes · observations shared, operator-run Object store (S3) rasters as Cloud-Optimized GeoTIFF shared tile cache Ingestion jobs / decoders models · satellites · observations write file + one index row — nothing else
No local state. Every replica reads the same database and the same object store — kill any of them, nothing is lost.

Nobody configures anything by hand

All configuration is code. A new layer is one declarative line:
# platform configuration (ConfigMap)
MOSAICS: "… ; goes19_abi_ir103 | goes19_abi_ir103 | sat | brightness_temperature | GOES-19 ABI IR 10.3 µm"
▼  at startup, every replica applies it  ▼
meteo-gis init: mosaic published: meteo:goes19_abi_ir103 (K)
meteo-gis init: tile layer created: meteo:goes19_abi_ir103 (gridsets 2)
Consequence: any environment can be rebuilt identically from code — no snowflake server, no “only Bob knows how it's set up”.

Replicas are disposable — and we measured it

starts empty no disk to prepare reads config from DB single source of truth finds the others by DNS no fixed IPs, no member list serves < 1 minute after start
Durability: upgrades and failures become routine — replace instances, never repair them.

What is in the object store

Real content of the demo store — file names carry satellite/sensor or model/parameter and the exact time (naming convention):
buckets/
├── rasters/                                    each image stored once, as COG
│   ├── models/arpege/t/
│   │   └── arpege_t_isobaric85000_20260901T00Z_20260901T06Z.tif   run · validity
│   └── sat/
│       ├── meteosat-12/fci/meteosat-12_fci_ir105_20260901T1200Z.tif
│       └── goes-19/abi/goes-19_abi_ir103_20260904T1400Z.tif      real GOES-19 data
└── gwc-tiles/                                  rendered map tiles — shared cache,
    └── meteo_goes19_abi_ir103/…                a tile rendered once serves everyone
COG = Cloud-Optimized GeoTIFF: replicas read only the byte ranges they need, straight from S3 — no copies on local disks.

Finding the right image — satellite index

One S3 file = one row. GeoServer answers “this area, this instant” with one indexed query. (table sat_granule, real rows)
fid PK satellite UQ IDX instrument UQ IDX product UQ IDX time UQ IDX param_key FK location
7goes-19abiir1032026-09-04 13:00Zbrightness_temperature…_ir103_1300Z.tif
8goes-19abiir1032026-09-04 14:00Zbrightness_temperature…_ir103_1400Z.tif
2meteosat-12fciir1052026-09-01 12:10Zbrightness_temperature…_ir105_1210Z.tif
PK primary key  ·  UQ UNIQUE (satellite, instrument, product, time) — same instant ingested twice = update, never a duplicate  ·  IDX lookup index — the time navigation query
param_key points to the shared parameter referential: every quantity has a canonical unit (here kelvin). A measurement always has a unit.

Finding the right image — model index

Models add two dimensions: the run (reference_time) and the level. Same pattern, richer key. (table model_granule)
fid PK model UQ IDX param_key UQ IDX reference_time UQ time UQ IDX elevation UQ level_type
1arpegeair_temperature2026-09-01 00Z run2026-09-01 00Z85000isobaric (Pa)
2arpegeair_temperature2026-09-01 00Z2026-09-01 06Z +6 h85000isobaric (Pa)
5arpegeair_temperature2026-09-01 00Z2026-09-01 06Z50000 500 hPaisobaric (Pa)
UQ UNIQUE (model, param_key, reference_time, time, elevation)  ·  IDX lookup (model, param_key, time)  ·  GIST spatial index on the raster footprint (the_geom, not shown)  ·  each row also carries location — the s3:// path of the granule file (shown on the satellite slide, omitted here for width)
Clients navigate time, altitude and forecast run — each step is one indexed query on this table, served through standard WMS dimensions.

Observations — SYNOP, METAR, …

The station (written once)
station_id PKnamegeomelevation
LFPGParis-CDGpoint (4326)119
ESSAStockholm-Arlandapoint (4326)42
The measurements (time-partitioned — TimescaleDB hypertable)
station_id PK time PK IDX param_key PK IDX valuesource_unitmessage_type
LFPG2026-09-04 14:00Zair_temperature293.4 KCelMETAR
LFPG2026-09-04 15:00Zair_temperature292.8CelSYNOP
ESSA2026-09-04 14:00Zair_temperature286.1CelMETAR
PK (station_id, time, param_key) — natural key, duplicates impossible  ·  IDX (param_key, time DESC) — “latest value everywhere” query
Values are stored in the canonical unit (kelvin), the source unit is kept. Decoded °C and model K can never be mixed up. GeoServer publishes SQL views on top — decoders and GIS stay decoupled.

Navigating time and altitude is standard

No custom API. Clients add standard WMS dimensions to the map request:
…/geoserver/ows?request=GetMap&layers=meteo:arpege_t
  &TIME=2026-09-01T06:00Z ← validity instant
  &ELEVATION=50000 ← level (here 500 hPa)
  &DIM_REFERENCE_TIME=2026-09-01T00:00Z ← forecast run
Standards over custom code: less to build, less to maintain, nothing proprietary to explain to integrators.

Real data, live on the demo

GOES-19 full disk IR
  • GOES-19 — infrared 10.3 µm,
    brightness temperature (K)
  • Public NOAA source — anyone can rerun the ingestion
  • 6 hourly images, navigable in time
  • Added with 1 ingestion job + 1 config line
This image is served by the platform you are looking at — meteo-gis.sladoire.dev. Two live tropical cyclones on the left.

Where this stands — and what we decide together

Proven, rerunnable tests
  • Database bootstrap, image build, 3-replica cluster
  • S3 tiles + COG rasters, model/sat/obs ingestion
  • Server-side interpolation (IDW + comparative study)
  • Whole stack redeployed from the written runbooks
    on a second cluster, zero deviation
Deliberately open — workshop decisions
  • Identity / RBAC integration
  • Common UI framework
  • Observability stack
  • Messaging (if the platform adopts one)
  • Data retention: owner & policy
Details, decision records and runbooks: meteo-gis-docs.sladoire.dev — every claim in this deck links to its proof there.