Manifest reference
Reference for manifest.yaml, with an annotated example, per-chart value overrides, object storage, and a custom CA bundle
阅读时间22 分钟最后更新于 18 小时前
manifest.yamlshdctl manifest validateshdctl manifest validateshdctl release generateCommit it to version control. The manifest acts as a controlled input for release upgrades: bumping and any other field, then rerunning and , produces a reviewable diff your team can approve before deployment, and ArgoCD or your CD picks it up from there.
releaseVersionshdctl release pull --clean-outputshdctl release generateThe manifest contains no secrets. Secrets are generated separately by from , which lives in your CI secret store or a vault, never in Git. If that file is ever lost, rebuilds it from the secrets already in the cluster.
shdctl secret generatesecrets.import.yamlshdctl secret exportInitialize a manifest
If you don't already have a manifest, generate one interactively with .
shdctl manifest initValidate a manifest
Check a manifest against the embedded CUE schema with . It is the only command that also rejects keys the schema does not define.
shdctl manifest validateAnnotated example
A Self-Hosted Deployment manifest with the most common fields looks like this. Keep and the block even though the schema marks them optional: fails without them. A manifest written for an earlier release can also hold a line: it's ignored, and shdctl warns about it until a future release rejects it, so delete it.
configuration.kubernetes.storageconfiguration.networking.ingress.traefikshdctl release generateplatform:# manifest.yamlreleaseVersion: 2.0.0 # required: matches the release tag in Unity's registryartifactSync: # Set sourceRepository: `shdctl manifest schema` lists this value as its default, # but every `artifact sync` subcommand fails when the field is left out. sourceRepository: uccmpprivatecloud.azurecr.io concurrency: 5 # parallel `artifact sync` workers. Leave it out and # the sync runs one at a time (`verify`: four at a time)deployment: helm: concurrency: 4 # optional, default 1; deploy charts within the same wave # in parallel (helm format only). Waves remain sequential. # The `--concurrency` CLI flag overrides this when both are set. argocd: # defaults for `release generate --format argocd` repoURL: "git@github.com:your-org/your-argocd-charts.git" pathPrefix: "generated-charts" # the repo directory the rendered charts are committed # under, relative to its root. Keep it non-empty: an # empty prefix breaks every child application destinationServer: "https://kubernetes.default.svc" # required: generation fails without it targetRevision: "main" # the branch ArgoCD reads the charts fromconfiguration: # imageVariant: hardened # optional. When set, shdctl appends the matching # suffix (such as -hardened) to image versions that # declare the variant in versions.yaml. Omit for # default images. networking: appDomain: uam.example.com # the FQDN your users reach the app at # ipFamily: ipv6 # optional. "ipv4" (default) or "ipv6" for # single-stack IPv6 clusters. Omit for IPv4. # Optional. Reference an existing Kubernetes Secret (in the install namespace) # whose `ca-bundle.crt` key holds a PEM-encoded CA chain (root + intermediates). # Mounted into builtin workflow StorageTool containers so .NET trusts your # ingress TLS chain. Required only when your ingress uses a private CA not in # the default system trust store, such as an internal corporate or government PKI. # trustedCaSecretName: my-ca-bundle # IPv4 or IPv6 CIDRs allowed to reach the LoadBalancer. List your cluster's # own outbound (NAT egress) addresses alongside your users' and operators': # Unity Studio calls this deployment back at its public appDomain, so those # calls arrive from the cluster's egress address and are refused if it is # not here — Studio then fails with the rest of the deployment healthy. allowedIngressCIDRs: - "203.0.113.0/24" # users and operators - "198.51.100.10/32" # the cluster's own NAT egress address ingress: traefik: type: LoadBalancer tls: enabled: true certificate: traefik-tls-cert # name of the K8s Secret holding the cert # Optional. Pin specific nodePorts for traefik entrypoints. Omit (default) # to let Kubernetes auto-assign from the cluster's nodePort range — the # assigned port is stable for the life of the Service. Set explicit values # only when an external load balancer or firewall rule requires it. # nodePorts: # web: 32080 # HTTP entrypoint # websecure: 32443 # HTTPS entrypoint serviceMesh: istio: enabled: false kubernetes: namespace: asset-solutions # namespace where workloads are deployed dnsService: kube-dns # CoreDNS Service in kube-system, for example rke2-coredns-rke2-coredns on RKE2 docker: repository: registry.example.com # your registry (used after `artifact sync`), or # Unity's registry itself if you pull from it directly namespace: asset-solutions # subpath/namespace within your registry; the schema # refuses it when pulling from Unity's registry directly imagePullSecret: # K8s Secret holding registry pull credentials name: regcred # the older `imagePullSecret: regcred` spelling # still works, and logs a deprecation notice # generate: false # `secret generate` builds that Secret by # default; set false where something else in the # cluster creates and refreshes it, such as a # registry token refresher for short-lived tokens autoscaling: minReplicas: 1 # the platform services, Keycloak and Traefik: with 1, # a service is briefly down while its pod moves during a # node drain; 2 keeps them up through node maintenance maxReplicas: 10 storage: defaultStorageClass: gp3 # garage + license server only; other RWO volumes # use the cluster's default storage class, which # must exist readWriteManyStorageClass: efs # Optional. Annotations added to every pod the release creates. Values are # strings, so quote "true". To change or drop one for a single chart, use # that chart's overrides: at global.podAnnotations for the release's own # charts, at the chart's own pod-annotation key for third-party ones; null # removes it. # # Example: AKS with an HTTP proxy (httpProxyConfig) injects # HTTP_PROXY/HTTPS_PROXY/NO_PROXY into every pod, and platform services call # each other by short Service name, which no NO_PROXY entry matches — so # those calls go to the proxy and fail. This annotation makes AKS skip the # pod; nodes, and so image pulls, still use the proxy, but pods get no # internet access. The automation-manager job pulls the release manifest # from the registry itself, so give it the proxy (unless your registry is # reachable without it): # overrides: # automation-manager: # values: # env: # httpsProxy: http://<proxy>:<port> # noProxy: .svc,.cluster.local,localhost,127.0.0.1 # podAnnotations: # kubernetes.azure.com/no-http-proxy-vars: "true" transformations: parallelism: 30 # max concurrent transformation workflows monitoring: database: enabled: true prometheus: enabled: true # installs a Prometheus stack. If the cluster already # runs the Prometheus Operator, set enabled: false and # installedInCluster: true to add only ServiceMonitors logCollection: enabled: true # Loki + Alloy authentication: x509: # Mandatory mutual TLS (mTLS): every client reaching the deployment must # present a client certificate. Unity Studio's publisher has none, so # Studio publishing does not work while this is enabled in this release. enabled: false # caSecretName: x509-ca-cert # Secret holding the client CA; this is the default infrastructure: sizing: medium # small | medium (default) | large # singleNode: true # optional: collapse infrastructure components to # single-replica for ephemeral test clusters. # Not for production. # components: # per-component CPU/memory/storage overrides # postgresql: # storage: 400Gi # the data volume # backupStorage: 800Gi # the pgBackRest repository volume (backups + WAL). # # Default by sizing profile: 100Gi small, 800Gi # # medium, 1600Gi large. It can only grow, which # # needs allowVolumeExpansion on the StorageClass: # # a smaller value is refused. Ignored, with a # # warning, when objectStore.buckets.postgresBackups # # is set objectStore: provider: garage # "garage" (default) deploys the in-cluster # object store; "s3" uses a bucket you # own. See "Bringing your own object storage". # provider: s3 # endpoint: https://s3.us-east-2.amazonaws.com # scheme://host[:port], no path # region: us-east-2 # forcePathStyle: false # true for MinIO, Ceph RGW, StorageGRID # buckets: # transformationLogs: my-transformation-logs # transformation step logs # mongodbBackups: my-mongodb-backups # MongoDB nightly dumps # studioAssets: my-studio-assets # Unity Studio publications — needs a CORS rule + https # postgresBackups: my-postgres-backups # optional: PostgreSQL backups + WAL # overrides: # optional last-resort layer of extra Helm # <chart-name>: # values, per chart, applied after every layer # values: # the release ships. See "Per-chart Helm value # replicaCount: 3 # overrides" below before reaching for it.
Full schema reference
The annotated example above covers the most common fields. For the complete field list, including every type, default value, constraint, and cross-field rule, print the schema your installed shdctl is using with . Three of the defaults it prints are not applied when you leave the field out: , , and . The example above sets all three.
shdctl manifest schemaartifactSync.sourceRepositoryartifactSync.concurrencydeployment.argocd.destinationServerAuto-discovery
shdctl searches upward from the current working directory for . Pass to override: that file must exist and parse, or the command stops. The search is best-effort, so a command that needs no manifest runs without one.
manifest.yaml--manifest <path>Per-chart Helm value overrides
Every Helm value shdctl renders comes from the release package: a chart's own , a platform layer, and the per-chart values the release's chart template sets. adds one more layer on top of those, per chart, under your control:
values.yamlconfiguration.overridesconfiguration: overrides: keycloak: # must name a chart in the release (see below) values: replicaCount: 3
shdctl release generatevalues.yamlconfiguration.networking.ipFamily: ipv6global.ipFamilyipv6What to know before you use it:
-
A chart name that isn't in the release fails the command. The definitive list is the directories underafter a run. In the release package itself, valid names are the
generated-charts/keys ofhelm_chartsmerged with theextracted-release/common/versions.yamloverlay — most charts are declared only in the common file, so don't judge a name by the overlay alone. A typo stopsextracted-release/platform/shd/versions.yamlwithshdctl release generateinstead of silently doing nothing.configuration.overrides: unknown chart "..." -
Only the block's shape is validated, not what's inside it.confirms that
shdctl manifest validateis a mapping; it cannot know whether a key exists in the chart. Misspell a key underoverrides.<chart>.valuesand Helm ignores it without complaint. Read the generatedvalues:— or its diff, if you commitvalues.yamlto Git — before deploying, and re-check your overrides after every release upgrade.generated-charts/ -
Maps deep-merge, and keys you don't name are left alone.changes
settings: {logLevel: debug}and keeps every sibling underlogLevel.settings -
removes a key, whatever it holds — a scalar, a whole map, a whole array. It is dropped from the generated
null. That file is the chart's rootvalues.yaml, so the chart sees no value at all — unless the key belongs to a subchart, whose own default then takes over, since thosevalues.yamlfiles ship alongside. (An empty mapvalues.yamlis not a removal: it merges into the existing map and changes nothing. An empty array{}does replace the array.)[] -
Listing an array replaces the whole array. To change one element instead, write a mapping where the array sits, keyed by the element's name:configuration: overrides: elasticsearch-eck: values: nodeSets: master-nodes: podTemplate: spec: containers: elasticsearch: resources: limits: cpu: "2"The names depend on your topology — a single-node deployment has one nodeSet named, not
single-node— so read the names your release actually produced from the generatedmaster-nodes. A key matches the element whosevalues.yamlit equals, or whosenameit equals for elements shaped like Kubernetes objects; for an array whose elements have no name, use the index. An element that is a plain value — a string or a number — can be removed by index but not changed; to change one, replace the whole array. Only a mapping patches an array: a list written where the array sits replaces it outright, as above, and a scalar is ignored — leaving the array as the release shipped it. Elements you do not name are untouched, order is preserved, and arrays nested inside an element are patched the same way rather than replaced — so changing one field of a container or a volume claim does not mean restating it. A key naming an element the array does not contain failsmetadata.name, and the error lists the names it found. To remove an element, set it toshdctl release generate; to add one, replace the whole array. Anullnaming an element the array does not contain fails the same way, so a removal does not quietly become a no-op once a later release drops the element itself.nullThat failure needs the array to still be there. If a release removes or renames the array itself, there is nothing left for your mapping to patch — the key is simply not in the release's values — so the mapping is written into the generatedas an ordinary map and Helm ignores it in silence. Nothing errors, because the syntax cannot tell a patch that an upgrade left stale from a map the chart genuinely reads: one more reason to read that file and re-check your overrides after every release upgrade.values.yaml -
Thekey is not accepted. Earlier versions documented it for merging lists; under
_arrayMerge, it now stopsvalues:. Write the mapping form above instead.shdctl release generate
Bringing your own object storage
The deployment needs S3-compatible object storage for three things: the logs of
each transformation step, the nightly MongoDB backups, and the publications Unity
Studio writes. By default it deploys an in-cluster store for all three, which
needs no configuration and no outbound access — this is the only option on a
cluster that cannot reach anything outside itself. PostgreSQL's backups are a
fourth, optional consumer: they go to a PersistentVolume unless you name a bucket
for them.
configuration.objectStore.provider: s3configuration: objectStore: provider: s3 endpoint: https://s3.us-east-2.amazonaws.com region: us-east-2 forcePathStyle: false buckets: transformationLogs: my-transformation-logs mongodbBackups: my-mongodb-backups studioAssets: my-studio-assets postgresBackups: my-postgres-backups
Field | Required on | What it is |
|---|---|---|
| — | |
| yes | |
| yes | The region string the store expects — for AWS, the buckets' region. Stores that ignore it still need a non-empty value. |
| no (default | |
| yes | Bucket for the per-step logs a transformation writes. Not application logs. |
| yes | Bucket for the nightly MongoDB dumps. |
| yes | Bucket for the publications Unity Studio writes. The only one a browser reaches directly, so the only one needing a CORS rule, and it needs an |
| no | Bucket for the pgBackRest repository — PostgreSQL full backups and archived WAL. Naming it moves that repository off its PersistentVolume, which is then no longer provisioned. Requires an |
Each key names one dataset with one producer, rather than a shared pool — so a
bucket you point at holds nothing else, and a later dataset
would arrive as a key of its own rather than moving into these.
transformationLogspostgresBackupshttphttpCreate the buckets before you deploy, together with one access key pair that
can read, write and delete objects in every one you name — backup retention
deletes the dumps it ages out, and pgBackRest deletes the backups and WAL it
expires. prompts for that access key ID and secret access
key on this provider rather than generating them: a generated key could never
authenticate against your store. A key scoped to only some of the buckets passes
cleanly and then fails much later, the first time a backup or a
transformation runs. A run with no file leaves the
pair at the placeholder and only warns, so that snapshot must not be
deployed as-is.
shdctl secret generatesecret generate--use-defaults--importTBDbuckets.studioAssetshttps://<your appDomain>configuration.networking.appDomainGETPUTHEAD*ETagContent-Typebuckets.studioAssetshttpshttp://https://<your appDomain>httpbuckets.postgresBackupshttpEndpoint constraints. The endpoint is and nothing else:
no path, no query string, no fragment, and no . All of those, and a
missing scheme, fail — the earliest check — and are
refused again by , which is the gate that still applies if
you skip validation. They are refused rather than accepted-and-dropped: the
component that writes step logs takes a host and port with no scheme and no
path, so an endpoint with a path prefix would configure backups correctly and
silently break step-log uploads, and an endpoint with credentials in it would put
them in the values files you commit to Git. (The component that reads step logs
back does take a full URL — the two differ, which is why one endpoint has to
satisfy the stricter of them.) An IPv6 literal is supported, written
bracketed: . means the connection is not encrypted;
use it only on a network you trust.
scheme://host[:port]user:pass@shdctl manifest validateshdctl release generatehttp://[fd00::1]:9000httpLimitations to check before you commit to this.
- The endpoint must present a publicly trusted TLS certificate, or be reached
over . An endpoint whose certificate comes from a private CA — an on-premises appliance with an internal PKI, typically — is not supported in this release.
httpcovers your ingress, not this endpoint. Thatconfiguration.networking.trustedCaSecretNamefallback does not extend tohttporbuckets.postgresBackups.buckets.studioAssetsrejectspostgresBackupsoutright;httpis not rejected but does not work over it, because the browser refuses the mixed content (above). So a store fronted by a privately-issued certificate has no supported path to PostgreSQL backups or to Studio publishing in this release. LeavestudioAssetsout and PostgreSQL keeps backing up to its volume; Studio publishing has no equivalent fallback.postgresBackups - A store that requires path-style bucket addressing is only partly supported.
reaches the MongoDB backup, pgBackRest and Unity Studio clients; the transformation step-log clients have no path-style setting yet. So if you are adopting this on MinIO, Ceph RGW or StorageGRID, verify that a transformation's step logs are readable after switching — MongoDB backups succeeding is not evidence that they are — and tell Unity if they are not. On AWS S3, or any store serving virtual-hosted-style requests, leave it
forcePathStyle: trueand this does not apply.false - One provider and one credential pair covers every bucket; per-bucket endpoints or a per-purpose provider choice are not supported.
Leaving out entirely, or setting , is the
in-cluster store and needs nothing else in the block. If you fill in ,
, or but leave at , shdctl
warns that they are being ignored rather than quietly deploying the in-cluster
store; setting under
warns the same way, since there is no in-cluster store to
size.
objectStoreprovider: garageendpointregionforcePathStylebucketsprovidergarageconfiguration.infrastructure.components.garageprovider: s3buckets.postgresBackupsprovidergarageshdctl release generatepostgresBackupsprovider: garageSwitching an existing deployment to your own buckets
Transformation step logs need nothing: they are transient and continuously
garbage-collected. The MongoDB backup history and the Unity Studio publications do
not move on their own. Copy both first, or the old dumps and every publication your
users made go away with the in-cluster store: Studio publications are user data, and
nothing else holds a copy. Replace with your deployment's namespace,
and run every step from one shell on a workstation that can reach the cluster.
<namespace>Naming doesn't move PostgreSQL's backup history either:
PostgreSQL starts a fresh backup chain in the bucket. Take a manual backup first if
you need the old chain, and keep the backup volume until you're satisfied with the
new one.
buckets.postgresBackups-
Copy the backup history and the Studio publications with the MinIO client,. The deployment keeps running meanwhile:
mc# the in-cluster store's own keys, read out of the clusterexport INCLUSTER_KEY=$(kubectl -n <namespace> get secret s3-api-storage-credentials -o jsonpath='{.data.GARAGE_ACCESS_KEY}' | base64 -d)export INCLUSTER_SECRET=$(kubectl -n <namespace> get secret s3-api-storage-credentials -o jsonpath='{.data.GARAGE_SECRET_KEY}' | base64 -d)# the key pair you created for your own bucketsexport MANAGED_KEY=<your-access-key-id>export MANAGED_SECRET=<your-secret-access-key>kubectl -n <namespace> port-forward svc/garage 3900:3900 &mc alias set incluster http://localhost:3900 "$INCLUSTER_KEY" "$INCLUSTER_SECRET"# your manifest's configuration.objectStore.endpoint; add --path on if it sets forcePathStyle: truemc alias set managed <your-object-store-endpoint> "$MANAGED_KEY" "$MANAGED_SECRET"mc mirror incluster/psmdb-backups managed/<your-backups-bucket>mc mirror incluster/studio-assets managed/<your-studio-assets-bucket> -
Right before you switch, copy again in a quiet window: with no MongoDB backup due, and nobody publishing from Studio. Anything written to the in-cluster store after this copy is not carried over.copies only what changed since the first run, so this one is short. Then check that the counts match:
mc mirrormc mirror incluster/psmdb-backups managed/<your-backups-bucket>mc mirror incluster/studio-assets managed/<your-studio-assets-bucket>mc ls --recursive incluster/psmdb-backups | wc -lmc ls --recursive managed/<your-backups-bucket> | wc -lmc ls --recursive incluster/studio-assets | wc -lmc ls --recursive managed/<your-studio-assets-bucket> | wc -l -
Setin your manifest, then run
provider: s3again, supplying your access key, andshdctl secret generate, followed byshdctl secret deployandshdctl release generate. Between the secret deployment and the release deployment, step-log uploads and MongoDB backups fail: the running services hold your new key but still point at the in-cluster store. That is expected; keep the gap short, and both recover once the charts are deployed.shdctl release deploy -
With ArgoCD, the in-cluster store's application disappears from the release and is pruned on the next sync. With Helm, nothing removes it, andskips it too: it keeps running, but nothing writes to it anymore. Run the two
shdctl release uninstallcommands once more, to pick up anything written during the switch, then remove it yourself withmc mirror. Either way, stop the port-forward withhelm -n <namespace> uninstall garage.kill %1 -
The in-cluster store's PersistentVolumeClaims survive the switch, and they are the only remaining copy of anything you did not mirror. Keep them until you have seen a fresh MongoDB backup complete, a transformation run end to end, and a Studio publication appear in your own bucket. Then delete them:kubectl -n <namespace> get pvc | grep garagekubectl -n <namespace> get pvc -o name | grep garage | xargs -r kubectl -n <namespace> delete
Custom CA bundle for a privately-signed ingress
If your Asset Manager ingress uses a TLS certificate signed by a CA that is not
in the default container trust store (for example a government or internal corporate
PKI), the .NET StorageTool containers in the built-in Argo workflows will
fail to validate downloads from your own ingress with .
PartialChainTo fix this, create a Kubernetes Secret in the install namespace holding your
CA chain, then reference it from your manifest:
kubectl -n <install-namespace> create secret generic my-ca-bundle --from-file=ca-bundle.crt=/path/to/your-ca-chain.pem
configuration: networking: trustedCaSecretName: my-ca-bundle
Requirements:
- Single key inside the secret: .
ca-bundle.crt - Value: PEM-encoded concatenation of the root and intermediate CAs needed to validate your ingress.
- Plain blocks only —
BEGIN CERTIFICATE(OpenSSL trust-extended) is filtered out by .NET and won't be loaded.BEGIN TRUSTED CERTIFICATE - Kubernetes Secret size limit is 1 MiB.
You may use any mechanism to populate the secret: , External Secrets
Operator, sealed-secrets, etc. shdctl does not create or read it.
kubectlThe same bundle is mounted into the Unity Studio publisher, which also calls
your ingress, so a privately-issued ingress certificate needs nothing extra for
Studio. It does not cover ,
which is a separate setting: that one turns on mutual TLS (mTLS), requiring
every client reaching your deployment to present a certificate of its own, and
Studio's publisher has none — so Studio publishing does not work while x509
client authentication is enabled in this release.
configuration.authentication.x509.enabled: true