sdmx-lab · architecture

A whole statistical-data platform,
deployed in one command.

A disposable SDMx teaching lab: metadata registry, authoring workbench, the .Stat Suite, an open-data portal and notebooks — behind one TLS door, seeded end to end, torn down after class.

1 server 28 containers 14 subdomains ~15 min from blank box make deploy
the shape of it

Four layers, each reproducible on its own

Nothing is precious. The box is rebuilt from this repository and a set of idempotent seed scripts, so the disposal-and-rebuild cycle is the normal way to operate it.

01 · provision

The server

OpenTofu creates a Hetzner box with a reserved IP that outlives it, so DNS is a one-time setup. cloud-init installs Docker & git.

02 · umbrella

One TLS door

A single Compose project: Traefik fronts FMR, the FMR Workbench, CKAN, Jupyter and the landing page, with automatic Let's Encrypt certs.

03 · .stat suite

Wrap, don't fork

The OECD platform runs side by side, vendored at a pinned tag and left pristine; a thin overlay wraps it into our domain.

04 · seeds

A worked example

Idempotent scripts create the lab users, the SDMx structural foundation, the sample data, and the CKAN catalogue — and trigger indexation.

topology

One door, two networks

Traefik is the only thing listening on 80/443. It terminates TLS for every subdomain and sits on both Docker networks, bridging the umbrella services and the side-by-side .Stat Suite. Traffic on the connectors animates toward the services.

the dialogue

Inside .Stat: who talks to whom, and what they exchange

.Stat is a dozen cooperating services plus the FMR Workbench that authors into it. Edges are coloured by what flows: structures, data, identity & permissions, and config, search, storage. The DLM is the hub — most producer actions are it handing off to the right service.

identityKeycloakSingle sign-on. Issues OIDC tokens to the apps; the services validate against it.
gatewayKong (api.)Fronts structures and data, routing by path and Accept header to the NSIs and get-data.
structuresNSI ×3The SDMx registry, one per dataspace. Serves and accepts SDMx-ML; backed by MSSQL.
dataget-dataThe data query service. Reads observations from MSSQL, fetches the DSD from the NSI.
ingesttransferImports data files and promotes data design → release.
permissionsauth mgmtHolds the authorization rules; the DLM asks it what a user may do.
discoverysfsReads structures via api. and writes the index into Solr — the only writer to the .Stat Solr.
configconfigServes the tenant/app configuration (dataspaces, settings) to the Data Explorer, DLM and sfs at startup.
authoringFMR WorkbenchAuthors structures via api.; opened from the DLM to edit an artefact.
producer hubDLMOrchestrates the lifecycle: hands off structures, data, permissions and indexing to each service.
disseminateData ExplorerReads structures + data via api., search via sfs. What the public sees.

A verified nuance: get-data reads structures through the NSI API; transfer reaches structures and data directly in MSSQL (it holds the struct/data DB connections), so there is no transfer→NSI call. Not drawn (peripheral support services): share (saved charts → Mongo), data-viewer, and url-shortener.

common operations

Four everyday tasks, step by step

The same components, put to work. Edges keep their colours: structures, data, identity, config & search. Kong's routing (by content negotiation): SDMx-REST 2.0 data requests → get-data; legacy ?format=csv/plain data and all structures → the NSI.

consume A web user looks at a dataflow

Data Explorer · browser

1
User → Exploreropen a dataflow
2
Explorer → sfssearch & navigation
3
Explorer → NSIfetch the DSD (api.)
4
Explorer → get-dataobservations, SDMx-JSON
5
get-data → MSSQLread data (+ DSD from NSI)
6
render chartin the browser

consume An API user downloads data

script / client · api.

1
Client → KongGET /release/rest/data …?format=csv
2
Kong → NSIroute: plain CSV → NSI
3
NSI → MSSQLread observations
4
NSI → ClientSDMx-CSV download

The Explorer's JSON / AllDimensions queries take the other Kong route, to get-data.

author An SDMx operator edits structural metadata

FMR Workbench · opened from the DLM

1
Operator → DLM"edit structure"
2
DLM → Workbenchopen (linkFMR)
3
Workbench → Keycloaksign in (OIDC, stat-suite)
4
Workbench → NSIread the structure (api.)
5
Operator → Workbenchedit & save
6
Workbench → NSIpersist SDMx-ML · permission checked
7
NSI → MSSQLwrite the structure DB

