Skip to main content
Version: v3.9.0

KubernetesControlPlane fields

Reference for kind: KubernetesControlPlane. How potctl deploys the file is on Kubernetes. The cluster object is the ControlPlane CRD.

Deploy
apiVersion: datasance.com/v3 # required, string
kind: KubernetesControlPlane # required, string
metadata:
name: prod-cp # required, string
namespace: my-ecn # no, string. Must match -n when both are set
spec:
config: ~/.kube/config # required, string. Kubeconfig path
ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t # no, string. Base64 PEM for CLI trust
iofogUser: # required for embedded auth, object. Not sent to the operator
name: Admin # no, string
surname: User # no, string
email: [email protected] # required, string
password: "ChangeMe!12345" # no, string
auth: # required, object. Keep the fields for one mode
mode: embedded # required, embedded or external
insecureAllowHttp: false # no, bool
insecureAllowBootstrapLog: false # no, bool
bootstrap: # required when mode is embedded
username: bootstrap-user # required if embedded, string
password: "ChangeMe!12345" # required if embedded, string
issuerUrl: https://idp.example.com # required if external, string. Remove when mode is embedded
client: # required if external
id: cli # required if external, string
secret: client-secret # required if external, string
rateLimit: # no, object
enabled: false # no, bool
maxRequestsPerWindow: 100 # no, int
windowMs: 60000 # no, int
sessionStore: # no, object
type: memory # no, string
ttlMs: 3600000 # no, int
secret: session-secret # no, string
tokenTtl: # no, object
accessTokenTtlSeconds: 3600 # no, int
refreshTokenTtlSeconds: 86400 # no, int
oidcTtl: # no, object
interactionTtlSeconds: 600 # no, int
grantTtlSeconds: 600 # no, int
sessionTtlSeconds: 3600 # no, int
idTokenTtlSeconds: 3600 # no, int
database: # no, object. Required when replicas.controller is greater than 1
provider: postgres # if external database, string
host: db.example.com # if external database, string. Empty uses embedded SQLite for one controller
port: 5432 # if external database, int
user: pot # if external database, string
password: db-password # if external database, string
databaseName: pot # if external database, string
ssl: false # no, bool
ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t # no, string. Base64 PEM
replicas:
controller: 1 # no, int32, default 1 if 0 or omitted
nats: 2 # no, int32. If NATS is enabled and this is set, at least 2
controller:
publicUrl: https://controller.example.com # no, string. Derived by the operator if empty
trustProxy: true # no, bool. Auto true in Ingress mode
consoleUrl: https://controller.example.com # no, string. publicUrl if empty
consolePort: 8008 # no, int, default 8008
pidBaseDir: /home/runner # no, string, default /home/runner
logLevel: info # no, string, default info
https: false # no, bool, default false
secretName: controller-tls # required when https is true, string
events: # no, object
auditEnabled: false # no, bool
retentionDays: 30 # no, int
cleanupInterval: 3600 # no, int. Seconds
captureIpAddress: false # no, bool
images:
operator: ghcr.io/datasance/operator:3.9.0 # no, string. CLI only. Default operator:3.9.0 on the flavor registry
pullSecret: pull-secret # no, string
controller: ghcr.io/datasance/controller:3.9.0 # no, string
router: ghcr.io/datasance/router:3.9.0 # no, string
nats: ghcr.io/datasance/nats:2.15.0 # no, string. Default image tag 2.15.0 on this train
services:
controller:
type: LoadBalancer # no, string. LoadBalancer, ClusterIP, or NodePort
address: 192.0.2.10 # no, string
annotations: {} # no, map
externalTrafficPolicy: Local # no, string. Applied for LoadBalancer and NodePort
router:
type: LoadBalancer # no, string
address: 192.0.2.11 # no, string
annotations: {} # no, map
externalTrafficPolicy: Local # no, string
nats:
type: LoadBalancer # no, string
address: 192.0.2.12 # no, string
annotations: {} # no, map
externalTrafficPolicy: Local # no, string
natsServer:
type: ClusterIP # no, string
address: "" # no, string
annotations: {} # no, map
externalTrafficPolicy: Cluster # no, string
ingresses:
controller:
host: controller.example.com # string. Required when the controller Service is ClusterIP
secretName: controller-tls # string. Required when the controller Service is ClusterIP
ingressClassName: nginx # no, string
annotations: {} # no, map
router:
address: router.example.com # required when the Router Service is not a LoadBalancer, string
messagePort: 5671 # no, int, default 5671
interiorPort: 55671 # no, int, default 55671
edgePort: 45671 # no, int, default 45671
nats:
address: nats.example.com # required when the client Service is not a LoadBalancer, string
serverPort: 4222 # no, int, default 4222
clusterPort: 6222 # no, int, default 6222
leafPort: 7422 # no, int, default 7422
mqttPort: 8883 # no, int, default 8883
httpPort: 8222 # no, int, default 8222
nats:
enabled: true # no, bool, default true if the block is omitted
jetStream:
storageSize: 10Gi # no, string
memoryStoreSize: 1Gi # no, string
storageClassName: standard # no, string
vault: # no, object. Keep the provider block that matches provider
enabled: false # no, bool. Default true on the cluster object when the vault block is active and this is empty
provider: hashicorp # no, string
basePath: secret/data/$namespace # no, string. The operator replaces $namespace
hashicorp: # required for hashicorp, openbao, or vault
address: https://vault.example.com # string
token: vault-token # string
mount: secret # string
aws: # required for aws or aws-secrets-manager
region: us-east-1 # string
accessKeyId: <access-key-id> # string
accessKey: <secret-access-key> # string
azure: # required for azure or azure-key-vault
url: https://example.vault.azure.net # string
tenantId: <tenant-id> # string
clientId: <client-id> # string
clientSecret: <client-secret> # string
google: # required for google or google-secret-manager
projectId: <project-id> # string
credentials: "<service-account-json>" # string

