Konstruct 0.7.0
Released: August 2026
Highlights
- Platform Themes replace Kontract — the theme-driven PaaS surface is now simply Themes. Konduit is the flagship platform theme; Ship It!, Metropolis, and your own themes sit alongside it. The word "kontract" is retired everywhere: APIs, CRDs, charts, repositories, and docs
- Themes build on the control plane, serve on zone clusters — the shared build plane (kpack + Harbor) runs on the control plane for every organization; the built theme and app workloads run on a zone cluster (an ordinary workload cluster that hosts platform theme workloads)
- New organization model — the platform team gets a visible platform organization that owns the control plane; the control plane appears in its clusters graph, with every other organization's management cluster ghosted alongside it for platform admins
- Streamlined self-hosted install — bootstrap-as-cluster-template, a default template catalog, and one-command enablement of the Themes surface
Breaking changes
0.7.0 is a hard cutover for anything that used the old kontract names. There are no aliases and no conversion webhooks.
| 0.6 | 0.7.0 |
|---|---|
kontract-operator (chart, image, umbrella values key) | theme-operator |
KontractApp CRD (kontractapps.konstruct.civo.com) | ThemedApp (themedapps.konstruct.civo.com) |
REST routes /api/v1/kontract/*, /api/v1/kontract-cluster | /api/v1/theme/*, /api/v1/theme/cluster |
UI routes /kontract, /kontract/theme/:name | /theme, /theme/run/:name |
Workload namespaces kontract-{org}-{env} | themed-{org}-{env} |
Values gate konduitEnabled | themesEnabled |
Env vars KONTRACT_FORGE_*, KONTRACT_METRICS_URL | THEME_FORGE_*, THEME_METRICS_URL |
Theme contract kontract.js, global kontract, kontract-rpc | theme.js, global theme, theme-rpc |
Theme spec KONTRACT.md, manifest field kontractTheme: v1 | THEME.md, themeSpec: v1 |
Theme repos konstructio/kontract-theme-* | konstructio/theme-* (old URLs redirect) |
Cluster template / stored cluster_type value kontract | theme |
Anything you pinned against the old names — API clients, MCP tool allowlists (tool names regenerate from the OpenAPI operation IDs), bookmarks, values files in your own gitops repositories — must move to the new names.
Organization model changes
Two behaviors change even if you never used Themes:
- Seed records are hidden from the organization list. A
Projectlabeledkonstruct.civo.com/bootstrap: "true"is treated as the control plane's seed record, not an organization. On pre-0.7.0 installs the bootstrap Project doubled as the platform team's organization — after upgrading it disappears from the organization picker until you migrate (below). - The control plane is owned, not ambient. The clusters graph shows the control plane only to the organization that owns it — recorded as a
konstruct.civo.com/platform-organnotation on the bootstrap Project. New installs link this automatically when the first organization is created; platform admins see unowned control planes regardless. Existing installs must add the link by hand.
Fresh 0.7.0 installs get this shape from the seed job automatically. Upgraded installs must create it — see step 7 below.
Upgrading from 0.6 (self-hosted)
The Themes surface is unavailable during the upgrade. Running apps on zone clusters keep serving, but their control-plane records are recreated and every registered theme must be rebuilt.
1. Before you upgrade
- Export the theme-surface resources:
kubectl get kontractapps,zones,themes,characters,ratecards,skucatalogs,subscriptions -A -o yaml > kontract-backup.yaml - Pause Argo CD auto-sync for the
konstructapplication. - Scale the old operator down:
kubectl -n konstruct-system scale deploy konstruct-kontract-operator --replicas=0 - Remove finalizers from every
KontractAppso the CRD swap cannot hang — and do this after the operator is truly stopped; a self-healed operator re-adds them:
kubectl -n <org> patch kontractapp <name> --type=merge -p '{"metadata":{"finalizers":null}}'
2. Migrate secrets and values
- Dual-write the renamed secret keys before deploying — the chart's ExternalSecret maps Vault key names straight to env names. Add
THEME_FORGE_*copies of everyKONTRACT_FORGE_*key, and copykonstruct/kontract/harbortokonstruct/theme/harbor. Delete the old keys only after verification. - In your
gitopsvalues for the umbrella: rename thekontract-operator:key totheme-operator:, point its image at.../images/theme-operator, and renamekonduitEnabledtothemesEnabled. - Set
theme-operator.seedGitops.domain(or bothseedGitops.harborHostandseedGitops.vmHostexplicitly). The 0.7.0 operator chart ships a PreSync seed job that commits its runtime prerequisites into the platformgitopsrepository, and the template refuses to render without these hostnames — an upgraded install that skips this sees its Argo CD comparison fail withexecution error at .../seed-gitops-job.yaml. The job itself is skip-if-present, so on an already-seeded install it runs as a no-op. SetvmHostexplicitly if your metrics ingest hostname is not the derivedprometheus-remote-write.<domain>.
3. Deploy 0.7.0
Bump the umbrella chart. The upgrade installs the ThemedApp CRD and the new operator, and prunes the old operator deployment. The old kontractapps CRD is not pruned automatically — delete it after step 4.
4. Migrate the theme-surface resources
- Re-apply each exported
KontractAppaskind: ThemedApp(same group/version). Dropstatus, and rewritemetadata.finalizersfromkontractapp.konstruct.civo.com/finalizertothemedapp.konstruct.civo.com/finalizer— the new operator does not recognize the old string, and a carried-over finalizer strands the resource on delete. - Registered themes: update each
Themeto the renamed repository (konstructio/theme-*) — or delete and re-register; the operator recreates the backingThemedAppitself. - Delete the old CRD once nothing references it:
kubectl delete crd kontractapps.konstruct.civo.com - Delete the drained
kontract-{org}-{env}namespaces. New workloads land inthemed-{org}-{env}.
5. Migrate stored cluster records
Projects for zone clusters created before 0.7.0 store cluster_type: kontract. The new filters compare against theme, so an unmigrated record leaks into organization and cluster lists instead of being treated as platform infrastructure:
kubectl -n default patch project <zone-cluster> --type=merge \
-p '{"spec":{"cluster_type":"theme","cluster_template_name":"theme"}}'
6. Migrate the default template catalog
If your install carries the live-applied default catalog from 0.6, the theme template is still named kontract. Recreate it under the new name (the API resolves theme clusters against a template named theme):
kubectl -n default get clustertemplate kontract -o json \
| jq '.metadata = {name: "theme", namespace: "default"} | .spec.name = "theme"' \
| kubectl apply -f -
kubectl -n default delete clustertemplate kontract
7. Migrate the organization model
Pre-0.7.0 installs have a single bootstrap Project playing both roles. Split it to match the fresh-install shape.
The team-management operator runs the full control-plane bootstrap against any bootstrap: "true" Project without a BootstrapComplete condition — on an already-running control plane that means re-initializing your gitops repository. Create the seed record with the label off, hydrate its status, and only then flip the label.
# 1. Seed record for the control plane — label OFF until status is hydrated
kubectl apply -f - <<'MANIFEST'
apiVersion: konstruct.civo.com/beta1
kind: Project
metadata:
name: <control-plane-cluster-name>
namespace: default
labels:
konstruct.civo.com/bootstrap: "false"
annotations:
konstruct.civo.com/platform-org: platform
spec:
name: <control-plane-cluster-name>
description: "Control plane seed record"
cluster_name: <control-plane-cluster-name>
custom_template_fields_inputs:
gitopsRepoName: <platform-gitops-repo>
MANIFEST
# 2. Mark bootstrap as already complete (this cluster IS the bootstrapped CP)
kubectl -n default patch project <control-plane-cluster-name> --subresource=status --type=merge -p '{
"status": {
"conditions": [{"type":"BootstrapComplete","status":"True","reason":"MigratedPreExisting",
"message":"Control plane bootstrapped pre-0.7.0","lastTransitionTime":"2026-01-01T00:00:00Z","observedGeneration":1}],
"phase": "provisioned", "detokenized": true, "downstream_repo_created": true,
"gitops_pushed": true, "repo_credentials_set": true, "template_rendered": true, "kbot_setup_check": true
}
}'
# 3. Now it is safe to mark it as the seed record
kubectl -n default label project <control-plane-cluster-name> konstruct.civo.com/bootstrap=true --overwrite
# 4. Rename the old bootstrap Project into the visible platform organization
kubectl -n default get project platform-org -o json \
| jq '.metadata = {name: "platform", namespace: "default", labels: {"konstruct.civo.com/bootstrap": "false"}} | .spec.name = "platform" | {apiVersion, kind, metadata, spec}' \
| kubectl apply -f -
kubectl -n default delete project platform-org
The platform organization is named platform (0.7.0 installs seed it under that name). Skipping this migration leaves the platform organization hidden and the control plane invisible to everyone except platform admins.
8. Migrate the gitops registry
In the registry repository your zone cluster syncs: rename the kontract/ tree to theme/ (and kontract-quotas/ to theme-quotas/), update the app-of-apps Application (kontract-apps → theme-apps, path .../theme), and expect the app label konstruct.civo.com/themed-app and destination namespaces themed-{org}-{env} on operator-committed manifests. Land this in the same window as the operator upgrade or Argo CD orphans one of the two trees.
9. Rebuild every theme
The platform broker now speaks only theme-rpc. A theme built before 0.7.0 goes dark in the runner until rebuilt against the new contract. Trigger a pinned rebuild per theme app:
kubectl -n <org> annotate themedapp <name> konstruct.civo.com/rebuild-revision=<commit-sha> --overwrite
Verify
/api/v1/configreturnsthemes_enabled;/api/v1/theme/*routes respond;/api/v1/kontract/*returns 404kubectl get themedapps -Ashows your apps; nokontract*CRDs remain- The platform organization appears in the organization picker and the control plane renders in its clusters graph
- Each theme loads end-to-end in the runner (
/theme/run/<name>) grep -ri kontractover your owngitopsrepositories returns nothing
Also in this release
-
Zone clusters formalized — the run-target for platform theme workloads is a plain workload cluster; no special cluster template beyond the
themetype -
Registration templates — civo-microservice Pipeline and Helm templates with zero-detokenization CI
-
Operator seed job — once the theme-operator is scaled up (
replicaCount: 1), its PreSync seed job bootstraps the initial-state secret,gitopsprerequisites (Harbor, kpack, VictoriaMetrics components), and theplatformorganization; requiresseedGitops.domainin values. Validated on a fresh self-hosted install with these caveats: the seed job does not create theplatformnamespace (create it before registering themes), the control plane is adopted by the first organization created (re-point thekonstruct.civo.com/platform-organnotation toplatformif that org should own it), and enabling the surface is a deliberate umbrella-values commit — provisioning a theme cluster alone does not flip it:konstruct-api:
extraEnv:
THEMES_ENABLED: "true"
theme-operator:
replicaCount: 1
seedGitops:
domain: <your-domain>
config:
validClusters:
- <your-theme-cluster> # chart default is the SaaS zone list
harbor:
existingSecret: <secret with harbor_url/harbor_username/harbor_password>Themes also need a GitAccount on the
platformorganization (theme repositories are managed under its git group), and Harbor's admin password seeded at Vaultsecret/konstruct/foreman/harbor-adminbefore the harbor component syncs -
Deployed version in the sidebar — the UI shows the exact deployed umbrella chart version, release-candidate suffix included, sourced from a new
chart_versionconfig field (KONSTRUCT_VERSIONbehavior unchanged) -
Theme clusters from the UI — platform admins create and delete theme workload clusters from the clusters graph, which now renders them hanging off the control plane