Skip to content

Platform overview

This page orients developers: what the platform is, how the repository is organised, what gets built, and how releases work.

The platform in one loop

  1. An administrator defines a site in the Hub UI or through the Hub API.
  2. The Hub writes the desired state as a Site custom resource (apiVersion: operators.ecmwf.int/v2, Custom Resource Definition (CRD) sites.operators.ecmwf.int) in Kubernetes.
  3. The operator reconciles each Site resource into running Site Pods plus the Service, persistent volume claim (PVC), Secret, ConfigMaps, and route — all named after the Site id.
  4. Users and automation reach the site through the site URL and the Site API.

The Site resource is the only source of truth. 03-domain-model describes it; 09-lifecycle-and-reconciliation describes the reconciliation; 02-architecture shows how the pieces connect.

Repository layout

Path Contents
core/ sites-core: configuration schema and loader, Site model, Kubernetes exporters, integrations, notifications, jobs
hub/ sites-hub: the Hub web application, Hub API, initializer
operator/ sites-operator: the kopf-based controller
site/ sites-site plus the site applications: Go API, Lua/OpenResty proxy, admin, oauth2, s3, webdav, mirror, initializer, custom application integration
helm/ecmwf-sites/ Helm chart: platform workloads, CRD, role-based access control (RBAC), routing, CronJobs
docs/ These design documents (docs/design/), the end-user guides (docs/end-user/), and the system-administration guides (docs/sys-admin/)
utilities/ Deployment scripts, chart-maintenance tools, release tools, local-dev assets, repository tests
.github/ Workflows and composite actions (see 15-ci-cd)

Python workspace

The four Python packages share one uv workspace (core, hub, operator, site, listed in the root pyproject.toml). From the repository root:

uv venv --python 3.13        # Makefile default: UV_PYTHON = 3.13
uv sync --all-packages --all-groups

make init-workspace runs the same commands. make test refuses to run unless lua is on PATH (the proxy Lua modules are unit-tested), then runs unittest discover over each package's Python tests and go test for the Go API; see 20-testing.

Built images

make publish-images IMAGE_TAG=<tag> builds and pushes these images to $(SITES_PRIVATE_REGISTRY)/$(SITES_PRIVATE_REGISTRY_PROJECT) (defaults eccr.ecmwf.int and sites-oss):

Image Purpose
core Runtime image for the four platform CronJobs (backup, quota, retention, analytics preloader)
hub Hub application server and its init step
operator Site controller
site-api Go file/operation API
site-proxy OpenResty proxy and UI listing
site-admin File Browser administration UI
site-oauth2 OAuth2 Proxy browser login
site-s3 Optional S3-compatible file access
site-webdav Optional WebDAV file access
site-mirror Content mirror container (migration.enabled)
site-initializer Per-site startup preparation

There is no site-custom image: custom applications supply their own. The platform Dockerfiles build from a pinned uv + Python 3.12 slim base image, while the local workspace standardises on 3.13.

Chart defaults are separate: images.hub|core|operator.repository point to eccr.ecmwf.int/sites/..., while this repository publishes to $(SITES_PRIVATE_REGISTRY)/$(SITES_PRIVATE_REGISTRY_PROJECT). Set the chart repository values when you deploy images published from this repository.

Monorepo lockstep versioning