Not deployed​

These fields come back from describe. Leave them out of the file you pass to deploy.

controllerPods: [] # written into stored config after deploy. Leave it out of the first deploy file

metadata.name is the name stored in potctl namespace config. metadata.namespace, when set, must match -n.

The translated custom resource name is pot.

CLI-only fields​

These fields stay on the workstation. They are not written to the operator custom resource.

spec.config (required)​

PropertyValue
Typestring (file path)
RequiredYes
DescriptionPath to the kubeconfig used to install the operator and apply the ControlPlane custom resource. ~ is expanded. The path is stored as absolute.

spec.ca​

PropertyValue
Typestring, base64-encoded PEM
RequiredNo
DescriptionCA for verifying the Controller API when the CLI connects over HTTPS. Stored in ~/.iofog/v3/trust/<namespace>/. Not sent to the operator.

See Securing Kubernetes control plane (CLI YAML).

spec.iofogUser​

Required for the embedded auth workflow.

FieldTypeRequiredDescription
namestringNoDisplay name
surnamestringNoDisplay name
emailstringYesUsed when creating the Controller user after deploy
passwordstringNoIf set: at least 12 characters, one uppercase letter, one special character. If omitted, the CLI may prompt at deploy.

Not sent to the operator custom resource.

spec.controllerPods​

Output written into stored config after deploy (pod names). Leave it out of the file you deploy the first time.

Fields translated to the operator ControlPlane​

Deep operator behavior is on the ControlPlane CRD.

spec.auth (required)​

FieldTypeRequiredNotes
modeembedded or externalYes
insecureAllowHttpboolNoLocal trials
insecureAllowBootstrapLogboolNoLocal trials
bootstrap.usernamestringYes if embedded
bootstrap.passwordstringYes if embeddedRequired in YAML at deploy. At least 12 characters, one uppercase letter, one special character.
issuerUrlstringYes if external
client.idstringYes if external
client.secretstringYes if external
rateLimit.*objectNo
sessionStore.*objectNo
tokenTtl.*objectNo
oidcTtl.*objectNo

