Local development deployment¶
utilities/deploy_sites.sh builds the platform images from this checkout,
loads them into a local cluster, and installs the sites Helm release from
helm/ecmwf-sites with utilities/values-local-dev.yaml.
Use the sys-admin guides for shared platforms.
Requirements¶
Docker, kubectl, Helm, and one local Kubernetes runtime: kind,
k3d, or a single-node k3s (run the script on the k3s node). All
cluster nodes must run the same supported image platform: linux/amd64 or
linux/arm64. The script enforces this: it reads the platform of every
node, stops on a mixed-platform cluster or an unsupported platform, and
stops when a --platform override differs from the node platform. For k3s,
it also stops when the cluster has more than one node, because the image
import only reaches the local node.
Deploy¶
make deploy-dev # or select:
make deploy-dev RUNTIME=kind TAG=local-dev
./utilities/deploy_sites.sh --runtime kind --tag local-dev
flowchart LR
A["detect runtime and node platform"] --> B["build 11 images"]
B --> C["load images into the cluster nodes (kind load / k3d import / k3s ctr)"]
C --> D["verify loaded images on every node"]
D --> E["ensure TLS Secret (self-signed when missing)"]
E --> F["helm upgrade --install sites (post-rendered, values-local-dev.yaml)"]
F --> G["delete platform pods so they use the loaded images"]
The script never pushes images to a registry. Site images are loaded under
both the short name and the full registry/project/name reference, because
the operator renders full references into Site Pod image settings. Do not
run the script while another operator changes the same release.
The 11 built images are core, hub, operator, site-api,
site-proxy, site-admin, site-oauth2, site-s3, site-webdav,
site-mirror, and site-initializer (matrix in set_image_matrix()).
core, hub, and operator use the Dockerfiles under
utilities/docker/local-dev/ (source builds from this checkout); the site
images reuse the production site/*.Dockerfile files, the same files
make publish-images builds. Each build passes TAG=<local tag> as a build
argument, and the script inspects each built image and stops when its
platform does not match the requested one. Every site-* image is then
re-tagged with the full registry reference (build_images).
Runtime detection and cluster access¶
Without --runtime, the script reads the current kubectl context and
matches, in order: kind-*, then any context containing k3s, then
k3d-* (or any context containing k3d), then a kubectl version probe
for a +k3s server build. When nothing matches, it stops and tells you to
pass --runtime or set SITES_CLUSTER_RUNTIME.
With a runtime selected, the script prepares access (configure_cluster_access):
- kind: takes the cluster name from the
kind-<name>context, or from the firstkind get clustersentry, and runskind export kubeconfig. - k3d: takes the cluster name from the
k3d-<name>context, or from the firstk3d cluster listentry. - k3s: probes for a usable containerd import command, in order:
k3s ctr,sudo -n k3s ctr,ctr -n k8s.io,sudo -n ctr -n k8s.io. It stops with guidance when none works.
Build, load, and verify¶
Loading (load_image): kind load docker-image per image, k3d image
import per image, or docker save <image> | k3s ctr images import -. Each
site-* image is loaded twice, under both its tags (load_images).
Verification (verify_loaded_images) runs before Helm. It lists the cluster
nodes (kind get nodes; k3d node containers by label; k3s: the local
node), reads the containerd image list on each node, and stops when any
image is missing. Short names are checked as docker.io/library/<image>;
site-* images are additionally checked under the full registry reference.
Helm upgrade¶
deploy_sites.sh then (deploy_sites):
- applies the namespace (default
sites-dev) idempotently; - ensures the TLS Secret (see below);
- runs
helm upgrade --install sites helm/ecmwf-siteswith the local values file and these overrides: images.tagandsitesConfig.hub.versionset to the local tag, so the release references the images it just loaded;images.pullPolicyandsitesConfig.kubernetes.image_pull_policyset toIfNotPresent, so neither platform pods nor Site pods try to pull the local tags;sitesConfig.kubernetes.private_registry,private_registry_project, andprivate_registry_image_pull_secrets_namefrom the registry settings below;- one
--set-file sitesConfig.branding.<asset>per branding file present (see Local branding assets); - deletes all pods labelled
sites.ecmwf.int/appin the platform namespace (forced, grace period 0) so they restart on the refreshed images. Site pods are not restarted; recreate a Site to apply new Site images.
Post-renderer¶
The Helm release is post-processed by
utilities/local-dev/post-renderer/render.sh: it writes the rendered chart
plus kustomization.yaml to a temporary directory and runs kustomize
build (or kubectl kustomize). The kustomization applies the local
changes the chart does not offer: it runs the Hub Deployment and every
CronJob as user and group 0, so they can write to local (non-NFS) volumes.
With Helm 4, which takes a post-render plugin instead of a direct command,
the script re-installs the same renderer as the Helm plugin
sites-local-dev on every run and passes the plugin name.
Options¶
| Setting | Effect |
|---|---|
--runtime kind\|k3d\|k3s / RUNTIME (make) / SITES_CLUSTER_RUNTIME |
Override runtime auto-detection |
--tag / TAG (make) / SITES_LOCAL_IMAGE_TAG |
Local image tag (default local-dev) |
--platform / SITES_LOCAL_IMAGE_PLATFORM |
Build platform; must match the cluster node platform |
DOCKER_BIN |
Path to the docker binary |
SITES_NAMESPACE |
Namespace (default sites-dev) |
SITES_PRIVATE_REGISTRY, SITES_PRIVATE_REGISTRY_PROJECT |
Registry prefix used in rendered site image references (defaults eccr.ecmwf.int, sites); loading stays local |
SITES_PRIVATE_REGISTRY_IMAGE_PULL_SECRET |
Optional pull-secret name published into local config (also set on the service account) |
SITES_TLS_HOST, SITES_TLS_SECRET_NAME, SITES_TLS_DAYS |
Self-signed certificate host (default: routing.host in utilities/values-local-dev.yaml), secret name (default: routing.tls.secretName), validity (default 365 days) |
SITES_TLS_HUB_HOST, SITES_TLS_HUB_SECRET_NAME |
Hub certificate host (default: routing.hubHost) and secret name (default: routing.tls.hubSecretName; when empty, the main Secret covers both hosts) |
Local values overlay¶
utilities/values-local-dev.yaml is the local overlay. Beyond the defaults
it documents itself, it:
- sets
dev: trueanddebug: true(see below); - routes through ingress mode on host
sites-dev.ecmwf.int(the TLS defaults follow this host) with TLS from Secretsites-dev-tls; - provisions a local 10 GiB
ReadWriteOncepersistent volume claim (PVC) on the cluster default StorageClass instead of shared storage, and disables website NetworkPolicies; - creates placeholder local OpenID Connect (OIDC) and application Secrets through the chart, so the deployment runs without the shared Vault publishing;
- points OIDC at the accounts-test realm, and selects the
prometheusmetrics provider and email notifications with the other integrations off.
Local branding assets¶
Place logo.svg and/or favicon.svg in utilities/branding/ to brand the
local platform:
- The script validates each existing file with the platform startup SVG
rules (
parse_branding_svgfromsites-core, the same checks the Hub and operator apply; see 16). The check runs throughuvagainst this workspace. - A valid file is passed to Helm with
--set-file sitesConfig.branding.<asset>. An invalid file stops the deployment with the platform error message, instead of breaking pods later. - A missing file falls back to the packaged Sites asset.
Local TLS and verification¶
The script reads routing.host and routing.tls.secretName from the local
values file (SITES_TLS_HOST and SITES_TLS_SECRET_NAME override). When
routing.hubHost is set, it also makes the Hub certificate: it reads
routing.hubHost and routing.tls.hubSecretName (SITES_TLS_HUB_HOST and
SITES_TLS_HUB_SECRET_NAME override) and creates a second Secret. When
routing.tls.hubSecretName is empty, the chart uses routing.tls.secretName
for both hosts, so the script puts both host names in that one certificate. It
keeps an existing TLS Secret; delete the Secret to force a new certificate.
Otherwise it generates a self-signed certificate with openssl: RSA-2048,
CN and a DNS Subject Alternative Name for the host, valid for
SITES_TLS_DAYS days, written under a temporary directory with a 077-mask
and published with kubectl create secret tls.
The generated certificate is self-signed. The local values set
sitesConfig.dev: true, which (in the configuration schema) disables TLS
certificate verification for Site API, Splunk, and OpenSearch connections,
and enables unredacted logs. That is acceptable only on an isolated
development cluster: an active network attacker could read request
credentials. Never carry dev: true into a shared environment
(CFG).
Related tests¶
utilities/tests/deploy-sites-branding.test.sh— branding validation and usage text (needsuv,kubectl,helm; skips otherwise). It accepts a plain logo and an internal fragment reference, and rejects ascriptelement, aDOCTYPE, an external image reference, an event attribute, broken XML, non-UTF-8 bytes, and an oversized file. It also checks that the usage text documentsutilities/branding/.utilities/tests/local-dev-post-renderer.test.sh— the Helm post-renderer (needshelmpluskustomizeorkubectl; skips otherwise). It renders the real chart and asserts five rootsecurityContextpatches (Hub plus the four CronJobs), that a representative chart resource survives the render, and that the renderer never injectsSITE_VERIFY_TLS. The patches replace the pod security context of the chart to run as root. The container security contexts stay: a read-only root filesystem and no Linux capabilities.
Run them with bash utilities/tests/<name>.test.sh; neither runs in CI, so
run them after touching deploy_sites.sh or the post-renderer.
Behaviour requirements¶
| ID | Requirement | Implemented in | Verified by |
|---|---|---|---|
| DEV-01 | The script builds all 11 images and loads them locally without pushing. | utilities/deploy_sites.sh (build_images, load_images) |
— |
| DEV-02 | Loaded images are verified before Helm runs; the platform pods are restarted after the upgrade. | deploy_sites.sh (verify_loaded_images, deploy_sites) |
— |
| DEV-03 | A self-signed TLS Secret is created only when absent. | deploy_sites.sh (ensure_tls_secret) |
— |
| DEV-04 | A local branding SVG is validated with the platform rules before install; invalid files stop the deployment; missing files use the packaged asset. | deploy_sites.sh (validate_branding_svg) |
utilities/tests/deploy-sites-branding.test.sh |
| DEV-05 | The usage text documents the utilities/branding/ directory the script reads. |
deploy_sites.sh (usage) |
utilities/tests/deploy-sites-branding.test.sh |
| DEV-06 | The local overlay sets dev: true; the script itself never sets TLS verification off. |
utilities/values-local-dev.yaml |
utilities/tests/local-dev-post-renderer.test.sh (no SITE_VERIFY_TLS injection) |
| DEV-07 | Runtime auto-detection follows the context-name and server-version order and stops with guidance when it cannot detect a runtime; k3s import support is probed in a fixed command order. | deploy_sites.sh (detect_cluster_runtime, configure_k3s_import) |
— |
| DEV-08 | Deployment stops on a mixed-platform or unsupported cluster, on a platform override that does not match the nodes, on a built image with the wrong platform, and on a multi-node k3s cluster. | deploy_sites.sh (select_image_platform, build_images) |
— |
| DEV-09 | The post-renderer runs the Hub Deployment and every CronJob as root through kustomize; Helm 3 uses the script directly, Helm 4 uses the re-installed sites-local-dev plugin. |
utilities/local-dev/post-renderer/ (render.sh, kustomization.yaml, plugin.yaml), deploy_sites.sh (local_dev_post_renderer) |
utilities/tests/local-dev-post-renderer.test.sh |
| DEV-10 | The Helm upgrade pins the release to the locally loaded tag and IfNotPresent pull policies, for platform images and for rendered Site pods. |
deploy_sites.sh (deploy_sites) |
— |