Workspace Configuration
Top-level configuration files defining complete infrastructure deployments. YAML files orchestrate providers, resources, topology, namespaces, and lifecycle management for platform environments.
Schema
apiVersion: strata.huybrechts.xyz/v1
kind: workspace
meta:
name: <workspace_name> # Required: ^[a-z][a-z0-9_]*$
annotations:
description: <description>
labels:
version: "<version>"
spec:
lifecycle: {} # Optional: workflow phases
properties: # Tool-specific configs
terraform:
organization: <org>
cloudspace: <workspace>
custom: {} # Custom metadata
providers: [] # Cloud providers
provisioners: [] # IaC tools
topology: [] # Infrastructure clusters
namespaces: [] # Application deployments
variables: [] # Non-sensitive data
secrets: [] # Sensitive credentials
Lifecycle Phases
Phase |
Description |
When Executed |
|---|---|---|
|
Clean build artifacts |
Before operations |
|
Validate configuration |
Before planning |
|
Build artifacts |
Before provisioning |
|
Preview changes (dry-run) |
Before applying |
|
Create/update infrastructure |
During deployment |
|
Initialize resources |
After provisioning |
|
Configure infrastructure |
After initialization |
|
Export resource metadata |
After configuration |
|
Health checks |
After deployment |
|
Backup operations |
Before destruction |
|
Teardown infrastructure |
During teardown |
Lifecycle structure:
lifecycle:
<phase_name>:
scripts:
- file: <script_path>
description: <description>
Providers
providers:
- name: <provider_name> # Unique name
file: <provider_path> # Path to provider config
Provisioners
provisioners:
- name: <provisioner_name>
provisioner: terraform | ansible | bicep # IaC tool
source:
repository: <repository_name> # optional — omit for single-repo workspaces
source_path: <path> # path within the repo (or workspace root when repository is absent)
target_path: <path> # optional: path in the build output
backend: # Terraform only — state storage
type: terraform_cloud | azurerm | s3 | ...
configuration: {}
configuration: # tool-specific options (see below)
playbook: site.yml # Ansible: main playbook (default: site.yml)
inventory: inventory/hosts.yml # Ansible: inventory file (auto-discovered if omitted)
ssh_private_key_secret: <name> # Ansible: secret key name in resolved_values.secrets
extra_vars: # Ansible: extra -e variables
key: value
scope: resourceGroup # Bicep: resourceGroup (default) | subscription | managementGroup | tenant
resource_group: <rg> # Bicep: required when scope=resourceGroup
location: <location> # Bicep: required for subscription/managementGroup/tenant
management_group_id: <mg-id> # Bicep: required when scope=managementGroup
deployment_name: strata-infra # Bicep: ARM deployment name (default: strata-{stage})
parameters_file: params.json # Bicep: parameters file relative to source_path
mode: Incremental # Bicep: Incremental (default) | Complete
output: # Terraform only — controls build output files
format: strata | custom | script | none # default: strata
emits: [] # categories to emit (omit = format defaults)
files: [] # custom file definitions (see Build Output Profile)
Build Output Profile (Terraform only)
The output block on a Terraform provisioner controls what .auto.tfvars.json files
strata build run produces. When absent, format: strata is assumed (current behaviour).
output.format modes:
Mode |
Behaviour |
|---|---|
|
Default. Emit all built-in files ( |
|
Emit only what |
|
One user-provided Python/shell script owns all output files. |
|
Copy source files only — no tfvars output generated. |
output.emits[] categories:
Category |
File produced |
Tier |
|---|---|---|
|
|
Build + Deploy |
|
|
Build + Deploy |
|
|
Build |
|
|
Build |
|
|
Build |
|
|
Build |
|
|
Build |
|
|
Build |
|
|
Build |
|
|
Build |
|
|
Build |
|
|
Build |
|
|
Build |
|
|
Build |
features and variables are two-tier: constant/environment store entries are
written at build time; integration-backed entries (Flagsmith, Azure App Config, Vault)
are written by the deployer immediately before terraform init. Secrets are never
written to any file — they are always injected as TF_VAR_* environment variables.
output.files[] — custom file definitions:
Each entry produces one .auto.tfvars.json file. Two modes, mutually exclusive:
Source mode — pass through a key from the merged properties/custom dict:
files:
- name: aks_config.auto.tfvars.json
variable: aks_config # wraps output as { "aks_config": { ... } }
type: object # object (default) | map | list | flat
source: properties # properties | custom
key: aks_config # key to extract from the merged dict
# Multiple variables in one file
- name: platform.auto.tfvars.json
sources:
- variable: aks_config
source: properties
key: aks_config
- variable: dns_config
source: properties
key: dns_config
Script mode — run a Python/shell script that reads platform.json and writes the file:
files:
- name: environment_info.auto.tfvars.json
type: script
script: "@my_repo/scripts/build_env_info.py" # supports @repo_name/path
The script receives these environment variables: STRATA_PLATFORM_PATH,
STRATA_BUILD_PATH, STRATA_OUTPUT_PATH, STRATA_OUTPUT_FILE,
STRATA_WORKSPACE_PATH, STRATA_DRY_RUN.
format: script — one script produces all output files:
output:
format: script
script: scripts/build_tfvars.py # script receives STRATA_PROVISIONER too
Single-repo form — when IaC lives inside the workspace itself, omit repository and
use source_path alone. strata resolves the path relative to the workspace root:
provisioners:
- name: platform_iac
provisioner: terraform
source:
source_path: terraform # resolved as <workspace_root>/terraform
Multi-repo form — when IaC lives in a separate registered repository:
provisioners:
- name: platform_iac
provisioner: terraform
source:
repository: platform-iac # must match a repo registered via `strata repo add`
source_path: deploy/terraform
Provisioner types
Type |
Tool |
Deployer |
Stage types that activate it |
|---|---|---|---|
|
Terraform CLI |
|
|
|
ansible-playbook |
|
|
Topology
Defines infrastructure clusters and components:
topology:
- name: <topology_name>
provider: <provider_name> # Must reference defined provider
provisioner: terraform
type: <topology_type> # docker-swarm, kubernetes, etc.
volumes: [] # Optional: shared volumes
- name: <volume_name>
type: <volume_type> # local, replicated, distributed
components: [] # VMs, nodes
- name: <component_name> # Unique across workspace
role: <role> # manager, worker, control-plane, etc.
resource:
file: <resource_path>
count: <count> # 1-100
module: # Optional
name: <module_name>
file: <module_path>
labels: {}
tags: []
Component roles by topology:
docker-swarm:
manager(1, 3, 5, 7 - odd for quorum),worker(1+)kubernetes:
control-plane/master(1, 3, 5 - odd),worker(1+)custom: Any role name
Namespaces
namespaces:
- name: <namespace_name>
file: <namespace_path>
Variables & Secrets
Variables (non-sensitive):
variables:
- key: WORKSPACE # UPPER_SNAKE_CASE
source: constant # constant, env, file, computed
value: platform
Secrets (sensitive):
secrets:
- key: TERRAFORM_API_TOKEN
source: bitwarden # bitwarden, vault, env, file
value: <secret_id> # ID or reference
Examples
Minimal:
meta:
name: minimal
spec:
providers:
- name: local
file: config/providers/local.yaml
provisioners:
- name: infra
provisioner: terraform
source:
type: local
repository: /
reference: /
source_path: deploy/terraform
deploy_path: terraform
topology:
- name: cluster
provider: local
provisioner: terraform
type: docker-swarm
components:
- name: node
role: manager
resource:
file: config/resources/vm-basic.yaml
count: 1
Single Topology (Terraform only):
meta:
name: platform
labels:
version: "1.0.0"
spec:
properties:
terraform:
organization: myorg
cloudspace: platform-workspace
providers:
- name: kamatera_europe
file: config/providers/kamatera-eu-fr.yaml
provisioners:
- name: platform_infrastructure
provisioner: terraform
source:
type: local
repository: /
reference: /
source_path: deploy/terraform
deploy_path: terraform
topology:
- name: platform_swarm
provider: kamatera_europe
provisioner: terraform
type: docker-swarm
volumes:
- name: config
type: replicated
- name: data
type: local
components:
- name: manager
role: manager
resource:
file: config/resources/vm-manager.yaml
count: 1
- name: worker
role: worker
resource:
file: config/resources/vm-worker.yaml
count: 3
namespaces:
- name: infra
file: config/namespaces/platform-infra.yaml
variables:
- key: WORKSPACE
source: constant
value: platform
secrets:
- key: TERRAFORM_API_TOKEN
source: bitwarden
value: d47e736b-2db8-47d5-b46b-b2c8016ece73
Terraform + Ansible (infrastructure then configuration):
meta:
name: haven
labels:
version: "1.0.0"
spec:
providers:
- name: kamatera_europe
file: config/providers/kamatera-eu-fr.yaml
provisioners:
- name: haven_infrastructure
provisioner: terraform
source:
repository: haven
source_path: terraform
backend:
type: terraform_cloud
configuration:
organization: myorg
workspace: haven-prd
- name: haven_init
provisioner: ansible
source:
repository: haven
source_path: ansible
configuration:
playbook: site.yml
inventory: inventory/hosts.yml
ssh_private_key_secret: haven_ssh_key # resolved from the secret store at deploy time
extra_vars:
env: production
topology:
- name: haven_swarm
provider: kamatera_europe
provisioner: terraform
type: docker-swarm
components:
- name: manager
role: manager
resource:
file: config/resources/vm-manager.yaml
count: 1
- name: worker
role: worker
resource:
file: config/resources/vm-worker.yaml
count: 3
secrets:
- key: haven_ssh_key
source: bitwarden
value: b2f90a12-3c4d-5678-abcd-ef1234567890 # Bitwarden item ID holding the PEM key
- key: TERRAFORM_API_TOKEN
source: bitwarden
value: d47e736b-2db8-47d5-b46b-b2c8016ece73
meta:
name: multi_region
labels:
version: "2.0.0"
spec:
properties:
terraform:
organization: myorg
cloudspace: multi-region
custom:
owner: "DevOps Team"
project: "Global Platform"
providers:
- name: azure_westeurope
file: config/providers/azure-westeurope.yaml
- name: azure_eastus
file: config/providers/azure-eastus.yaml
provisioners:
- name: infra
provisioner: terraform
source:
type: local
repository: /
reference: /
source_path: deploy/terraform
deploy_path: terraform
topology:
- name: europe_cluster
provider: azure_westeurope
provisioner: terraform
type: kubernetes
components:
- name: eu_control_plane
role: control-plane
resource:
file: config/resources/vm-k8s-master.yaml
count: 3
- name: eu_worker
role: worker
resource:
file: config/resources/vm-k8s-worker.yaml
count: 5
- name: us_cluster
provider: azure_eastus
provisioner: terraform
type: kubernetes
components:
- name: us_control_plane
role: control-plane
resource:
file: config/resources/vm-k8s-master.yaml
count: 3
- name: us_worker
role: worker
resource:
file: config/resources/vm-k8s-worker.yaml
count: 5
namespaces:
- name: infrastructure
file: config/namespaces/infra.yaml
- name: applications
file: config/namespaces/apps.yaml
Custom build output — wrapping an existing Terraform repo:
meta:
name: aks_core
spec:
properties:
git_repo_name: iac_aks_core
kubernetes_version: "1.31"
providers:
- name: azure_westeurope
file: "@iac_aks_core/stack/provider.yaml"
provisioners:
- name: terraform
provisioner: terraform
source:
repository: iac_aks_core
source_path: .
backend:
type: azurerm
configuration:
resource_group_name: ${var:TF_RG}
storage_account_name: ${var:TF_SA}
container_name: tfstate
key: ${var:TF_STATE_KEY}
output:
format: custom
emits:
- features # → flags.auto.tfvars.json (enable_* booleans)
- variables # → variables.auto.tfvars.json
files:
# Script produces environment_info.auto.tfvars.json from platform.json
- name: environment_info.auto.tfvars.json
type: script
script: "@iac_aks_core/scripts/build_env_info.py"
# Pass through aks_config/dns_config from environment properties
- name: aks.auto.tfvars.json
variable: aks_config
type: object
source: properties
key: aks_config
- name: dns.auto.tfvars.json
variable: dns_config
type: object
source: properties
key: dns_config
topology:
- name: aks_platform
provider: azure_westeurope
provisioner: terraform
type: kubernetes
components:
- resource: aks_cluster
resources:
- name: aks_cluster
file: "@iac_aks_core/stack/aks.yaml"
Full-control script output:
provisioners:
- name: terraform
provisioner: terraform
source:
repository: my_tf_repo
source_path: .
output:
format: script
script: scripts/build_tfvars.py # writes all .auto.tfvars.json files itself
Execution Flow
Load configuration → Parse and validate workspace YAML
Register providers → Load provider configurations
Lifecycle: clean → Clean build artifacts
Lifecycle: check → Validate configuration
Lifecycle: build → Build deployment artifacts
Lifecycle: plan → Preview infrastructure changes
Provision infrastructure → Execute provisioners for each topology
Lifecycle: provision → Post-provisioning hooks
Lifecycle: initialize → Initialize resources
Lifecycle: configure → Configure infrastructure
Deploy namespaces → Deploy application namespaces
Lifecycle: output → Export resource metadata
Lifecycle: health → Verify deployment health
Best Practices
Naming: Descriptive names (
platform,production,development)Versions: Track versions through labels
Provider organization: Group by region or purpose
Component names: Unique across entire workspace
Secret management: Always use secret managers (Bitwarden, Vault)
Variable naming: UPPER_SNAKE_CASE for keys
Idempotent scripts: Lifecycle scripts safe to run multiple times
Manager/control-plane counts: Odd numbers (1, 3, 5, 7) for quorum
Documentation: Clear annotations for purpose
Multi-Topology
When using multiple topologies:
Shared backend: All topologies share Terraform backend (atomic operations)
Execution order: Processes in defined order
Component names: Must be unique across entire workspace
Provider references: Each topology can use different providers
Validation
Platform validates:
Valid workspace name (lowercase, alphanumeric, underscores)
Required fields (providers, provisioners, topology)
Unique names (providers, provisioners, topologies, components, namespaces, variables, secrets)
Provider/provisioner references exist
Component counts within limits (1-100)
File references exist
Troubleshooting
Validation failed: Check required fields, verify file references, ensure unique names, validate naming patterns
Provider not found: Verify provider name matches definition, check file path, ensure valid configuration
Component conflicts: Check for duplicate names, verify uniqueness across workspace
Topology validation failed: Use odd numbers for manager/control-plane, verify provider/provisioner references
Variable/Secret conflicts: Ensure unique keys, verify valid sources
Lifecycle script failed: Check executable permissions, verify path, review logs, ensure dependencies installed
Advanced
Cost tracking: Use custom properties (owner, costcenter, project, budget_monthly)
Environment separation: Separate workspace files (production.yaml, staging.yaml, development.yaml)
Multi-region: Multiple topologies with different providers
Disaster recovery: Protection lifecycle phase with backup scripts