Operating Strata at Scale β Multi-Tenant Designο
Status: Design draft β not yet implemented.
Context: A company using strata to manage 100+ tenants across multiple regions with shared infrastructure and dedicated application resources.
Scenarioο
A managed services company operates a platform for 100+ tenants. Each tenant has:
A code (e.g.
acme,contoso) β unique short identifierA name (e.g. βAcme Corporationβ)
Allowed regions/zones β constrained by GDPR, data residency, or contractual requirements
Environments β typically
dev,test,acceptance,productionA tier β determines sizing, HA, and feature availability
Key constraints:
In-house deployments only β internal ops team, no tenant self-service
Shared infrastructure β one AKS cluster (or similar) per zone/region, shared networking and ingress
Dedicated application resources β per-customer namespace, web app, database, DNS, secrets
Evolving landscapes β new services get added, old ones get deprecated; changes should be non-breaking where possible
Team isolation β each team owns its own landscape and configuration independently; teams collaborate at shared interfaces (providers, base charts, global policies) but a teamβs entire config tree is separate from others
Multiple landscapes β different teams own different landscapes with their own workspaces
Customer onboarding β ~1/week; each customer gets its own config file for clean Git history
Three-Layer Bootstrap Architectureο
Infrastructure is bootstrapped in three layers, each with its own lifecycle and blast radius:
Team
β
βββ Global Bootstrap (once)
β βββ Identity, DNS zones, policies, cost management, audit logging
β
βββ Zone Bootstrap (per zone)
β βββ eu (westeurope + northeurope)
β β βββ AKS, VNet, ACR, Key Vault, ingress, monitoring, ArgoCD
β βββ us (eastus + eastus2)
β βββ AKS, VNet, ACR, Key Vault, ingress, monitoring, ArgoCD
β
βββ Customer Bootstrap (per customer, per zone)
βββ acme: namespace, RBAC, secrets scope, storage, network policies
βββ contoso: namespace, RBAC, secrets scope, storage, network policies
βββ ...
Layer 0: Global Bootstrap (one-time)ο
Organization-wide resources that exist once across all landscapes:
Entra ID / identity and service principals
Global DNS zones
Governance policies and compliance standards
Centralized monitoring (Log Analytics workspace)
Cost management and billing
Managed as a single strata deployment. Changes very rarely.
Layer 1: Zone Bootstrap (per zone, stable)ο
Shared infrastructure within a zone. One strata deployment per zone:
kind: deployment
meta:
name: landscape_alpha_eu
spec:
properties:
landscape: alpha
zone: eu
workspace: infrastructure
environments: [zones/eu.yaml, production.yaml]
stages:
- name: infrastructure # terraform: AKS, VNet, ACR, Key Vault
- name: configure # ansible: cluster setup, cert-manager
- name: platform_services # helm: ArgoCD, traefik, prometheus, loki
Count: ~2β6 deployments per landscape. Changes rarely. Standard strata workflow.
Key resources per zone: AKS cluster, VNet + subnets, ACR (container registry), Key Vault (zone-scoped, path-based isolation per customer), ArgoCD instance, monitoring stack. The zoneβs environment file selects which provider regions are targeted.
Layer 2: Tenant Bootstrap (per tenant, per zone)ο
Tenant-specific resources provisioned into an existing zone. Creates the isolation boundary before apps are deployed:
Kubernetes namespace + RBAC policies
Key Vault secret scope (path-based:
tenants/{code}/*)Tenant storage account
Network policies (ingress/egress rules)
DNS records
This is what the tenant slot concept automates (see below).
Tenant Slots (dedicated, per-tenant)ο
A tenant slot is a lightweight deployment of dedicated resources into an existing zone. It covers two concerns:
Infrastructure bootstrap (Terraform, via strata) β namespace, RBAC, secrets scope, storage, network policies
Application deployment (Helm, via ArgoCD) β the actual app instances running in the tenantβs namespace
Strata handles concern #1 and generates the ArgoCD ApplicationSet entries for concern #2. This separation means:
Strata manages infrastructure lifecycle (create, update, destroy namespaces and resources)
ArgoCD manages application lifecycle (deploy, upgrade, rollback app versions)
App teams own their Helm values; platform team owns base charts and infrastructure
Count: ~100 tenants Γ 4 environments = ~400 slots. Generated from a registry, not hand-authored.
Workspace-per-Layer Patternο
Each deployment layer owns one workspace file. This is the single most important structural decision for operating strata at scale.
Why one workspace per layer?ο
A workspace defines all possible resources and modules for a deployment scope. When two deployment scopes share a workspace, the workspace grows to accommodate both β and both must be kept in sync even when only one changes.
One workspace per layer keeps each workspace small (2β5 resources, 1β3 provisioners), scoped to its layerβs blast radius, and independently evolvable.
Pattern |
Workspace size |
Blast radius |
Independent evolution |
|---|---|---|---|
One workspace for everything |
20β50 resources |
Entire platform |
β Changes affect all layers |
One workspace per layer |
2β5 resources |
That layer only |
β Layers evolve independently |
The three workspacesο
For the three-layer architecture above, this means three workspace files:
workspaces/
bootstrap.yaml β Layer 0: Key Vault, ACR, state backend
infrastructure.yaml β Layer 1: AKS, VNet, ArgoCD, monitoring
application.yaml β Layer 2: namespace, RBAC, secrets scope, storage
Each workspace defines the resources and modules for its layer. Deployments reference exactly one workspace. The workspace does not know about other layers β it only knows about its own resources.
Why this works at 400+ deploymentsο
The 400 tenant slots (100 tenants Γ 4 environments) all reference application.yaml.
The workspace defines what can exist in a tenant deployment. The environment files
control what is active for each tenant and environment. No workspace changes are
needed when onboarding a new tenant β only a new deployment manifest and environment
overrides are needed.
application.yaml β defines: namespace, RBAC, webapp, storage, cdn, monitoring
β
referenced by 400 deployment manifests
each deployment selects its active resources via environment overrides
Layer boundaries are enforced by conventionο
A deployment manifest in Layer 2 references workspace: application. If someone
accidentally puts workspace: infrastructure in a customer deployment, they get access
to zone-level resources β a misconfiguration that crosses the layer boundary.
strata validate run --pattern "deployments/**" (GAP-03 overlap check) detects namespace
and artifact_path collisions that would result from this, providing a safety net even
without an explicit layer-boundary rule.
Example deployment structureο
deployments/
bootstrap.yaml # Layer 0, workspace: bootstrap
zones/
eu.yaml # Layer 1, workspace: infrastructure
us.yaml # Layer 1, workspace: infrastructure
tenants/
acme-eu-prd.yaml # Layer 2, workspace: application
acme-eu-acc.yaml # Layer 2, workspace: application
contoso-eu-prd.yaml # Layer 2, workspace: application
...
The directory structure mirrors the layer hierarchy. Each file references the workspace for its layer. New tenants = new files, no workspace changes.
New Conceptsο
Tenant Registry (kind: tenant)ο
One YAML file per tenant. Onboarding = adding a file. Offboarding = removing it. Git blame shows who changed what for which tenant.
The tenant file carries an environments list β these are environment files that define the tenantβs capability profile (sizing, modules, HA, features). There is no separate βtierβ concept; tiers are just environment files by convention (e.g. environments/tiers/enterprise.yaml).
Environment Merge Orderο
At slot generation, strata merges two environment lists:
Tenant environments β from the tenant file (
spec.environments)Deployment environments β from the deployment matrix (lifecycle stage)
Later layers override earlier ones. This means deployment-level settings (secrets, endpoints, approval gates) always win over tenant-level settings (sizing, modules).
workspace (all resources/modules defined)
β tenant environments applied (tier: toggle modules, set sizing)
β deployment environments applied (lifecycle: secrets, endpoints)
= final resolved configuration for this slot
Per-Tenant File (tenants/{code}.yaml)ο
apiVersion: strata.huybrechts.xyz/v1
kind: tenant
meta:
name: acme
annotations:
description: "Acme Corporation"
labels:
landscape: alpha
spec:
code: acme
name: "Acme Corporation"
zones: [eu]
onboarded: 2025-03-15
environments:
- environments/tiers/enterprise.yaml
features:
new_dashboard: true
apiVersion: strata.huybrechts.xyz/v1
kind: tenant
meta:
name: globex
annotations:
description: "Globex Inc"
labels:
landscape: alpha
spec:
code: globex
name: "Globex Inc"
zones: [eu]
onboarded: 2026-06-01
environments:
- environments/tiers/standard.yaml
- environments/tenants/globex-overrides.yaml # tenant-specific tweaks
Tier Environment Filesο
Tiers are standard kind: environment files that use the existing override mechanism to toggle modules and set sizing:
# environments/tiers/enterprise.yaml
apiVersion: strata.huybrechts.xyz/v1
kind: environment
meta:
name: tier_enterprise
annotations:
description: "Enterprise tier β HA, monitoring, CDN"
spec:
properties:
tier: enterprise
ha_enabled: true
overrides:
resources:
- name: webapp
count: 3
- name: monitoring
enabled: true
- name: backup
enabled: true
- name: cdn
enabled: true
# environments/tiers/standard.yaml
apiVersion: strata.huybrechts.xyz/v1
kind: environment
meta:
name: tier_standard
annotations:
description: "Standard tier"
spec:
properties:
tier: standard
overrides:
resources:
- name: webapp
count: 1
- name: monitoring
enabled: false
- name: backup
enabled: false
- name: cdn
enabled: false
No new model or kind is needed for tiers β they reuse the existing EnvironmentModel with its overrides block.
Why One File Per Customer?ο
Concern |
Single registry file |
One file per customer |
|---|---|---|
Git diffs |
Every change touches the same file |
Changes are isolated per customer |
PR reviews |
Reviewer must scan a long diff |
PR adds/modifies exactly one file |
Merge conflicts |
Two teams onboarding simultaneously = conflict |
No conflicts β different files |
Git blame |
Shows last editor of the file, not per customer |
Shows exactly who changed this customerβs config |
CODEOWNERS |
Cannot scope ownership per customer |
Can assign ownership per customer or per landscape dir |
CI validation |
Must validate all customers on every change |
Can validate only the changed customer |
Strata discovers all *.yaml files in customers/ and assembles the full registry at load time.
Validation Rulesο
Customer codes are unique within a landscape (enforced by filename:
{code}.yaml)Customer zones must exist as zones in the owning landscape
Customer zones must be valid for data residency constraints (cross-referenced with configuration provider regions)
All environment file paths in
spec.environmentsmust resolve to validkind: environmentfilesStrata assembles the full customer list by scanning
customers/*.yamlat load time
Generated Deployments (per customer)ο
strata tenant generate produces standard kind: deployment files β one per tenant Γ zone Γ lifecycle environment. No new kind needed.
# build/tenants/acme-eu-production.yaml (GENERATED β not hand-authored)
apiVersion: strata.huybrechts.xyz/v1
kind: deployment
meta:
name: acme_eu_production
labels:
tenant: acme
zone: eu
spec:
properties:
tenant: acme
zone: eu
workspace: application
environments:
- environments/tiers/enterprise.yaml # from tenant.environments
- environments/production.yaml # from deployment matrix
inputs:
from: deploy/zone-eu.yaml # cross-workspace reference
The application workspace defines all possible customer resources (namespace, RBAC, storage, network policies, app modules). The merged environments control which are active and how theyβre sized. Standard strata build/deploy pipeline handles the rest.
Variable Flow: Tenant Metadata β Terraformο
Tenant files carry two kinds of data that look similar but flow differently:
Field |
Purpose |
Reaches Terraform? |
|---|---|---|
|
Deployment metadata β routing, labeling, audit |
β Not automatically |
|
Runtime values resolved at deploy time |
β
Yes, as |
|
Declares which variable keys this tenant requires |
Contract only β no values |
spec.configuration is metadata, not Terraform inputο
spec.configuration holds arbitrary key/value pairs that are injected into the
generated deploymentβs spec.properties at slot generation time. These appear in
the deployment manifest and are useful for routing, labeling, and audit. They are
not automatically passed to Terraform.
# customers/acme.yaml
spec:
configuration:
crm_id: "42"
invoice_prefix: "ACM"
support_tier: gold
After slot generation, spec.properties in the deployment manifest contains:
# build/customers/acme-eu-production.yaml (generated)
spec:
properties:
customer: acme
zone: eu
crm_id: "42" # β from spec.configuration
invoice_prefix: "ACM"
support_tier: gold
These properties appear in strata deploy list output and the deployment manifest,
but Terraform does not see them unless you also expose them as variables.
How values actually reach Terraformο
customer.yaml spec.environments spec.references.variables
β β β
β tier file (contract only β
β spec.variables[] declares required keys)
β β
β environment variable deployment
β store definition spec.variables[]
β β β
ββββββββββββββββββββ΄βββββββββββββββββββββββββββ
β
ValueController.resolve()
β
ResolvedValues.variables
β
inject_tf_vars()
β
TF_VAR_<key> in subprocess env
β
terraform plan / apply
β
var.<key> in .tf files
Pattern A β Tier-wide constants (same value for all customers in a tier)ο
Put the variable in the tier environment file. All customers using that tier share the same value.
# environments/tiers/enterprise.yaml
spec:
properties:
tier: enterprise
variables:
- key: vm_size
store: constant
value: Standard_D4s_v3
- key: replica_count
store: constant
value: "3"
- key: backup_retention_days
store: constant
value: "30"
Terraform root module:
variable "vm_size" {}
variable "replica_count" {}
variable "backup_retention_days" {}
Pattern B β Tenant-specific constantsο
Put the variable in a per-tenant environment override file and reference it from the tenant file. Only that tenant sees the value.
# environments/tenants/acme-overrides.yaml
spec:
variables:
- key: crm_id
store: constant
value: "42"
- key: invoice_prefix
store: constant
value: ACM
# tenants/acme.yaml
spec:
environments:
- environments/tiers/enterprise.yaml
- environments/tenants/acme-overrides.yaml # β tenant-specific values
Terraform root module:
variable "crm_id" {}
variable "invoice_prefix" {}
Pattern C β CI-injected valuesο
Use store: environment to read from an environment variable in the CI runner.
Useful for values that differ per pipeline run (e.g. a build number or deployment token)
or values that must not appear in YAML source.
# environments/tiers/enterprise.yaml (or deployment spec.variables)
spec:
variables:
- key: deploy_token
store: environment
value: STRATA_DEPLOY_TOKEN # reads os.environ["STRATA_DEPLOY_TOKEN"]
The CI pipeline sets STRATA_DEPLOY_TOKEN before running strata deploy. Strata
reads it, adds it to ResolvedValues.variables, and injects it as TF_VAR_deploy_token.
Feature flags β Terraform booleansο
Feature flags set in a customer file or tier environment flow to Terraform as
TF_VAR_<flag> with the value "true" or "false".
# customers/acme.yaml
spec:
features:
new_dashboard: true
In Terraform:
variable "new_dashboard" {
type = bool
default = false
}
resource "kubernetes_config_map" "features" {
data = {
new_dashboard = var.new_dashboard
}
}
Secretsο
Secrets follow the same path as variables but are marked sensitive. Define them in environment files with a secret store backend:
spec:
secrets:
- key: customer_api_key
store: azure-keyvault
value: customers/acme/api-key # path in Key Vault
Strata injects secrets as TF_VAR_<key> alongside variables. Terraform treats
them the same way β variable "customer_api_key" { sensitive = true }.
Declaring required keys (spec.references)ο
Use spec.references.variables on the customer file to declare which variable
keys a customerβs deployments depend on. This is a contract β strata validates that
every key is defined in the merged environment before the build runs:
# customers/acme.yaml
spec:
references:
variables: [vm_size, crm_id, deploy_token]
secrets: [customer_api_key]
features: [new_dashboard]
If a referenced key is missing from all merged environment layers, strata build
fails with: βTenant βacmeβ requires variable βcrm_idβ but it is not defined in any
merged environment layer.β
Merge precedence (lowest β highest)ο
Workspace defaults
Tenant tier environment files (
spec.environments, in order)Lifecycle/deployment environment files (dev, test, acceptance, production)
Deployment
spec.variables(highest β overrides everything)
Later layers override earlier ones by key. This means production secrets always win over tier defaults, and deployment-level overrides are the final word.
Cross-Workspace Referencesο
The infrastructure workspace produces outputs (AKS endpoint, resource group, ingress IP, ACR login server) that the application workspace needs as inputs. This is a cross-workspace dependency.
Why strata should know about itο
Concern |
Terraform-only (remote state) |
Strata-managed |
|---|---|---|
Dependency validation |
Implicit β Terraform fails at plan time if state doesnβt exist |
Explicit β strata validates before build |
Ordering |
Operators must know to deploy infra first |
|
Discovery |
Hardcoded backend config in Terraform source |
Declared in deployment YAML, resolved at build time |
Audit trail |
Not captured in deployment manifest |
Recorded: which infra deployment provided inputs |
How it worksο
Layer 1: Deployment declares the dependency
The application deployment references an infrastructure deployment via spec.inputs:
# build/customers/acme-eu-production.yaml
spec:
workspace: application
environments: [...]
inputs:
from: deploy/zone-eu.yaml # infrastructure deployment
Layer 2: Infrastructure deployment exposes outputs
After strata build completes for the infrastructure workspace, outputs are written to the build artifact (platform.json):
{
"deployment": "landscape_alpha_eu",
"outputs": {
"aks_cluster_name": "alpha-eu-aks",
"aks_resource_group": "rg-alpha-eu",
"ingress_ip": "20.1.2.3",
"acr_login_server": "alphaeuregistry.azurecr.io",
"keyvault_name": "kv-alpha-eu"
}
}
Layer 3: Application deployment consumes them
At build time, strata resolves the inputs.from reference, reads the upstream platform.json, and injects outputs as deployment properties. These flow into Terraform as variables β no hardcoded remote state config needed in the Terraform source.
infrastructure deployment (zone-eu)
β
β outputs β platform.json
βΌ
application deployment (acme-eu-production)
β
β inputs resolved from platform.json β injected as properties
βΌ
terraform plan/apply receives: var.aks_cluster_name, var.aks_resource_group, ...
Terraform can still use remote state directlyο
This is not either/or. Teams can choose:
Strata-managed inputs β declared in deployment YAML, resolved at build time, validated, auditable
Terraform remote state β
data "terraform_remote_state" "zone" { ... }in the Terraform source, invisible to strataData sources β
data "azurerm_kubernetes_cluster" "zone" { ... }lookups, no state dependency at all
Strata-managed inputs are preferred because they make the dependency explicit and enable strata validate to catch broken references before terraform plan runs.
Validation rulesο
inputs.frommust reference a deployment file that existsThe referenced deployment must have been built (its
platform.jsonexists in the build output)If not built,
strata builderrors: βDeployment βacme_eu_productionβ depends on βlandscape_alpha_euβ which has not been built. Runstrata build --deployment zone-eufirst.β
Hybrid Ownership Modelο
Not everything in a customer deployment is platform-owned. Resources follow this ownership rule:
If a resource dies with the app, it lives with the app. If itβs shared or platform-scoped, it lives in the platform.
Resource |
Owner |
Where |
Managed by |
|---|---|---|---|
Kubernetes namespace + RBAC |
Platform team |
Application workspace (Terraform) |
Strata |
Network policies |
Platform team |
Application workspace (Terraform) |
Strata |
Key Vault secret scope |
Platform team |
Application workspace (Terraform) |
Strata |
Base Helm chart templates |
Platform team |
Shared chart library |
ArgoCD |
App-specific Helm values |
App team |
App repo ( |
ArgoCD |
App database, queue, cache |
App team |
App repo ( |
App CI/CD |
DNS records |
Platform team |
Application workspace or global |
Strata |
The application workspace provisions the isolation boundary (namespace, RBAC, networking, secrets scope). App-specific resources (database, queue, cache) are owned by the app team and deployed via the appβs own CI/CD pipeline.
Zones and Data Residencyο
Zones in Configurationο
Zones in Configurationο
A zone is a logical grouping of provider regions. Zones are defined in the configuration model and serve as the bridge between customer constraints and physical infrastructure.
# In configuration.yaml
spec:
zones:
- name: eu
description: "European Union β GDPR-compliant regions"
regions: [westeurope, northeurope] # Azure: Dublin, Amsterdam
jurisdiction: eu
- name: us
description: "United States"
regions: [eastus, eastus2, eastus3, westus] # Azure: Virginia, Virginia2, Georgia, California
jurisdiction: us
- name: apac
description: "Asia-Pacific"
regions: [southeastasia, australiaeast]
jurisdiction: apac
jurisdictions:
- name: eu
description: "European Union β GDPR"
data_residency: strict # data must not leave jurisdiction
- name: us
description: "United States"
data_residency: standard
- name: apac
description: "Asia-Pacific"
data_residency: standard
How zones link to providersο
Providers declare which regions they operate in. Zones group those regions logically:
configuration.zones[eu].regions = [westeurope, northeurope]
β β
βΌ βΌ
providers/azure-eu-west.yaml: regions: [westeurope] β Dublin
providers/azure-eu-north.yaml: regions: [northeurope] β Amsterdam
A customer says zones: [eu]. At validation time, strata resolves:
customer.zonesβconfiguration.zones[eu].regionsβ[westeurope, northeurope]Checks that the deploymentβs provider operates in one of those regions
Checks that the zoneβs jurisdiction allows the customerβs data classification
Customers reference zonesο
# tenants/acme.yaml
spec:
code: acme
zones: [eu] # allowed in EU zone only β Dublin or Amsterdam
environments:
- environments/tiers/enterprise.yaml
# tenants/globalcorp.yaml
spec:
code: globalcorp
zones: [eu, us] # multi-zone β allowed in both EU and US
environments:
- environments/tiers/enterprise.yaml
Zone deploymentsο
Each zone gets one infrastructure deployment. The zone determines which provider and regions are used:
# deploy/zone-eu.yaml
kind: deployment
meta:
name: landscape_alpha_eu
spec:
properties:
landscape: alpha
zone: eu
workspace: infrastructure
environments: [zones/eu.yaml, production.yaml]
The zone environment file (environments/zones/eu.yaml) sets the provider, region-specific sizing, and any zone-specific overrides.
Validation rulesο
Customer
zonesmust reference zones defined inconfiguration.zonesEach zone must have at least one provider whose
regionsintersect with the zoneβsregionsIf jurisdiction has
data_residency: strict, customer data cannot be replicated outside the zoneβs regionsHard error if validation fails β build stops, not a warning
Audit Trailο
The deployment manifest records:
Tenant code and name
Zone deployed to (logical) and region deployed to (physical)
Jurisdiction and data residency classification
Tier and feature flags active at deploy time
CLI Commandsο
strata tenant Command Groupο
strata tenant list # list all tenants from registry
strata tenant show --code acme # show details for one tenant
strata tenant status # deployment status across all tenants
strata tenant status --landscape alpha # filter by landscape
strata tenant status --env production # filter by environment
Onboardingο
strata tenant onboard --code newcorp --name "New Corp" \
--zones eu --env environments/tiers/standard.yaml
Steps:
Validate zones against configuration provider regions and data residency constraints
Create tenant YAML file (from template)
Generate slot descriptors for all deployment environments
Create secret placeholders (team fills in values)
Optionally run
strata build+strata deployfor the dev environment
Lifecycleο
strata tenant upgrade --chart-version "3.3.0" # upgrade all tenants
strata tenant upgrade --code acme --chart-version "3.3.0" # upgrade one
strata tenant upgrade --env dev --chart-version "3.3.0" # upgrade all dev envs
strata tenant offboard --code oldcorp --confirm # remove tenant
strata tenant migrate --code contoso --from eu --to us # zone migration
Slot Generationο
strata tenant generate # generate all slot descriptors
strata tenant generate --code acme # generate for one tenant
strata tenant generate --env production # generate all production slots
Generated slot files go into build/tenants/ β they are outputs, not hand-edited source files.
Status Dashboardο
strata tenant status --landscape alpha
LANDSCAPE: alpha
ZONE: eu (AKS: healthy, nodes: 12/12)
CODE TIER ZONE DEV TEST ACC PROD APP DRIFT
acme enterprise eu β
β
β
β
3.2.1 none
contoso standard eu β
β
β
β
3.2.1 none
widgetco standard eu β
β
β οΈ β
3.2.0 acc: 1 ver behind
newcorp standard eu β
π β β 3.2.1 onboarding
Bulk Operationsο
strata tenant build --all # build all 400 slots
strata tenant build --code acme # build 4 slots for acme
strata tenant build --env production # build 100 production slots
strata tenant deploy --code acme --env prod # deploy one specific slot
Repository Strategyο
At scale, configuration data and tooling have different change frequencies and reviewers. The recommended split:
Repository |
Purpose |
Changes by |
Frequency |
|---|---|---|---|
|
Strata tooling, schemas, scripts, pipelines |
Platform team |
Infrequent |
|
Teamβs config: tenants, environments, providers, deployments |
Team |
Frequent |
|
Global bootstrap (Terraform + Ansible) |
Platform team |
Rare |
|
Zone bootstrap (AKS, networking, ArgoCD) per team |
Team |
On infra changes |
|
Tenant bootstrap templates (namespace, RBAC) per team |
Team |
Infrequent |
|
Shared base Helm charts |
Platform team |
On platform releases |
|
ArgoCD Application manifests and ApplicationSets |
Platform team |
On app deployments |
|
Application source + app-owned Helm values + app-owned Terraform |
App teams |
Frequent |
Team isolation principle: Each team has its own {team}-config repo (or directory) containing its tenants, environments, and zone configs. Teams share only global resources (identity, DNS, base charts) and interfaces (provider definitions, environment templates). A team can evolve its landscape β add services, deprecate old ones β without affecting other teams.
Why separate config from workspace?
Config is data, not code β lightweight review process
App teams can propose tenant config changes without touching tooling
CI/CD clarity: config repo triggers
strata validateβstrata deploy; workspace repo triggers tool tests
Directory Structureο
Config Repository ({team}-config)ο
Each team has its own config repo. Teams donβt see or touch each otherβs tenants.
alpha-config/
βββ .strata/ # links to platform-workspace
βββ tenants/
β βββ acme.yaml # one file per tenant
β βββ contoso.yaml
β βββ globex.yaml
β βββ ... # ~100 files, one per tenant
βββ environments/
β βββ tiers/
β β βββ starter.yaml # kind: environment β minimal modules
β β βββ standard.yaml # kind: environment β base modules
β β βββ enterprise.yaml # kind: environment β all modules, HA
β βββ tenants/
β β βββ acme-overrides.yaml # optional per-tenant env overrides
β βββ dev.yaml # lifecycle environments
β βββ test.yaml
β βββ acceptance.yaml
β βββ production.yaml
βββ workspaces/
β βββ infrastructure.yaml # shared infrastructure workspace
β βββ application.yaml # per-tenant application workspace
βββ providers/
β βββ azure-eu-west.yaml # westeurope (Dublin)
β βββ azure-eu-north.yaml # northeurope (Amsterdam)
β βββ azure-us-east.yaml # eastus (Virginia)
βββ stack/
β βββ zone-eu.yaml # zone bootstrap config
β βββ zone-us.yaml
βββ deploy/
βββ zone-eu.yaml # deploy zone
βββ zone-us.yaml
Build Outputο
build/
βββ landscape-alpha-eu/ # landscape build output
β βββ platform.json
β βββ sbom.json
βββ tenants/ # generated tenant slots
β βββ acme-dev/
β βββ acme-test/
β βββ acme-acceptance/
β βββ acme-production/
β βββ contoso-dev/
β β ...
β βββ globex-production/
βββ argocd/ # generated ArgoCD manifests
βββ tenant-appsets.yaml # ApplicationSets per tenant
βββ overlays/
βββ acme-dev/
β βββ values.yaml
βββ acme-production/
βββ values.yaml
Scale Characteristicsο
Dimension |
Count |
Management |
|---|---|---|
Landscapes |
2β5 |
One config repo per team |
Zones per landscape |
1β3 |
Manual, evolves with the landscape |
Landscape deployments |
~6β15 |
Standard strata workflow |
Tenant files |
~100 |
One YAML per tenant per landscape |
Tenant deployments (generated) |
~400 |
Generated from tenant files Γ lifecycle envs |
Onboarding rate |
~1/week |
Add one YAML file via PR |
App upgrade frequency |
Weeklyβmonthly |
Update tier env file or per-tenant env |
Infrastructure changes |
Ongoing |
Non-breaking evolution of the landscape |
Implementation Phasesο
Phase 1: Tenant Model + Directory-Based Discoveryο
New
tenantkind β model, service, validationDirectory-based discovery: scan
tenants/*.yaml, assemble registry at load timeTenant
environmentslist resolved and validated (files must exist, must bekind: environment)strata tenant list/strata tenant showZone validation against configuration provider regions
No generation, no slots β just the tenant files as source of truth
Phase 2: Deployment Generationο
strata tenant generateβ generates standardkind: deploymentfiles per tenant Γ zone Γ lifecycle envMerge order: tenant environments first, deployment environments on top
Generated files reference the application workspace + merged environments
Generated files are build outputs, stored in
build/tenants/Standard
strata build/strata deploypipeline handles them β no new builder
Phase 3: Cross-Workspace Inputsο
spec.inputs.fromfield on deployment model β references upstream deploymentBuild resolves upstream
platform.jsonand injects outputs as propertiesValidation: upstream must be built before downstream can build
strata tenant buildresolves infrastructure outputs automaticallystrata tenant deploydeploys into existing infrastructure
Phase 4: Lifecycle Commandsο
strata customer onboardβ add to registry + generate + build + deploy devstrata customer offboardβ remove resources + archive registry entrystrata customer upgradeβ bulk chart version update across slotsstrata customer statusβ dashboard across all customers and environments
Phase 5: Data Residency Enforcementο
Configuration-level jurisdiction definitions
Hard build-time validation: customer zones vs. allowed regions
Audit trail in deployment manifests
SBOM annotations for data classification
Application Deployment via ArgoCDο
Strata does not deploy customer applications directly. Instead, it generates ArgoCD ApplicationSet configurations that ArgoCD syncs from Git.
How it worksο
Strata generates customer bootstrap infrastructure (namespace, RBAC, secrets) via Terraform
Strata generates ArgoCD ApplicationSet entries from the customer registry
ArgoCD syncs application deployments from Git (base chart + app-specific values)
App teams own their Helm values in their app repos; platform team owns base charts
ApplicationSet Generationο
From the customer registry, strata can generate an ArgoCD ApplicationSet that creates per-customer Application resources:
# Generated by: strata customer generate --argocd
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: customer-webapp
spec:
generators:
- list:
elements:
- code: acme
namespace: acme-prod
tier: enterprise
zone: eu
- code: contoso
namespace: contoso-prod
tier: standard
zone: eu
template:
spec:
source:
repoURL: https://git.company.com/deploy-charts
chart: company-webapp
targetRevision: "3.2.1"
helm:
valueFiles:
- values-{{tier}}.yaml
- customers/{{code}}/values.yaml
destination:
server: https://aks-{{zone}}.company.internal
namespace: "{{namespace}}"
This keeps strata in the infrastructure lane and ArgoCD in the application lane.
Open Questionsο
Secret management at scale β One Azure Key Vault per zone with path-based isolation per customer (
customers/{code}/*) is the likely pattern. Strata provisions the secret scope during customer bootstrap; app teams and CI/CD write actual secret values.Rollout ordering β When upgrading the app for all customers, whatβs the rollout strategy? All dev first β all test β all acc β all prod? Or customer-by-customer through all environments? ArgoCD progressive rollout features (e.g. Argo Rollouts) may handle this natively.
Drift detection β How does
strata customer statusknow a deployment is βbehindβ? For infrastructure: compare Terraform state. For apps: query ArgoCD sync status via API or CLI.Multi-zone customers β Contoso has
zones: [eu, us]. Does that mean primary + DR replica? Active-active? How is the relationship between zones modeled for a single customer?Landscape team boundaries β Can a customer span multiple landscapes (different teams)? Or is a customer always owned by exactly one landscape/team?
App-owned infrastructure β App-specific resources (database, queue, cache) are deployed by the app teamβs CI/CD, not by strata. How does strata validate that required app-owned resources exist before deploying the customer deployment? Or is this left to the app pipeline?
Inputs shape β Should
spec.inputs.frombe a single reference or a list? A customer deployment might need outputs from multiple infrastructure deployments (e.g., zone infra + global DNS). List is more flexible but adds complexity to resolution order.