Configuration reference
A TSG deployment is described by one YAML document. This page is the reference
for apiVersion: tsg.dataspac.es/v1alpha1; run tsg --version to see which
versions your CLI accepts.
apiVersion: tsg.dataspac.es/v1alpha1
kind: TsgDeployment
metadata:
name: alfa
spec:
namespace: tsg-alfa
dataspace: { ... }
admin: { ... }
stacks: [ ... ]
The CLI rejects unknown fields. Otherwise, a renamed field could be ignored and produce the wrong manifests without an error.
Core concepts
These terms distinguish the deployment, its runtimes, and the identities hosted by those runtimes.
| Concept | What it is |
|---|---|
spec.dataspace | The trust domain the stacks in this file join. Names the dataspace and states the authority's DID. |
TsgDeployment | The complete desired deployment described by this file. |
spec.stacks[] | Deployable TSG runtimes. A stack owns Kubernetes resources, and is the unit of apply, inventory and prune. |
stack.participantSeeds[] | Dataspace identities onboarded into that runtime. They own no Kubernetes resources. |
Several organisations may deploy one dataspace from separate files. Each file
therefore states the authority instead of deriving it from the stacks it
contains. If a file includes the issuer, dataspace.authority.did must match
the DID served by that issuer. The CLI rejects a mismatch.
Every participant is a tenant, even when a stack has only one. Its DID is always
did:web:<host>:tenants:<id>. This keeps the DID stable if another participant
is added later. Credentials, contract agreements, and catalog entries can
continue to refer to the original DID.
Document
| Field | Type | Required | Description |
|---|---|---|---|
apiVersion | String | Yes | tsg.dataspac.es/v1alpha1 |
kind | String | Yes | TsgDeployment |
metadata.name | String (DNS label) | Yes | Deployment identity. Derives inventory names and ownership labels, so two deployments in one namespace must not share it. |
spec | DeploymentSpec | Yes |
spec
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
namespace | String (DNS label) | Yes | Kubernetes namespace for everything in this file. | |
dataspace | Dataspace | Yes | The trust domain being joined. | |
admin | Admin | Yes | Initial administrator of the SSO bridge. | |
auth | Auth | Client authentication method for the components. | ||
database | Database | PostgreSQL topology. | ||
routing | Routing | Ingress and Gateway API settings. | ||
images | Images | Image overrides per component type. | ||
patches | Array<Patch> | [] | Patches applied to the whole bundle. | |
extraManifests | Array<String> | [] | Extra manifest paths, relative to this file. | |
stacks | Array<Stack> | Yes | At least one deployable runtime. |
spec.dataspace
| Field | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Dataspace name, a lowercase DNS label. |
authority.did | String | Yes | did:web: identifier of the participant issuing the membership credential. |
authority.domain | String | Yes | Host serving the authority's credential type URLs. |
authority.credentialType | String | Yes | Credential type the components accept as proof of membership. |
spec.admin
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
username | String | Yes | Initial administrator username. | |
email | String | Defaults to <username>@example.com. | ||
require2FA | Boolean | false | Force TOTP enrolment on first login. | |
passwordSecret | SecretRef | Yes | Existing Secret holding the initial password. |
spec.auth
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
clientAuthMethod | String | client_secret_post | client_secret_post or private_key_jwt. | |
publicKeysDir | String | keys | Committed directory of public JWKs, relative to this file. |
With private_key_jwt, the public JWK is a render input. The renderer does not
contact the cluster and cannot read it from a Secret. Generate a pair with
tsg keys generate <stack> <client>, which writes
keys/<stack>-<client>.jwk to commit and the private half to a git-ignored
directory. The CLI refuses a JWK containing a d parameter.
spec.database
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
mode | String | perStack | perStack or shared. | |
defaults.instances | Integer | 1 | CloudNativePG instances per cluster. | |
defaults.storageSize | String | 1Gi | Volume size per instance. |
Every component owns its database through a CloudNativePG DatabaseRole. The
login role is named <stack>__<component>, and a separate basic-auth Secret
holds its password. A component cannot read tables owned by another component,
whether they share a stack or a Cluster.
shared puts every stack on one PostgreSQL instance. It saves resources in a
demo, but it is not a hard security boundary because all stacks share the
instance and its capacity. perStack is the default. Use it between
organisations that do not trust each other.
PostgreSQL still grants CONNECT on a database to PUBLIC. Any role in the
instance can connect and read catalog metadata such as table names, but table
contents remain available only to their owner.
spec.routing
At least one of ingress.enabled and gateway.enabled must be true. You can
enable both while moving hosts from Ingress to Gateway API.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
ingress.enabled | Boolean | true | ||
ingress.className | String | nginx | ||
ingress.clusterIssuer | String | letsencrypt | cert-manager issuer annotation. | |
ingress.annotations | Object | Added to every rendered Ingress. | ||
gateway.enabled | Boolean | false | ||
gateway.className | String | eg | GatewayClass name. | |
gateway.clusterIssuer | String | cert-manager ClusterIssuer annotation. | ||
gateway.issuer | String | cert-manager namespaced Issuer annotation. | ||
gateway.redirectHTTP | Boolean | true | Emit an HTTP listener and an HTTP→HTTPS route. | |
gateway.allowedRoutesFrom | String | Same | Same or All. | |
gateway.annotations | Object | Added to every rendered Gateway. |
The CLI renders one Gateway for each distinct host and assigns it to the first
component on that host. Other components add an HTTPRoute. See the
deployment guide for the
mergeGateways requirement.
spec.images
Image settings are keyed by component type. Set either tag or digest, but
not both. If you omit both, the CLI derives the tag from its version.
| Field | Type | Description |
|---|---|---|
controlPlane | Image | |
ssoBridge | Image | |
wallet | Image | |
dataPlanes.<type> | Image | Keyed by data plane type, e.g. http-data-plane. |
useCaseApps.<type> | Image | Keyed by app type, e.g. analytics-orchestration. |
Image
| Field | Type | Description |
|---|---|---|
repository | String | Defaults to the component's own TSG image. |
tag | String | Defaults to v<cli-version>. |
digest | String | sha256:<64 hex>. Pin this for committed bundles. |
A digest is immutable, so the rendered imagePullPolicy becomes
IfNotPresent; a tag renders Always.
spec.stacks
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | String (DNS label) | Yes | Names every rendered resource and the apply set. | |
name | String | Yes | Display name. | |
host | String (DNS name) | Yes | Public host. No scheme, no path. | |
routing | String | path | path or subdomain. | |
database | { instances, storageSize } | Per-stack sizing. Rejected in shared mode. | ||
components | Components | |||
participantSeeds | Array<ParticipantSeed> | [] | Identities onboarded into this runtime. | |
patches | Array<Patch> | [] | Applied after the global ones, to this stack's set only. | |
extraManifests | Array<String> | [] | Paths relative to the configuration file. |
With path routing, every component shares one host. The SSO bridge uses the
root, the wallet uses /wallet, the control plane uses /control-plane, and a
keyed component uses /<key>. This needs one certificate and one DNS record.
With subdomain routing, each component gets its own host.
stack.components
| Field | Type | Description |
|---|---|---|
ssoBridge | Workload | Always rendered; this only overrides it. |
wallet | Workload | Always rendered; this only overrides it. |
controlPlane | Workload + enabled | Opt in with enabled: true. |
dataPlanes | Record<key, DataPlane> | Keyed by component name. |
useCaseApps | Record<key, UseCaseApp> | Keyed by component name. |
A wallet and an SSO bridge are always rendered. A control plane is what makes a participant able to trade data rather than only hold credentials.
The control plane serves all participant seeds in its stack and requires at
least one. The CLI renders per-tenant IAM, wallet bindings, memberships and
catalogs under initTenants. Data planes and server use-case apps select a
seed with participant: <seed-id>. With exactly one seed, selection is automatic;
an ambiguous or unknown selection is rejected. A wallet-only stack may start
empty and onboard participants later.
Connector discovery and data-plane protocol URLs include /tenants/<slug>.
Management clients send the tenant slug explicitly. These bindings select a
tenant; they do not restrict OAuth clients to that tenant.
DataPlane
Everything in Workload, plus:
| Field | Type | Default | Description |
|---|---|---|---|
type | String | the key | http-data-plane or analytics-data-plane. |
subPath | String | the key | Path segment, in path routing. |
dnsPrefix | String | the key | DNS label, in subdomain routing. |
database | Boolean | true | Set false for a data plane that keeps no state. |
participant | String | sole seed | Participant seed id. Required when the stack has multiple seeds. |
The HTTP data plane executes dataflows. Dataset metadata and private asset
configuration live in the control plane. Add dataspace-starter under
useCaseApps to publish and manage them through the SDK. The data plane still
stores operational dataflow state and request logs, so keep its database unless
another persistence mechanism replaces it.
UseCaseApp
An application built on top of a stack. Everything in Workload,
plus type, subPath, dnsPrefix and participant as above.
Only server apps accept participant; static apps have no fixed identity.
Analytics Orchestration selects an analytics data plane for the same participant.
| Type | Shape | What it gets |
|---|---|---|
analytics-orchestration | server | A database, a confidential OAuth client and a rendered config. |
dataspace-starter | static | A workload, a route and a public PKCE client without credentials. |
gaiax-credential-workbench | static | A workload and a route. |
The image profile determines the shape. A server app is configured like a
component and reaches its stack through in-cluster Services. A static app is a
built single-page application that calls the APIs from the browser.
Dataspace Starter's profile also registers a public OAuth client for browser
login. It has no client secret.
useCaseApps:
analytics-orchestration: {}
credentials:
type: gaiax-credential-workbench
subPath: /credentials
Component keys share one namespace because they name the rendered resources.
wallet, control-plane and sso-bridge are reserved.
stack.participantSeeds
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | String (DNS label) | Yes | Tenant slug, and the last segment of the DID. | |
name | String | Yes | Display name. | |
issuer | Boolean | false | This participant issues the membership credential. | |
credentialSubject | Object | Extra claims the authority puts into this participant's credential. | ||
preAuthorizedCodeSecret | SecretRef | Existing Secret holding the OID4VCI pre-authorized code. | ||
members | Array<{ identifier, role }> | [] | role is owner, admin (default) or member. |
At most one participant across the file may be the issuer. Removing a seed is not an offboarding operation and removes no Kubernetes resources.
Workload
The typed customisation block covers common workload settings. Use a patch for other Kubernetes fields.
| Field | Type | Description |
|---|---|---|
replicas | Integer | |
resources | Object | requests and limits, each { cpu, memory }. |
nodeSelector | Object | |
routing.className | String | Overrides the Ingress or Gateway class for this component. |
routing.annotations | Object | Added to this component's Ingress, Gateway and HTTPRoute. |
config | Object | Application configuration, merged over the rendered defaults. |
config is passed to the component's own configuration document. See the
per-application configuration references under Applications.
SecretRef
| Field | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Name of an existing Secret in the namespace. |
key | String | Yes | Key within it. |
Patches
The CLI applies patches and extra manifests after rendering and before apply. This composition step covers Kubernetes settings outside the typed schema.
| Field | Type | Default | Description |
|---|---|---|---|
target | Object | group, version, kind, name, namespace, labelSelector, annotationSelector. | |
merge | Object | Maps merge recursively, arrays are replaced. | |
jsonPatch | Array | RFC 6902 operations. Required for list surgery. | |
delete | Boolean | false | Remove the matched resources from the bundle. |
optional | Boolean | false | Allow a target that matches nothing. |
allowMultiple | Boolean | false | Allow a target that matches several resources. |
patches:
- target:
kind: Deployment
labelSelector: tsg.dataspac.es/component=control-plane
merge:
spec:
template:
spec:
imagePullSecrets:
- name: registry-credentials
- Targets are Kustomize-compatible.
- Patches run in declared order. Global patches run before stack patches.
- Extra manifests load before patches, so patches can also target added objects.
- A target matching zero resources fails unless
optional: true; one matching several fails unlessallowMultiple: true. - A deleted resource is absent from the bundle, and therefore prune-eligible.
The CLI adds identity labels before patches, which makes them available to a
labelSelector. It validates the labels after patching and rejects changes to
them.
Rendered names are part of the contract because patches target them. Component
resources are <stack>-tsg-<component>, with suffixes such as -config,
-ingress, -gateway, -route, -pvc, -role or -binding. Names are
namespace-independent.
Secrets
Rendered resources contain no credential values. They only reference existing
Secrets, for database credentials, OAuth client credentials, private_key_jwt
keys, the initial administrator password and OID4VCI pre-authorized codes.
Before applying anything, tsg deploy checks that every referenced Secret and
key exists. A missing credential then fails at the CLI instead of leaving a
workload in CrashLoopBackOff.
Deliver Secrets with SOPS, Sealed Secrets, External Secrets, or kubectl. For
a demo:
tsg secrets generate participant.yaml --apply
It never overwrites material that already exists.
Patches, extra manifests and application config are caller-controlled and can
still introduce credentials, so inspect those inputs before committing a bundle.
Versioning
The CLI versions three formats independently:
| Format | Lives in | Marker |
|---|---|---|
| Input config | your Git repository | apiVersion |
bundle.json | rendered output | formatVersion |
| Inventory | the cluster | formatVersion |
This separation lets the input schema change without invalidating every bundle, and lets the bundle format change without invalidating cluster inventories. A CLI may read inventory written months earlier. It rejects an inventory format newer than it understands.
A released input version is not edited. Changes that break the input contract use a new version. Migrate a file with:
tsg migrate participant.yaml -w
The command rewrites the file at the newest version this CLI serves. It does not preserve comments or key order, so review the diff.
An apiVersion newer than any your CLI serves means the file was written for a
newer CLI; upgrade rather than editing the marker.
Production notes
private_key_jwtremoves shared client secrets from the deployment. Setauth.clientAuthMethod: private_key_jwt, generate a pair per client withtsg keys generate, and commit only the public halves.require2FAon the admin forces TOTP enrolment on first login.database.mode: perStackgives each stack its own PostgreSQL instance.- Pin digests for anything committed, so the bundle and the running image cannot drift apart.
- Review the rendered output.
tsg renderdoes not contact the cluster, so the manifests in a merge request are exactly what the controller will apply.