One release version covers:

  • The Python packages sites-core, sites-hub, sites-operator, sites-site (each package's __init__.__version__).
  • The Helm chart version and appVersion, and the chart's default images.tag.
  • The site.version setting used for site application images.

The root VERSION file is the single source. The chart injects images.tag into the platform configuration as site.version, so site application images follow the chart release. images.redis.tag is the only independent image tag.

make check-lockstep-version (utilities/release/check-lockstep-version.sh) compares the four package versions, the chart version and appVersion, and images.tag against VERSION, reports every mismatch, and exits non-zero when any differ. CI runs it in the release workflow. Helpers:

./utilities/release/set-version.sh 2.6.0
./utilities/release/check-lockstep-version.sh
# or: make set-version NEW_VERSION=2.6.0

Release flow

  1. Set the version: make set-version NEW_VERSION=<version>.
  2. Add a ## Version <version> section to CHANGELOG.md. The GitHub release uses this text (utilities/release/changelog-notes.sh).
  3. Merge into main and wait for the CI workflow to pass.
  4. Start the Release workflow from main.

The Release workflow checks the CI gate result for the target commit, re-runs the repository tests, checks the lockstep versions, and refuses an existing tag. The tag-and-release job then creates and pushes the tag with a deploy key that bypasses the organisation tag-protection ruleset, opens the GitHub release, and calls the Publish workflow. Publish verifies that the tag matches VERSION, builds and pushes the images and the chart to oci://eccr.ecmwf.int/sites-oss/ecmwf-sites (the pushed chart copy carries images.tag set to the release tag), and a separate job installs sitesctl from the Sites CLI repository (get.ecmwf.int) to build and upload this documentation. make release is the same path locally: it checks lockstep and calls make publish with the version read from VERSION. Override the registry with SITES_PRIVATE_REGISTRY and SITES_PRIVATE_REGISTRY_PROJECT. The full trigger, permission, and gate model is in 15-ci-cd.

Endpoint anatomy

The Hub owns the platform paths under the public host:

  • /hub/... — Hub UI pages.
  • /hub/api/v1/... and /hub/api/v2/... — Hub API.
  • /hub/admin/... — Hub administration pages.
  • /hub/error/page/<code>/ — shared error pages.
  • /favicon.svg, /favicon.ico — the configured platform icon (/favicon.ico redirects to the versioned SVG; see 16).
  • Root-level redirects: the redirects blueprint (prefix /) serves legacy paths such as /error/page/<code>/ and redirects them to their /hub equivalents.

Each site owns the paths under its own prefix /<space>/<site>/ (the owner replaces the space when the site has none):

  • /<space>/<site>/... — site content and listing, served by the site proxy.
  • /<space>/<site>/s/api/v2/... — authenticated Site API.
  • /<space>/<site>/s/api/v2/pub/... — anonymous read API on public sites.
  • /<space>/<site>/s/admin/, s/dav/, s/s3/ — optional site applications.
  • /<space>/<site>/s/healthz — NGINX stub status; allowed to the Site's own loopback probe and to trusted_ips only.
  • /s/metrics — Prometheus text from the Lua metrics module. Note this location is not under the site prefix. ServiceMonitors scrape it through the Site Service port (4180); OAuth2 Proxy skips authentication for it only while the metrics integration is enabled, and the proxy then requires a trusted client IP. See 21-observability.

Paths are identical in both routing modes; see 14-routing. A user-facing guide to these paths, with examples, is in end-user: Understanding Sites URLs.

Behaviour requirements

ID Requirement Implemented in Verified by
OVR-01 One version string covers the Python packages, the chart, and the image tags. utilities/release/set-version.sh make check-lockstep-version (utilities/release/check-lockstep-version.sh)
OVR-02 The root VERSION file is the only place that defines the release version. VERSION, utilities/release/changelog-notes.sh make check-lockstep-version (utilities/release/check-lockstep-version.sh)
OVR-03 A release publishes the images and the chart under the version read from VERSION, and fails when the versions are not in lockstep. Makefile (release, publish), .github/workflows/release.yml, .github/workflows/publish.yml —
OVR-04 Site application images follow the chart release through site.version; a user-supplied site.version in chart values cannot override it. helm/ecmwf-sites/templates/configmap-sites-config.yaml, core/.../model/site_app_config.py core/tests/test_helm_configuration.py (test_chart_injects_site_version_from_images_tag, test_user_supplied_site_version_in_sites_config_is_overridden)
OVR-05 s/healthz and /s/metrics allow loopback and trusted IPs only; the metrics route exists and is authenticated-exempt only while the metrics integration is active. site/.../applications/proxy/nginx/health.conf, metrics.conf, oauth2-proxy/oauth2.cfg, helm/.../servicemonitor.yaml site/tests/test_proxy_app_config.py (test_health_location_always_allows_loopback, test_health_location_keeps_configured_trusted_ips_after_loopback, test_metrics_wiring_is_rendered_when_metrics_is_enabled, test_disabling_metrics_removes_every_trace_from_the_proxy_configuration)