publish An operator publishes data — this is what fills the Solr index

DLM → transfer → sfs

1
Operator → DLMupload a data file
2
DLM → transferimport → design space
3
transfer → MSSQLwrite the design data DB
4
DLM → transferpromote design → release
5
Operator → DLMindex
6
DLM → sfsindex request
7
sfs → NSIread the dataflow's structure
8
sfs → Solrwrite index documents · now discoverable

sfs is the only writer to the .Stat Solr; everything else reads it through sfs's search API.

request routing

Why the same dataflow hits different services

api. is a Kong gateway that routes by Accept header and format, so two requests for the same dataflow can diverge here (verified by the response's Server header). The split is content negotiation, not CSV-vs-JSON: SDMx-REST 2.0 requests (Accept: application/vnd.sdmx.data+json or +csv, ?format=jsondata) go to get-data; legacy ?format=csv, plain, and all structure requests go to the NSI. get-data reads the data from MSSQL and fetches the DSD from the NSI only on a cache miss (measured: +2 NSI calls cold, 0 when warm).

data lifecycle

One indicator, all the way through

The seeds walk a single dataflow (BAFFELAN:DF_EXAMPLE) through the producer's loop. Two facts it makes concrete: each dataspace has its own structure registry, and structures must exist before data.

author

Structures

Agencies, canonical codelists, a DSD & dataflow — in the design space.

load

Data → design

18 observations imported via the transfer service.

promote

design → release

Structures + data transferred to the dissemination space.

index

Faceted search

sfs indexes the dataflow into Solr for discovery.

consume

Explorer

Browsable, filterable and charted in the Data Explorer.

the one command

make deploy, end to end

A single command runs the whole sequence across laptop and box. Because the IP is reserved, there is no DNS step on a rebuild — indexation and all.

1

provision

reserved IP, cloud-init

2

sync

repo → box

3

remote-up

umbrella + TLS

4

dotstat-up

.Stat + users

5

seed-all

structures→data→
promote→index

6

seed-ckan

brand + catalogue

reference

The services

Every public subdomain and the container behind it. Traefik learns the umbrella routes from Docker labels and the .Stat routes from a file-provider config.

SubdomainServiceRole
domain (bare)landingFront door: directory + live status
fmr.FMRFusion Metadata Registry
fwb.FMR WorkbenchAuthoring structures into .Stat
ckan.CKANOpen-data portal
jupyter.NotebooksPython + pysdmx / sdmx1
dotstat.Data ExplorerDissemination front-end
dlm.Data Lifecycle ManagerLoad, validate, promote
keycloak.KeycloakSingle sign-on (realm demo)
api.KongData + structure API gateway
sfs. · transfer. · auth.sfs · transfer · authSearch · import · permissions
repository

Where everything lives

Grouped by layer. Every script is idempotent and documented at the top of the file.

root lifecycle & docs

  • Makefilethe whole lifecycle: provision / sync / deploy / destroy + box targets
  • compose.yamlumbrella Compose; includes each stack, wires the shared network
  • ARCHITECTURE.md · deployment-{design,notes}.mdthis guide, the design, the ops lessons
  • .env.exampleevery tunable, documented

provision/ the server

  • tofu/main.tfreserved IPv4/IPv6, firewall, server
  • tofu/cloud-init.yamlDocker + git + swap at first boot
  • tofu/{variables,outputs,versions}.tfinputs, the IPs, the provider pin

stacks/ umbrella services

  • fmr/ · notebooks/registry and Jupyter, behind Traefik
  • fwb/Workbench + the template wiring it to the .Stat dataspaces
  • ckan/portal image + seed-ckan.sh (branding + catalogue)
  • landing/the front-door app: server-side health checks

stacks/dotstat/ .Stat wrap

  • overlay/render-{config,traefik}.shrouters + re-point config to our domain
  • overlay/dotstat.override.ymlenv fixes: SSO, DLM URLs, the get-data fix
  • overlay/patch-{keycloak,vendor-network}.shsilent-SSO CSP, shared external network
  • overlay/seed-users · seed-permissionsthe lab roster + authorization rules
  • overlay/seed-structures/ · seed-data/the SDMx foundation, data, promote, index