Embedded: the CLI runs bootstrap login and makes sure iofogUser exists. External: the CLI skips embedded user bootstrap.

Keycloak-shaped auth.url and realm fields are retired. See Embedded OIDC and External OIDC.

Optional nested fields, translated to operator and Controller auth environment when set:

BlockFields
rateLimitenabled, maxRequestsPerWindow, windowMs
sessionStoretype, ttlMs, secret
tokenTtlaccessTokenTtlSeconds, refreshTokenTtlSeconds
oidcTtlinteractionTtlSeconds, grantTtlSeconds, sessionTtlSeconds, idTokenTtlSeconds

spec.database​

FieldTypeRequiredNotes
providerstringIf external DBFor example postgres
hoststringIf external DBEmpty host: the operator uses embedded SQLite for a single controller
portintIf external DB
userstringIf external DB
passwordstringIf external DB
databaseNamestringIf external DB
sslboolNoDatabase TLS
castringNoBase64 PEM for the database CA. Becomes DB_SSL_CA.

If spec.replicas.controller is greater than 1, the external database must include host, port, user, password, databaseName, and provider. Encoding for ca is on the ControlPlane CRD.

spec.replicas​

FieldTypeDefaultNotes
controllerint321 if 0 or omittedController Deployment replicas on the custom resource
natsint32operator defaultIf NATS is enabled and this is set, the value is at least 2

More than one Controller replica requires an external database.

spec.controller​

FieldTypeDefaultNotes
publicUrlstringDerived by the operator if emptyCONTROLLER_PUBLIC_URL. See URL resolution below.
trustProxyboolAuto true in Ingress modeTRUST_PROXY
consoleUrlstringpublicUrl if emptyEdgeOps Console URL
consolePortint8008Service port 80 forwards to this port on the pod
pidBaseDirstring/home/runner
logLevelstringinfo
httpsboolfalsePod TLS. The readiness probe uses HTTPS when true.
secretNamestringRequired when https: true. TLS Secret mounted on the pod. For Ingress pattern B2, often the same value as ingresses.controller.secretName.

Controller HTTPS patterns: LoadBalancer plus pod TLS (A), ClusterIP plus Ingress with https: false (B1), ClusterIP plus Ingress plus https: true and a shared Secret (B2). See Securing Kubernetes control plane (CLI YAML) and Securing Kubernetes cluster (operator).

URL resolution (publicUrl / consoleUrl)​

The operator resolves this on the translated ControlPlane, not the CLI.

SituationOperator behavior
publicUrl set, consoleUrl emptyconsoleUrl becomes publicUrl
Ingress mode (ClusterIP and ingresses.controller.host), empty publicUrlhttps://<host> if the ingress secretName is set, otherwise http://<host>. Default trustProxy: true when unset.
LoadBalancer mode, empty publicUrlWait for the load balancer. publicUrl is {scheme}://<lb>:51121. consoleUrl is {scheme}://<lb>.
Derived schemehttps if controller.https is true, or Ingress mode with an ingress secretName. Otherwise http.

Full rules: URL resolution. After deploy, the CLI takes the API endpoint from the load balancer, from the Ingress named controller, or from the URL you set.

spec.events​

FieldTypeNotes
auditEnabledbool
retentionDaysint
cleanupIntervalintSeconds
captureIpAddressbool

spec.images​

Operator Deployment image (CLI install) and workload images on the ControlPlane custom resource. Release pins are on Control plane.

FieldScopeRequiredNotes
operatorCLI onlyNoOperator Deployment image. Not on the ControlPlane custom resource. Default for this train: operator:3.9.0 on the flavor registry.
pullSecretCustom resourceNoimagePullSecrets on operator-managed pods
controllerCustom resourceNoDefault from the CLI build if empty
routerCustom resourceNo
natsCustom resourceNoDefault image tag 2.15.0 on this train

