# OpenOva Naming Convention **Status:** Authoritative | **Updated:** 2026-04-27 This document defines the unified naming standard for all OpenOva infrastructure, platform, and application resources across all cloud providers, regions, and Catalyst Sovereigns. All new resources **must** follow this convention. Existing resources adopt the new names when touched. > **Glossary**: see [`GLOSSARY.md`](GLOSSARY.md). This document deals with how to compose names from the dimensions defined there. --- ## 1. Principles ### 1.1 Dimension-Based Naming Every name is a **composition of typed dimensions** — never free-text, never descriptive prose. Each dimension has a defined abbreviation. Names are deterministic: given the dimensions, the name is computable. ### 1.2 Don't Repeat the Parent When an object lives inside a container that already encodes location, **do not repeat** that information. ``` Parent provides context → Child adds only what is NEW Provider + Region (cloud account) → VPC/Network name = bb + env_type VPC → Subnet/SG/Route name = purpose only K8s Cluster → vcluster name = org only Namespace = env_type + app (or app only) vcluster → Namespace = app only (when 1 vcluster = 1 Environment) Namespace → Secret/ConfigMap/Deployment = purpose only No parent (global scope) → Full encoding required DNS names, K8s contexts, Crossplane CRs, server names ``` ### 1.3 Building Blocks, Not Failover Roles Clusters are named by their **functional security zone** (building block), not by a failover role such as "primary" or "dr". Geographic redundancy is achieved by running the **same building blocks in multiple regions** — PowerDNS lua-records (`ifurlup`, `pickclosest`) handle traffic distribution authoritatively at the DNS layer. The cluster name never changes; the routing does. Calling a cluster "primary" is operationally incorrect because after failover the other region becomes active — the building block label remains stable regardless. ### 1.4 Tags Carry What Names Cannot Resource names are kept minimal. Cloud tags and Kubernetes labels carry the **full context** for cross-cloud dashboards, billing, and compliance audits. ### 1.5 Organization Identity Lives in the vcluster Layer In multi-tenant deployments, Organization identity is expressed through the **vcluster name** (and on the host cluster, the Kubernetes namespace that hosts that vcluster) — never embedded in resource names below. This follows Principle 1.2: the vcluster is the parent that provides Organization context. --- ## 2. Dimension Taxonomy ### 2.1 Provider | Full name | 2-char | 1-char | |-----------|--------|--------| | Hetzner | `hz` | `h` | | Huawei Cloud | `hw` | `w` | | OCI (Oracle Cloud) | `oci` | `o` | | AWS | `aws` | `a` | | GCP | `gcp` | `g` | | Azure | `az` | `z` | | Contabo | `ct` | `c` | ### 2.2 Region Region codes are **provider-scoped**. The same 3-char code is never reused across providers. #### Hetzner | Location | 3-char | 1-char | Notes | |----------|--------|--------|-------| | Falkenstein, DE | `fsn` | `f` | | | Nuremberg, DE | `nbg` | `n` | | | Helsinki, FI | `hel` | `l` | `h` reserved for Hetzner provider | | Ashburn, VA, US | `ash` | `a` | | | Hillsboro, OR, US | `hil` | `i` | `h` reserved → use `i` | | Singapore, SG | `sin` | `s` | | #### Huawei Cloud | Location | 3-char | 1-char | |----------|--------|--------| | AP Southeast (Singapore) | `apse` | `p` | | CN North (Beijing) | `cnn` | `c` | | LA South (São Paulo) | `las` | `q` | | ME (Riyadh) | `mer` | `r` | #### OCI | Location | 3-char | 1-char | |----------|--------|--------| | ME Dubai | `dxb` | `x` | | EU Frankfurt | `fra` | `r` | | AP Singapore | `sg` | `g` | | US Ashburn | `iad` | `d` | | AP Sydney | `syd` | `y` | #### Contabo (legacy) | Location | 3-char | 1-char | |----------|--------|--------| | EU (generic) | `eu` | `e` | > **Collision rule**: the provider's 1-char code takes precedence. Region codes must not reuse the provider's 1-char. The table above already resolves all known collisions. ### 2.3 Building Block Building blocks describe the **security zone** the cluster or resource belongs to. This is stable regardless of which region is serving traffic. | Full name | 3-char | 1-char | Purpose | |-----------|--------|--------|---------| | Restricted Trust Zone | `rtz` | `r` | Production workloads — most restricted, no direct internet exposure | | DMZ (edge) | `dmz` | `d` | Internet-facing — WAF, ingress controllers, WireGuard endpoints | | Management | `mgt` | `m` | Catalyst control plane — console, projector, gitea, JetStream, OpenBao, Keycloak, etc. | ### 2.4 Env Type Renamed from the older `{env}` to avoid collision with the user-facing **Environment** object (see §11). Values unchanged. | Full name | 3-char | 1-char | |-------------|--------|--------| | Production | `prod` | `p` | | Staging | `stg` | `s` | | UAT | `uat` | `u` | | Development | `dev` | `d` | | POC | `poc` | `c` | ### 2.5 Organization | Field | Rule | |---|---| | Format | Lowercase slug, hyphenated. Length 3–32 characters. Must match `^[a-z][a-z0-9-]{2,31}$`. | | Reserved | `system`, `flux`, `crossplane`, `catalyst`, `gitea`, `kube-*`, anything matching a provider/region/bb/env_type code. | | Examples | `acme`, `bankdhofar`, `muscatpharmacy`, `omantel-internal` | | Source of truth | The Organization CRD on the Sovereign's management cluster. | --- ## 3. Core Patterns All **global** objects (no containing parent that encodes location) use the full pattern: ``` {provider}-{region}-{bb}-{env_type} ``` All **scoped** objects use only what the parent does not already provide — see §4. The **Catalyst Environment** (logical, user-facing) uses: ``` {org}-{env_type} ``` See §11 for the Environment object definition. --- ## 4. Object-Type Reference ### 4.1 Global Objects (full encoding always required) | Object | Pattern | Example | |--------|---------|---------| | K8s cluster context | `{prov}-{reg}-{bb}-{env_type}` | `hz-fsn-rtz-prod` | | Server / VM | `{prov}{reg}{bb}-{app}-{#}{env_type}` | `hzfsnr-k8s-1p` | | DNS location code | `{p}{r}{b}{e}` (4 chars) | `hfrp` | | Crossplane CR (on mgt plane) | `{prov}-{reg}-{bb}-{env_type}-{type}` | `hz-fsn-rtz-prod-vpc` | | Flux GitRepository (Sovereign-level) | `{prov}-{reg}-{bb}-{env_type}` | `hz-fsn-rtz-prod` | ### 4.2 Within Provider + Region (don't repeat provider/region) | Object | Pattern | Example | Parent context | |--------|---------|---------|----------------| | VPC / Network | `{bb}-{env_type}` | `rtz-prod`, `dmz-prod` | provider + region | | Cloud SSH Key | `{purpose}-{env_type}` | `cluster-prod` | provider + region | | Load Balancer | `{purpose}-{env_type}` | `ingress-prod` | provider + region | | Floating IP / EIP | `{purpose}-{env_type}` | `ingress-prod` | provider + region | | Object storage bucket | `{env_type}-{purpose}` | `prod-tf-state` | provider + region | | Volume snapshot policy | `{purpose}` | `daily-7d` | provider + region | ### 4.3 Within VPC / Network (don't repeat provider/region/vpc) | Object | Pattern | Example | |--------|---------|---------| | Subnet | `{purpose}` | `workers`, `lb`, `cp` | | Security Group / Firewall Rule | `{purpose}` | `k8s-nodes`, `lb-https` | | Route Table | `{purpose}` | `default`, `nat` | | NAT Gateway | `{purpose}` | `default` | | VPC Peering / Network Attachment | `to-{target-bb}` | `to-dmz`, `to-mgt` | ### 4.4 Within K8s Cluster (host level) The **host cluster** (`{prov}-{reg}-{bb}-{env_type}`) hosts one vcluster per Organization plus Catalyst control-plane workloads. | Object | Pattern | Example | |--------|---------|---------| | Catalyst control-plane Namespace | `catalyst-{component}` | `catalyst-projector`, `catalyst-gitea` | | Per-Org vcluster hosting Namespace | `{org}` | `acme`, `bankdhofar` | | Helm release (Catalyst level) | `{component}` | `external-secrets`, `cert-manager` | | Flux Kustomization (Catalyst level) | `{scope}` | `infrastructure`, `catalyst`, `crossplane` | | Certificate | `{domain-purpose}` | `openova-io-wildcard` | | ServiceAccount | `{role}` | `flux-reconciler`, `projector` | | PVC | `{purpose}` | `data`, `wal` | ### 4.5 Within Namespace (don't repeat anything above) | Object | Pattern | Example | |--------|---------|---------| | Secret | `{purpose}` | `db-credentials`, `hcloud-token` | | ConfigMap | `{purpose}` | `app-config`, `grafana-dashboards` | | Deployment / StatefulSet | `{component}` | `api`, `worker`, `ui` | | Service | `{component}` | `api`, `grpc` | | NetworkPolicy | `{rule}` | `deny-all`, `allow-ingress` | | Ingress / IngressRoute | `{component}` | `api`, `ui` | ### 4.6 Multi-Organization on a Sovereign Management Cluster The management cluster (`{prov}-{reg}-mgt-{env_type}`) hosts Crossplane and Catalyst components. Per-Organization isolation lives in the **vcluster** layer (see §4.7), not in resource names below it. ``` Host cluster: hz-fsn-mgt-prod Namespace: catalyst-projector ← Catalyst control-plane component Namespace: catalyst-gitea Namespace: acme ← parent namespace for Org acme's resources vcluster: acme ← (see §4.7) Crossplane CR: hz-fsn-rtz-prod-vpc ← no Organization slug in CR name; namespace is parent Secret: hcloud-token ← purpose only; namespace provides Org context Namespace: bankdhofar vcluster: bankdhofar ... ``` Organization identity is **never** embedded in the names of resources below the namespace. The namespace is the parent. ### 4.7 vcluster Naming (NEW) The vcluster is the per-Organization control plane on a parent host cluster. One vcluster per Organization per host cluster. | Object | Pattern | Example | |--------|---------|---------| | vcluster (within host cluster) | `{org}` | `acme`, `bankdhofar`, `muscatpharmacy` | | vcluster fully-qualified ref (cross-cluster, kubeconfig context) | `{prov}-{reg}-{bb}-{env_type}-{org}` | `hz-fsn-rtz-prod-acme`, `hz-hel-rtz-prod-acme` | | Flux GitRepository inside vcluster | `environment` | `environment` | | Flux Kustomization inside vcluster | `applications` | `applications` | **Sibling vclusters** named `acme` on `hz-fsn-rtz-prod` and on `hz-hel-rtz-prod` are two physical realizations of the same logical Catalyst Environment `acme-prod` (see §11). ### 4.8 Within a vcluster (per-Application namespace) Inside an Organization's vcluster, each Application gets its own namespace. | Object | Pattern | Example | |--------|---------|---------| | Application namespace | `{app}` | `marketing-site`, `blog`, `shared-postgres` | | All workloads, secrets, configmaps inside | `{component}` / `{purpose}` | `api`, `worker`, `db-credentials` | --- ## 5. DNS Pattern ### 5.1 Structure Two patterns coexist depending on whether the DNS is for **Catalyst control-plane** services or for **Application** endpoints inside an Organization. #### Catalyst control-plane DNS (Sovereign domain) ``` {component}.{location-code}.{sovereign-domain} ``` Example: `console.hfmp.openova.io`, `gitea.hfmp.openova.io`. Used for Catalyst's own services on the management cluster of a Sovereign. The location code is a 4-character dense encoding of provider + region + building-block + env_type, derived from the 1-char columns in §2. #### Application DNS (Environment domain) ``` {app}.{environment}.{sovereign-domain} ``` OR, for white-label Sovereigns (corporate self-host): ``` {app}.{environment}.{org-domain} ``` Examples: - `marketing-site.acme-prod.omantel.openova.io` (acme on Omantel Sovereign) - `marketing-site.acme-prod.acme.com` (acme on its own Sovereign with their own domain) - `blog.acme-prod.omantel.openova.io` (second App in same Environment) The Sovereign's `sovereign-domain` is set at provisioning time; corporate Sovereigns typically rebrand to their own domain. ### 5.2 Location Code Lookup Table | Location code | Provider | Region | Building Block | Env Type | Example DNS | |---------------|----------|--------|----------------|----------|-------------| | `hfrp` | Hetzner | Falkenstein | rtz | prod | `console.hfrp.openova.io` | | `hfrd` | Hetzner | Falkenstein | rtz | dev | `console.hfrd.openova.io` | | `hfdp` | Hetzner | Falkenstein | dmz | prod | `ingress.hfdp.openova.io` | | `hfmp` | Hetzner | Falkenstein | mgt | prod | `gitea.hfmp.openova.io` | | `hlrp` | Hetzner | Helsinki | rtz | prod | `console.hlrp.openova.io` | | `hldp` | Hetzner | Helsinki | dmz | prod | `ingress.hldp.openova.io` | | `hnrp` | Hetzner | Nuremberg | rtz | prod | `console.hnrp.openova.io` | | `hnmp` | Hetzner | Nuremberg | mgt | prod | `console.hnmp.openova.io` | | `hnmd` | Hetzner | Nuremberg | mgt | dev | `console.hnmd.openova.io` | | `harp` | Hetzner | Ashburn | rtz | prod | `console.harp.openova.io` | | `hsrp` | Hetzner | Singapore | rtz | prod | `console.hsrp.openova.io` | | `wprp` | Huawei | AP Southeast | rtz | prod | `console.wprp.customer.io` | | `oxrp` | OCI | Dubai | rtz | prod | `console.oxrp.customer.io` | | `orrp` | OCI | Frankfurt | rtz | prod | `console.orrp.customer.io` | > To derive a code not listed: concatenate the four 1-char codes from the dimension tables in §2. If a collision exists, it will already appear in this table with a resolution. Do not invent new collision resolutions — raise a PR to extend this table. ### 5.3 Coexistence During Migration Old names remain as CNAMEs until all consumers have migrated: ```dns # Old name (CNAME → new) old-service.openova.io CNAME new-service.hfmp.openova.io # New name (real A/AAAA record) new-service.hfmp.openova.io A ``` --- ## 6. Tags and Labels Since resource names are minimal (a VPC named `rtz-prod`, not `hz-fsn-rtz-prod`), tags carry the full context. ### 6.1 Cloud Resource Tags (all providers) ```yaml openova.io/provider: hetzner openova.io/region: fsn openova.io/building-block: rtz openova.io/env-type: prod # renamed from environment openova.io/cluster: hz-fsn-rtz-prod # the host cluster this resource belongs to openova.io/managed-by: catalyst # or: crossplane, opentofu (bootstrap only), manual ``` ### 6.2 Catalyst Resource Tags (vcluster + Environment context) For workloads running **inside** a vcluster: ```yaml openova.io/sovereign: omantel # which Sovereign hosts this openova.io/organization: acme openova.io/environment: acme-prod # Catalyst Environment object openova.io/vcluster: acme # vcluster name within parent host cluster openova.io/host-cluster: hz-fsn-rtz-prod openova.io/application: marketing-site # Application name within the Environment openova.io/blueprint: bp-wordpress # source Blueprint openova.io/blueprint-version: 1.3.0 ``` ### 6.3 Kubernetes Resource Labels (all objects) ```yaml metadata: labels: app.kubernetes.io/managed-by: flux # or: helm, kustomize app.kubernetes.io/component: grafana openova.io/building-block: rtz openova.io/env-type: prod ``` --- ## 7. Multi-Region Architecture and Building Block Symmetry Geographic redundancy is achieved by deploying **the same building blocks in multiple regions**. Both clusters carry the same building block label; neither is designated "primary" or "dr". Traffic distribution is a routing concern owned by PowerDNS lua-records (authoritative DNS-level GSLB) — not a naming concern. See [`MULTI-REGION-DNS.md`](MULTI-REGION-DNS.md) for the lua-record patterns. ``` Region A (Falkenstein) Region B (Helsinki) ──────────────────────── ────────────────────────── hz-fsn-rtz-prod hz-hel-rtz-prod vcluster: acme vcluster: acme ← Catalyst Environment acme-prod vcluster: bankdhofar vcluster: bankdhofar ← Environment bankdhofar-prod (each vcluster has its own (each vcluster has its own Flux watching the Org's Flux watching the Org's Application repos, branch Application repos, branch per env_type) per env_type) hz-fsn-dmz-prod hz-hel-dmz-prod Ingress + WAF Ingress + WAF WireGuard endpoint WireGuard endpoint ↕ PowerDNS authoritative DNS (per Application, lua-records) marketing-site.acme-prod.omantel.openova.io both regions registered — `ifurlup` selects healthy endpoint Management (one per Sovereign, single region recommended) ──────────────────────────────────────────────────────────── hz-nbg-mgt-prod All Catalyst control-plane components — see PLATFORM-TECH-STACK §2. Highlights: Gitea (5 Gitea Orgs: catalog, catalog-sovereign, -per-Catalyst-Organization, system — each Org Gitea Org holds shared-blueprints + one Gitea repo per Application. See GLOSSARY §"Gitea Orgs".) NATS JetStream (event spine + KV; per-Org Accounts) OpenBao (secrets — primary Raft cluster here; sibling replicas in each workload region with async perf replication. Each region's Raft is independent. See SECURITY §5.) Keycloak (per-Org realms in SME-style; per-Sovereign realm in corporate-style) SPIRE server (workload identity) Plus per-host-cluster infrastructure (Cilium, Flux, Crossplane, cert-manager, Kyverno, Harbor, etc.) — see PLATFORM-TECH-STACK §1. ``` When FSN becomes unavailable, `hz-hel-rtz-prod` serves all traffic for Applications with `placement: active-active` or `active-hotstandby`. The cluster name does not change. The PowerDNS lua-record's `ifurlup` health check fails for the FSN backend and the authoritative answer drops it from the response set within the configured probe window. Recovery is a routing event, not a renaming event. --- ## 8. OpenOva Own Sovereign Naming OpenOva's own deployed Sovereign (the one hosting our SaaS Organizations — formerly called "Nova") follows the same convention as any other Sovereign. | Object | Current name | Canonical name | Notes | |--------|-------------|----------------|-------| | Bootstrap K8s context (legacy) | `contabo-mkt` | `ct-eu-mgt-prod` | Adopted as alias for the existing Contabo VPS | | Future management cluster | — | `hz-nbg-mgt-prod` | After Hetzner migration | | Catalyst console (current) | — | `console.cemp.openova.io` | Contabo, EU, mgt, prod | | Catalyst console (post-migration) | — | `console.hnmp.openova.io` | Hetzner, Nuremberg, mgt, prod | The existing cluster `contabo-mkt` is adopted as `ct-eu-mgt-prod` immediately in kubeconfig and documentation. The directory path `clusters/contabo-mkt/` migrates to `clusters/hz-nbg-mgt-prod/` when the bootstrap machine itself is migrated. The bootstrap machine remains online indefinitely as `catalyst-provisioner` (used to bootstrap further Sovereigns). --- ## 9. Migration Rules | Phase | Action | |-------|--------| | **Now** | All new resources use the canonical name | | **On touch** | When modifying an existing resource for any reason, rename it | | **DNS** | Add new name as real record; old name becomes CNAME pointing to new | | **K8s contexts** | Add new context alias alongside old; update scripts and CI | | **Directory paths** | Migrate `clusters/` and `infra/` directories at migration time | | **Tag rename** | `openova.io/environment` → `openova.io/env-type` (single relabel pass during touch) | | **Never rename** | Kubernetes namespace names on running clusters (would require full redeploy) | --- ## 10. Quick Reference — Derivation Algorithm To name any new resource: 1. **Identify scope**: is this a global object (no parent encoding location), a host-cluster-scoped object, a vcluster-scoped object, or a namespace-scoped object? 2. **If global**: compose `{provider}-{region}-{bb}-{env_type}` from the dimension tables. 3. **If scoped**: start from the innermost scope and add only the dimensions the parent does not already provide. Use `{purpose}` at the deepest levels. 4. **If vcluster**: use `{org}` within the host cluster; use the qualified form `{prov}-{reg}-{bb}-{env_type}-{org}` for cross-cluster references. 5. **If a Catalyst Environment**: use `{org}-{env_type}` (see §11). 6. **If DNS**: derive the 4-char location code from the 1-char columns; check the lookup table in §5.2. For Application DNS use `{app}.{environment}.{sovereign-or-org-domain}`. 7. **Always**: add the full tag set from §6 to the resource. 8. **If uncertain**: raise a PR — do not invent ad-hoc names. --- ## 11. Catalyst Environment (User-Facing Object) The **Environment** is the user-facing scope where Applications are installed. Logical concept; one Environment can be realized by multiple vclusters across regions and building blocks. ### 11.1 Naming ``` {org}-{env_type} ``` Examples: `acme-prod`, `acme-dev`, `bankdhofar-prod`, `bankdhofar-uat`, `muscatpharmacy-prod`. > **DR is a Placement, not an Env Type.** There is no `dr` env_type — the canonical values are `prod | stg | uat | dev | poc` (§2.4). Disaster-recovery topology is expressed by the Application's Placement spec (`active-active` / `active-hotstandby` across multiple regions) inside the `*-prod` Environment, not by a separate `*-dr` Environment. ### 11.2 Realization An Environment is realized by: 1. **A branch inside each Application's Gitea repo.** Every Application is its own Gitea repo at `gitea.{location-code}.{sovereign-domain}/{org}/{app}` (e.g. `gitea.hfmp.acme-telecom.openova.io/acme-pharmacy/store-frontend`). Branches `develop`, `staging`, and `main` map to the `dev`, `stg`, and `prod` Environments respectively. The repo FQDN follows §5.1's Catalyst control-plane DNS pattern `{component}.{location-code}.{sovereign-domain}`. This rule is **uniform across SME and corporate** — one Application = one Gitea repo, regardless of scale. 2. **One or more vclusters** (`{org}` named on each parent host cluster). The set of host clusters realizing the Environment is determined by the Environment's Placement spec. 3. **One Flux per vcluster**, watching one branch (per env_type) across all of the Org's Application repos via N `GitRepository` sources. Each Flux applies manifests filtered to its region/building block via `kustomization.yaml` selectors. 4. **JetStream Account** at the Organization level (one per Org); subjects within the Account use the prefix `ws.{org}-{env_type}.>` for per-Environment partitioning. See [`ARCHITECTURE.md`](ARCHITECTURE.md) §5. 5. **One projector consumer-group** materializing per-Environment KV state for the console. 6. **One OpenBao path** rooted at `org/{org}/env/{env_type}/`. 7. **One `EnvironmentPolicy` CR** at `system/catalyst-config/policies/{org}-{env_type}-policy.yaml` in the Sovereign-admin's `system` Gitea Org. Owned by `sovereign-admin`; edit access optionally delegated to `org-admin` via Catalyst RBAC. SME and corporate Sovereigns use **the same CR shape** — only the field values (number of approvers, soak duration, RE-score threshold) differ per Org's policy choice. ### 11.3 Single-region vs multi-region | Mode | Vclusters | Notes | |---|---|---| | Single-region | 1 vcluster on one rtz cluster | SME default. No cross-region failover. | | Multi-region | N vclusters across regions × bb | Corporate / regulated default. PowerDNS lua-records route Application traffic across regions. | The Environment object's spec drives which vclusters get created; `environment-controller` (the Catalyst component) reconciles them. ### 11.4 Why a separate object instead of a tag? - It owns its own Git repo (a tag couldn't). - It owns Placement metadata (a tag couldn't). - It is the unit of Application install/uninstall/promotion. - Renaming it would break Git history and Flux state — naming is therefore stable for the lifetime of the Environment. --- *Authoritative. Cross-reference [`GLOSSARY.md`](GLOSSARY.md) for definitions.*