KubernetesControlPlane fields
Reference for kind: KubernetesControlPlane. How potctl deploys the file is on Kubernetes. The cluster object is the ControlPlane CRD.
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
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)
| Property | Value |
|---|---|
| Type | string (file path) |
| Required | Yes |
| Description | Path to the kubeconfig used to install the operator and apply the ControlPlane custom resource. ~ is expanded. The path is stored as absolute. |
spec.ca
| Property | Value |
|---|---|
| Type | string, base64-encoded PEM |
| Required | No |
| Description | CA 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.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Display name |
surname | string | No | Display name |
email | string | Yes | Used when creating the Controller user after deploy |
password | string | No | If 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)
| Field | Type | Required | Notes |
|---|---|---|---|
mode | embedded or external | Yes | |
insecureAllowHttp | bool | No | Local trials |
insecureAllowBootstrapLog | bool | No | Local trials |
bootstrap.username | string | Yes if embedded | |
bootstrap.password | string | Yes if embedded | Required in YAML at deploy. At least 12 characters, one uppercase letter, one special character. |
issuerUrl | string | Yes if external | |
client.id | string | Yes if external | |
client.secret | string | Yes if external | |
rateLimit.* | object | No | |
sessionStore.* | object | No | |
tokenTtl.* | object | No | |
oidcTtl.* | object | No |
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:
| Block | Fields |
|---|---|
rateLimit | enabled, maxRequestsPerWindow, windowMs |
sessionStore | type, ttlMs, secret |
tokenTtl | accessTokenTtlSeconds, refreshTokenTtlSeconds |
oidcTtl | interactionTtlSeconds, grantTtlSeconds, sessionTtlSeconds, idTokenTtlSeconds |
spec.database
| Field | Type | Required | Notes |
|---|---|---|---|
provider | string | If external DB | For example postgres |
host | string | If external DB | Empty host: the operator uses embedded SQLite for a single controller |
port | int | If external DB | |
user | string | If external DB | |
password | string | If external DB | |
databaseName | string | If external DB | |
ssl | bool | No | Database TLS |
ca | string | No | Base64 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
| Field | Type | Default | Notes |
|---|---|---|---|
controller | int32 | 1 if 0 or omitted | Controller Deployment replicas on the custom resource |
nats | int32 | operator default | If NATS is enabled and this is set, the value is at least 2 |
More than one Controller replica requires an external database.
spec.controller
| Field | Type | Default | Notes |
|---|---|---|---|
publicUrl | string | Derived by the operator if empty | CONTROLLER_PUBLIC_URL. See URL resolution below. |
trustProxy | bool | Auto true in Ingress mode | TRUST_PROXY |
consoleUrl | string | publicUrl if empty | EdgeOps Console URL |
consolePort | int | 8008 | Service port 80 forwards to this port on the pod |
pidBaseDir | string | /home/runner | |
logLevel | string | info | |
https | bool | false | Pod TLS. The readiness probe uses HTTPS when true. |
secretName | string | Required 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.
| Situation | Operator behavior |
|---|---|
publicUrl set, consoleUrl empty | consoleUrl becomes publicUrl |
Ingress mode (ClusterIP and ingresses.controller.host), empty publicUrl | https://<host> if the ingress secretName is set, otherwise http://<host>. Default trustProxy: true when unset. |
LoadBalancer mode, empty publicUrl | Wait for the load balancer. publicUrl is {scheme}://<lb>:51121. consoleUrl is {scheme}://<lb>. |
| Derived scheme | https 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
| Field | Type | Notes |
|---|---|---|
auditEnabled | bool | |
retentionDays | int | |
cleanupInterval | int | Seconds |
captureIpAddress | bool |
spec.images
Operator Deployment image (CLI install) and workload images on the ControlPlane custom resource. Release pins are on Control plane.
| Field | Scope | Required | Notes |
|---|---|---|---|
operator | CLI only | No | Operator Deployment image. Not on the ControlPlane custom resource. Default for this train: operator:3.9.0 on the flavor registry. |
pullSecret | Custom resource | No | imagePullSecrets on operator-managed pods |
controller | Custom resource | No | Default from the CLI build if empty |
router | Custom resource | No | |
nats | Custom resource | No | Default 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:
| Field | Type | Notes |
|---|---|---|
type | string | LoadBalancer, ClusterIP, NodePort |
address | string | Optional loadBalancerIP |
annotations | map | Service annotations |
externalTrafficPolicy | string | LoadBalancer 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: ClusterIPrequiresingresses.controller.hostandingresses.controller.secretName.services.router.type: ClusterIPrequiresingresses.router.addressand the message, interior, and edge ports.
spec.ingresses
ingresses.controller
| Field | Notes |
|---|---|
host | Ingress rule host |
secretName | Ingress TLS Secret |
ingressClassName | |
annotations | For example cert-manager |
ingresses.router
| Field | Default (registration) |
|---|---|
address | Required when the Router Service is not a LoadBalancer |
messagePort | 5671 |
interiorPort | 55671 |
edgePort | 45671 |
ingresses.nats
| Field | Typical default |
|---|---|
address | Required when the client Service is not a LoadBalancer |
serverPort | 4222 |
clusterPort | 6222 |
leafPort | 7422 |
mqttPort | 8883 |
httpPort | 8222 |
Hub registration on the cluster object uses the same ports. See ControlPlane CRD.
spec.nats
| Field | Type | Default |
|---|---|---|
enabled | bool | true if the block is omitted |
jetStream.storageSize | string | for example 10Gi |
jetStream.memoryStoreSize | string | for example 1Gi |
jetStream.storageClassName | string | optional |
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.
| Field | Description |
|---|---|
enabled | Enable vault integration. On the cluster object, default true when the vault block is active and this pointer is empty. |
provider | hashicorp, openbao, vault, aws, aws-secrets-manager, azure, azure-key-vault, google, or google-secret-manager |
basePath | Base path inside the vault. The operator replaces $namespace with the ControlPlane namespace. |
hashicorp.address | Vault server address (HashiCorp, OpenBao, or Vault providers) |
hashicorp.token | Vault token |
hashicorp.mount | Secret engine mount path |
aws.region | AWS region |
aws.accessKeyId | AWS access key ID |
aws.accessKey | AWS secret access key |
azure.url | Azure Key Vault URL |
azure.tenantId | Azure tenant ID |
azure.clientId | Azure client ID |
azure.clientSecret | Azure client secret |
google.projectId | Google Cloud project ID |
google.credentials | Google 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
| Rule | When it fails |
|---|---|
iofogUser.email | Empty |
auth.mode | Missing or invalid |
| Embedded bootstrap | Missing username or password in YAML |
| External auth | Missing issuer or client credentials |
replicas.controller greater than 1 | Incomplete external database |
| ClusterIP controller | Missing ingress host or secretName |
| ClusterIP router | Missing router ingress address or ports |
NATS enabled and replicas.nats set | Value 1 is rejected. Use at least 2, or leave it unset. |
| Vault provider | Missing provider-specific block |
Minimal skeleton
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.