The CLI installs the operator using operator, then applies the custom resource with pullSecret, controller, router, and nats. Empty strings are filled from build-time defaults.

spec.services​

Each of controller, router, nats, and natsServer:

FieldTypeNotes
typestringLoadBalancer, ClusterIP, NodePort
addressstringOptional loadBalancerIP
annotationsmapService annotations
externalTrafficPolicystringLoadBalancer or NodePort

externalTrafficPolicy is applied only for LoadBalancer and NodePort. If you omit it: LoadBalancer becomes Local, NodePort becomes Cluster, ClusterIP stays unset.

CLI checks:

  • services.controller.type: ClusterIP requires ingresses.controller.host and ingresses.controller.secretName.
  • services.router.type: ClusterIP requires ingresses.router.address and the message, interior, and edge ports.

spec.ingresses​

ingresses.controller

FieldNotes
hostIngress rule host
secretNameIngress TLS Secret
ingressClassName
annotationsFor example cert-manager

ingresses.router

FieldDefault (registration)
addressRequired when the Router Service is not a LoadBalancer
messagePort5671
interiorPort55671
edgePort45671

ingresses.nats​

FieldTypical default
addressRequired when the client Service is not a LoadBalancer
serverPort4222
clusterPort6222
leafPort7422
mqttPort8883
httpPort8222

Hub registration on the cluster object uses the same ports. See ControlPlane CRD.

spec.nats​

FieldTypeDefault
enabledbooltrue if the block is omitted
jetStream.storageSizestringfor example 10Gi
jetStream.memoryStoreSizestringfor example 1Gi
jetStream.storageClassNamestringoptional

If enabled is false, the operator skips NATS resources.

spec.vault​

Optional Controller secret store. If provider is set, the matching provider block is required.

FieldDescription
enabledEnable vault integration. On the cluster object, default true when the vault block is active and this pointer is empty.
providerhashicorp, openbao, vault, aws, aws-secrets-manager, azure, azure-key-vault, google, or google-secret-manager
basePathBase path inside the vault. The operator replaces $namespace with the ControlPlane namespace.
hashicorp.addressVault server address (HashiCorp, OpenBao, or Vault providers)
hashicorp.tokenVault token
hashicorp.mountSecret engine mount path
aws.regionAWS region
aws.accessKeyIdAWS access key ID
aws.accessKeyAWS secret access key
azure.urlAzure Key Vault URL
azure.tenantIdAzure tenant ID
azure.clientIdAzure client ID
azure.clientSecretAzure client secret
google.projectIdGoogle Cloud project ID
google.credentialsGoogle service account credentials JSON

Environment mapping is on the ControlPlane CRD. Config maps that set spec.useVault read secrets through this connection. See Config maps.

Validation summary​

RuleWhen it fails
iofogUser.emailEmpty
auth.modeMissing or invalid
Embedded bootstrapMissing username or password in YAML
External authMissing issuer or client credentials
replicas.controller greater than 1Incomplete external database
ClusterIP controllerMissing ingress host or secretName
ClusterIP routerMissing router ingress address or ports
NATS enabled and replicas.nats setValue 1 is rejected. Use at least 2, or leave it unset.
Vault providerMissing provider-specific block

Minimal skeleton​

prod-cp
apiVersion: datasance.com/v3
kind: KubernetesControlPlane
metadata:
name: prod-cp
spec:
config: ~/.kube/config
iofogUser:
auth:
mode: embedded
bootstrap:
username: admin
password: "ChangeMe12!"
database:
provider: postgres
host: postgres.example.svc
port: 5432
user: controller
password: secret
databaseName: controller
replicas:
controller: 1
nats: 2
images:
operator: ghcr.io/datasance/operator:3.9.0
controller: ghcr.io/datasance/controller:3.9.0
router: ghcr.io/datasance/router:3.9.0
nats: ghcr.io/datasance/nats:2.15.0

Worked examples are on Kubernetes.

Group 3See anything wrong with the document? Help us improve it!