Appearance
Helm charts
Three charts under deploy/charts/ install plexsphere. The procedure is in Install plexsphere with Helm; this page is the contract. In the tree every chart carries the version 0.0.0-dev. make release stamps a release into the packaged charts: the chart version is the release version without its leading v (X.Y.Z), and the appVersion of plexsphere and plexsphere-signer is the release tag (vX.Y.Z). The plexsphere-built images default their tag to appVersion; the spicedb chart keeps the SpiceDB release as its appVersion. An edge version, v0.0.0-edge.<stamp>.g<sha>, which the edge lane publishes from every push to main, is stamped the same way: the chart version is 0.0.0-edge.<stamp>.g<sha> and appVersion is the edge tag, so an edge chart defaults its images to the edge images of the same commit. The edge channel describes the version and how to select it.
Two rules hold for every chart:
- No chart renders a Secret. Every credential comes from a Secret the operator creates and the chart reads by name.
- An empty value leaves its variable unset. The chart never renders an environment variable with an empty value, so the binary's own boot error names what is missing.
Resource names
Names are not prefixed with the release name, because the charts reach each other by fixed hosts. fullnameOverride replaces a chart's base name. Install one release of each chart per namespace.
| Chart | Kind | Name |
|---|---|---|
plexsphere | Deployment, Service | plexsphere (the API, port 8080) |
plexsphere | PodDisruptionBudget, when api.replicas is above 1 | plexsphere |
plexsphere | Deployment, Service | plexsphere-console (Service port 80) |
plexsphere | PodDisruptionBudget, when console.replicas is above 1 | plexsphere-console |
plexsphere | Job, pre-install and pre-upgrade hook | plexsphere-migrate |
plexsphere | ServiceAccount | plexsphere |
plexsphere | ClusterRole, ClusterRoleBinding | plexsphere-api-<namespace> |
plexsphere | HTTPRoute, when gateway.enabled | plexsphere-api, plexsphere-console |
plexsphere | NetworkPolicy, when networkPolicy.enabled | plexsphere-default-deny, plexsphere, plexsphere-console (the last only with console.enabled) |
plexsphere-signer | Deployment, Service, ServiceAccount, NetworkPolicy | plexsphere-signer (gRPC 8443, HTTP 8081) |
spicedb | Deployment, Service | spicedb (gRPC 50051) |
spicedb | PodDisruptionBudget, when replicas is above 1 | spicedb |
spicedb | NetworkPolicy, when networkPolicy.enabled | spicedb |
spicedb | Issuer (cert-manager.io/v1), when tls.enabled, tls.existingSecret is empty and tls.certManager.issuerRef names no issuer | spicedb-selfsigned |
spicedb | Certificate (cert-manager.io/v1), under the same three conditions; the CA, valid ten years, written to the Secret spicedb-ca | spicedb-ca |
spicedb | Issuer (cert-manager.io/v1), under the same three conditions; signs from that Secret | spicedb-ca |
spicedb | Certificate (cert-manager.io/v1), when tls.enabled and tls.existingSecret is empty | spicedb-tls, written to the Secret spicedb-tls; its dnsNames are spicedb.<namespace> and spicedb.<namespace>.svc |
The charts render no Secret, these rows included: cert-manager writes spicedb-ca and spicedb-tls from the Certificates above. Naming a Secret in spicedb.tls.existingSecret renders none of the four resources, the path for a cluster without cert-manager.
The API runs api.replicas Pods, the console console.replicas, and the bundled SpiceDB spicedb.replicas. Above one replica, the chart also renders for that workload a PodDisruptionBudget, which keeps one of its Pods available (minAvailable: 1) and lets a drain evict a Pod that is not ready (unhealthyPodEvictionPolicy: AlwaysAllow), and a topology spread constraint over the node hostname (maxSkew: 1, whenUnsatisfiable: ScheduleAnyway), so a single-node cluster still schedules every replica. The signer runs one replica and has no budget, because its chart exposes no value to change the count. Availability states what an upgrade, a drain and a node failure cost each of them, and what the operator-supplied Postgres, NATS JetStream, OpenBao and object store must provide.
The API Pod has a 60-second termination grace period. It covers the HTTP drain (up to 25 s), the background-worker join (up to 10 s), and closing the shared connections.
Secrets the charts read
| Secret (default name) | Keys | Read by | Value that renames it |
|---|---|---|---|
plexsphere-database | DATABASE_URL | the migrate hook, the API (PLEXSPHERE_DSN), the signer (PLEXSPHERE_SIGNER_DB_DSN) | database.existingSecret and database.key in both the plexsphere and the plexsphere-signer chart |
plexsphere-cursor-keys | the thirteen cursor HMAC keys below, NSK_WRAP_KEY_B64, SIGNING_PUBLIC_KEY_B64 | the API | apiKeys.existingSecret |
spicedb-credentials | SPICEDB_GRPC_PRESHARED_KEY | the API, SpiceDB | authz.spicedb.presharedKeySecret (plexsphere), presharedKey.existingSecret and presharedKey.key (spicedb) |
spicedb-datastore | SPICEDB_DATASTORE_CONN_URI | SpiceDB | datastore.existingSecret and datastore.key (spicedb) |
spicedb-tls (type kubernetes.io/tls) | tls.crt, tls.key, ca.crt | SpiceDB, which serves the certificate and the key from /etc/spicedb/tls; the API, which mounts ca.crt alone at /etc/plexsphere/spicedb-ca | spicedb.tls.existingSecret (spicedb), authz.spicedb.caSecret and authz.spicedb.caKey (plexsphere) |
plexsphere-access-signer-client | client.crt, client.key, ca.crt | the API, mounted at /etc/plexsphere/access-signer | accessSigner.clientSecret |
plexsphere-signer-tls (type kubernetes.io/tls) | tls.crt, tls.key | the signer | tls.existingSecret |
plexsphere-signer-client-ca | client-ca.crt | the signer | clientCA.existingSecret |
plexsphere-signer-seed | seed (32 bytes) | the signer under keyProvider: software, as an optional volume | seed.existingSecret |
| none by default | pin | the signer under keyProvider: pkcs11, mounted read-only at /etc/plexsphere-signer/pkcs11 | pkcs11.pin.existingSecret and pkcs11.pin.key |
| none by default | S3_ACCESS_KEY, S3_SECRET_KEY | the API | objectStore.credentialsSecret |
| none by default | ROLE_ID, SECRET_ID | the API | cloudCredentials.appRoleSecret |
| none by default | ROLE_ID, SECRET_ID | the API | audit.pepper.appRoleSecret |
none by default (type kubernetes.io/dockerconfigjson) | .dockerconfigjson | the kubelet, pulling the image of every Pod that names the Secret | imagePullSecrets in each chart, spicedb.imagePullSecrets for the bundled SpiceDB |
cert-manager writes spicedb-tls while the spicedb chart renders its Certificate; behind spicedb.tls.existingSecret you write it. The API mounts one key of it through items, so SpiceDB's private key is never projected into the API container.
The thirteen cursor HMAC keys, each at least 32 bytes as hex: PROJECTS_CURSOR_HMAC_KEY, CLOUDS_CURSOR_HMAC_KEY, PROVIDER_BUNDLES_CURSOR_HMAC_KEY, CLOUD_CREDENTIALS_CURSOR_HMAC_KEY, DOMAINS_CURSOR_HMAC_KEY, IDENTITIES_CURSOR_HMAC_KEY, INVITATIONS_CURSOR_HMAC_KEY, AUDIT_CURSOR_HMAC_KEY, AUTHZ_CURSOR_HMAC_KEY, NODES_CURSOR_HMAC_KEY, CAPABILITIES_CURSOR_HMAC_KEY, HOOKS_CURSOR_HMAC_KEY, INTEGRITY_VIOLATIONS_CURSOR_HMAC_KEY. The API reads each as PLEXSPHERE_<key>. NSK_WRAP_KEY_B64 is 32 bytes in base64; SIGNING_PUBLIC_KEY_B64 is the Ed25519 public key of the signer seed in base64.
The plexsphere chart
| Key | Default | Drives |
|---|---|---|
fullnameOverride | "" | The base name of every resource. |
image.tag | "" (the chart's appVersion) | The tag of the API, migrate and console images. |
image.pullPolicy | IfNotPresent | The pull policy of the same three images. |
imagePullSecrets | [] | The imagePullSecrets of the API, console and migrate Pods, a list of {name} entries naming kubernetes.io/dockerconfigjson Secrets you create. Only name is rendered, and an empty list renders no key. Create the Secrets before helm install, because the migrate hook pulls first. The bundled SpiceDB reads spicedb.imagePullSecrets instead; this list does not reach it. |
api.image.repository | ghcr.io/plexsphere/plexsphere | The API image. |
api.resources | requests 50m / 128Mi, limits 500m / 512Mi | The API container. |
api.replicas | 1 | spec.replicas of the API Deployment. Above 1 the chart also renders the PodDisruptionBudget and the topology spread constraint. The per-IP auth rate limit and the per-Node edge-PSK fetch limit are kept in each API Pod, so N replicas admit up to N times the configured rate from one client. Each Pod holds up to 46 Postgres connections with the default pool sizes, and an upgrade runs up to twice api.replicas Pods while old ones shut down; see Run more than one API replica. |
api.extraEnv | [] | Appended to the API container's env: the optional knobs in the dev stack reference, PLEXSPHERE_SSE_NATS_URL, or SPICEDB_MTLS_* for a SpiceDB that authenticates its clients by certificate instead of the preshared key and the authz.spicedb.caSecret this chart wires. |
api.extraVolumes | [] | Appended verbatim to the API Pod's volumes; the migrate Job and the console take none of it. With api.extraVolumeMounts and SSL_CERT_DIR in api.extraEnv it carries the CA of an IdP behind a private CA; see Expose the API and the console. The chart validates the list in no way, and an empty list renders no entry. |
api.extraVolumeMounts | [] | Appended verbatim to the API container's volumeMounts; the migrate and console containers take none of it. The chart validates the list in no way: a mount whose volume api.extraVolumes does not declare renders, and the API server refuses the Pod. An empty list renders no entry. |
migrate.image.repository | ghcr.io/plexsphere/plexsphere-migrate | The migrate hook image. |
migrate.backoffLimit | 3 | Retries of the migrate hook, which covers a Postgres not accepting connections yet. |
migrate.resources | requests 50m / 64Mi, limits 500m / 256Mi | The migrate container. |
console.enabled | true | The console Deployment, Service and route. |
console.replicas | 1 | spec.replicas of the console Deployment. Above 1 the chart also renders the PodDisruptionBudget and the topology spread constraint. |
console.image.repository | ghcr.io/plexsphere/console | The console image. |
console.resources | requests 10m / 16Mi, limits 250m / 64Mi | The console container. |
externalUrl | "" | PLEXSPHERE_ACTIONS_CALLBACK_BASE_URL, PLEXSPHERE_ACCESS_CALLBACK_BASE_URL, PLEXSPHERE_PROVISIONING_BROKER_ENROL_BASE_URL; the fallback of auth.csrfAllowedOrigins, auth.callbackUrl and provisioning.plexdDownloadUrl. |
database.existingSecret | plexsphere-database | PLEXSPHERE_DSN for the API, DATABASE_URL for the migrate hook. |
database.key | DATABASE_URL | The key read from that Secret. |
apiKeys.existingSecret | plexsphere-cursor-keys | The thirteen PLEXSPHERE_*_CURSOR_HMAC_KEY variables, PLEXSPHERE_NSK_WRAP_KEY_B64, PLEXSPHERE_SIGNING_PUBLIC_KEY_B64. |
signing.keyId | "" | PLEXSPHERE_SIGNING_KEY_ID, the identifier plexd records with the signing public key. |
auth.sessionIdleTimeout | 30m | PLEXSPHERE_SESSION_IDLE_TIMEOUT |
auth.sessionMaxLifetime | 12h | PLEXSPHERE_SESSION_MAX_LIFETIME |
auth.stateTtl | 10m | PLEXSPHERE_AUTH_STATE_TTL |
auth.csrfAllowedOrigins | "" (externalUrl) | PLEXSPHERE_CSRF_ALLOWED_ORIGINS |
auth.callbackUrl | "" (<externalUrl>/v1/auth/callback) | PLEXSPHERE_AUTH_CALLBACK_URL, the OIDC redirect_uri the API sends to the IdP on sign-in and on the callback, and the redirect URI to register there. One trailing slash of externalUrl is trimmed before the path is appended. With both empty the variable stays unset and the API derives the URL from each request's Host, with https only when the request reached it over TLS, so behind a Gateway that terminates TLS it sends http://. The chart validates neither value; the API refuses at boot a URL that does not start with http:// or https://. Upgrading from a chart version without this key: a release with externalUrl set sends that origin's callback URL on every sign-in, where it sent one derived from the request's Host before, so the IdP must have <externalUrl>/v1/auth/callback registered. A release that set PLEXSPHERE_AUTH_CALLBACK_URL through api.extraEnv moves it to this key, or the API container carries the variable twice. |
rateLimit.clientIpSource | remote-addr | remote-addr sets PLEXSPHERE_RATELIMIT_ALLOW_REMOTEADDR=true; proxy-headers sets PLEXSPHERE_AUTH_TRUST_PROXY_HEADERS=true. Any other value fails the render. |
nats.url | "" | PLEXSPHERE_OBS_NATS_URL |
authz.spicedb.endpoint | "" (spicedb.<namespace>.svc:50051 while spicedb.enabled) | SPICEDB_ENDPOINT. Required when spicedb.enabled is false. The bundled default is namespace-qualified because gRPC verifies the served certificate against this authority, and the dependency's leaf names no unqualified host: a bare Service name is scoped to no namespace, so a shared issuer signs it for anyone. |
authz.spicedb.insecure | false | SPICEDB_PRESHARED_KEY_INSECURE: the preshared key over plaintext gRPC, which puts a bearer credential and every tuple on the Pod network in the clear. With the bundled dependency it is the negation of spicedb.tls.enabled, and either half alone fails the render. |
authz.spicedb.caSecret | "" | The Secret whose authz.spicedb.caKey the API mounts at /etc/plexsphere/spicedb-ca and reads through SPICEDB_TLS_CA_PATH=/etc/plexsphere/spicedb-ca/ca.crt. Empty names the bundled dependency's TLS Secret while that dependency serves TLS, and leaves an endpoint of your own on the system roots. Must be empty while authz.spicedb.insecure is true. |
authz.spicedb.caKey | ca.crt | The one key read from that Secret. Only this key is projected into the API container, never tls.key. |
authz.spicedb.presharedKeySecret | spicedb-credentials | SPICEDB_PRESHARED_KEY, from the key SPICEDB_GRPC_PRESHARED_KEY. |
accessSigner.endpoint | plexsphere-signer:8443 | PLEXSPHERE_ACCESS_SIGNER_ENDPOINT |
accessSigner.sessionSigningScope | platform | PLEXSPHERE_ACCESS_SESSION_SIGNING_SCOPE |
accessSigner.clientSecret | plexsphere-access-signer-client | The mTLS client material; PLEXSPHERE_ACCESS_SIGNER_CLIENT_CERT, PLEXSPHERE_ACCESS_SIGNER_CLIENT_KEY and PLEXSPHERE_ACCESS_SIGNER_SERVER_CA point into its mount. |
actions.objectStoreBucket | "" | PLEXSPHERE_ACTIONS_OBJECT_STORE_BUCKET |
objectStore.endpoint | "" | PLEXSPHERE_S3_ENDPOINT |
objectStore.region | "" | PLEXSPHERE_S3_REGION |
objectStore.usePathStyle | false | PLEXSPHERE_S3_USE_PATH_STYLE |
objectStore.allowInsecureEndpoint | false | PLEXSPHERE_S3_ALLOW_INSECURE_ENDPOINT |
objectStore.credentialsSecret | "" | PLEXSPHERE_S3_ACCESS_KEY and PLEXSPHERE_S3_SECRET_KEY; unset leaves the client on its ambient credentials. |
bridge.acmeDirectoryUrl | https://acme-v02.api.letsencrypt.org/directory | PLEXSPHERE_BRIDGE_ACME_DIRECTORY_URL |
provisioning.plexdImage | ghcr.io/plexsphere/plexd:v0.8.0 | PLEXSPHERE_PROVISIONING_BROKER_PLEXD_IMAGE; tracks the plexd release the tree pins. |
provisioning.plexdDownloadUrl | "" (<externalUrl>/downloads/plexd) | PLEXSPHERE_PROVISIONING_BROKER_PLEXD_DOWNLOAD_URL, the base of the plexd download path; the install script of a broker-provisioned machine appends /<platform> and /checksums.sha256 to it. The default is the path the API serves. |
provisioning.plexdDownloadSourceUrl | "" (https://github.com/plexsphere/plexd/releases/download) | PLEXSPHERE_PLEXD_DOWNLOAD_SOURCE_URL, the base of the release assets the API fetches the pinned plexd binaries from on the first request per platform; it fetches <base>/<version>/plexd-<platform> and its .sigstore.json. Set it when the API cannot reach GitHub. |
provisioning.caBundle.configMap | "" | The ConfigMap whose key provisioning.caBundle.key holds the PEM certificates every machine the broker provisions trusts before it talks to the control plane. The API mounts the key at /etc/plexsphere/provisioning-ca and reads it once at boot through PLEXSPHERE_PROVISIONING_BROKER_CA_BUNDLE_FILE=/etc/plexsphere/provisioning-ca/ca.crt. Empty renders no variable, mount or volume. |
provisioning.caBundle.key | ca.crt | The one key read from that ConfigMap. Empty fails the render while provisioning.caBundle.configMap is set. |
provisioning.cloudCredentialsStore | "" | PLEXSPHERE_PROVISIONING_BROKER_CLOUD_CREDENTIALS_STORE, the ClusterSecretStore every credential ExternalSecret the broker renders into a Project namespace reads through. The operator installs the store. Setting it also adds a ClusterRole rule with get, list, watch, create, update, patch and delete on externalsecrets in external-secrets.io. Empty renders no ExternalSecret and no rule. |
provisioning.providerConfigApiGroups | [] | The API groups of the ProviderConfig kinds the broker renders, one per Cloud provider, such as openstack.m.crossplane.io. A non-empty list adds a ClusterRole rule with the same seven verbs on providerconfigs in exactly these groups. An entry that is not an API group name, * included, fails the render. |
audit.allowInsecurePepper | false | PLEXSPHERE_AUDIT_ALLOW_INSECURE_PEPPER=true: the deterministic static audit pepper, the dev-only alternative to audit.pepper.mount. It makes audit pseudonyms reversible. The render fails when both are set. |
audit.pepper.endpoint | "" | PLEXSPHERE_AUDIT_PEPPER_ENDPOINT, the OpenBao address. Required when audit.pepper.mount is set. |
audit.pepper.mount | "" | PLEXSPHERE_AUDIT_PEPPER_MOUNT; setting it selects the OpenBao audit pepper store, a KV v2 mount. The API creates each Domain's pepper at <mount>/audit/pepper/<domain id> on first use and needs create and read on <mount>/data/audit/pepper/*. |
audit.pepper.namespace | "" | PLEXSPHERE_AUDIT_PEPPER_NAMESPACE, the OpenBao namespace of the requests. |
audit.pepper.allowInsecureTls | false | PLEXSPHERE_AUDIT_PEPPER_TLS_SKIP_VERIFY=true: skips TLS verification of the OpenBao server, so an on-path attacker can read the login credential and every Domain pepper. The API logs an insecure opt-in warning at boot. Not for production. |
audit.pepper.appRoleSecret | "" | PLEXSPHERE_AUDIT_PEPPER_APPROLE_ROLE_ID and PLEXSPHERE_AUDIT_PEPPER_APPROLE_SECRET_ID, from the keys ROLE_ID and SECRET_ID. The API uses AppRole auth when this and audit.pepper.kubernetesRole are both set. |
audit.pepper.kubernetesRole | "" | PLEXSPHERE_AUDIT_PEPPER_K8S_ROLE, the OpenBao Kubernetes auth role. |
audit.pepper.kubernetesJwtPath | "" (/var/run/secrets/kubernetes.io/serviceaccount/token) | PLEXSPHERE_AUDIT_PEPPER_K8S_JWT_PATH, the service-account token Kubernetes auth presents. |
platformIdp.issuer | "" | PLEXSPHERE_PLATFORM_IDP_ISSUER |
platformIdp.clientId | "" | PLEXSPHERE_PLATFORM_IDP_CLIENT_ID |
platformIdp.clientSecretRef | "" | PLEXSPHERE_PLATFORM_IDP_CLIENT_SECRET_REF |
platformIdp.discoveryUrl | "" | PLEXSPHERE_PLATFORM_IDP_DISCOVERY_URL |
platformIdp.alias | "" | PLEXSPHERE_PLATFORM_IDP_ALIAS |
platformOperator.subject | "" | PLEXSPHERE_PLATFORM_OPERATOR_SUBJECT |
platformOperator.email | "" | PLEXSPHERE_PLATFORM_OPERATOR_EMAIL |
platformOperator.userId | "" | PLEXSPHERE_PLATFORM_OPERATOR_USER_ID |
platformOperator.additionalSubjects | [] | PLEXSPHERE_PLATFORM_OPERATOR_ADDITIONAL_SUBJECTS, the entries joined with commas; further Platform Operators, granted like platformOperator.subject, which must be set. |
artifacts.ociRegistry | "" | PLEXSPHERE_ARTIFACTS_OCI_REGISTRY; setting it turns the plexd artifact registry on. |
artifacts.fulcioSan | "" | PLEXSPHERE_ARTIFACTS_FULCIO_SAN |
artifacts.oidcIssuer | "" | PLEXSPHERE_ARTIFACTS_OIDC_ISSUER |
observability.mimirQueryUrl | "" | PLEXSPHERE_OBS_MIMIR_QUERY_URL |
observability.lokiQueryUrl | "" | PLEXSPHERE_OBS_LOKI_QUERY_URL |
observability.lokiUrl | "" | PLEXSPHERE_OBS_LOKI_URL |
observability.allowInternalSinkDestinations | false | PLEXSPHERE_OBS_ALLOW_INTERNAL_SINK_DESTINATIONS |
cloudCredentials.kvMount | "" | PLEXSPHERE_CLOUD_CREDENTIALS_KV_MOUNT; setting it turns the cloud credential surface on, a KV v2 mount. The API writes each credential at <mount>/clouds/<cloud id>/credentials/<credential id> and needs create and update on <mount>/data/clouds/*. Requires cloudCredentials.openbaoAddress and one auth method. |
cloudCredentials.openbaoAddress | "" | PLEXSPHERE_CLOUD_CREDENTIALS_OPENBAO_ADDRESS, the OpenBao address. Required when cloudCredentials.kvMount is set. |
cloudCredentials.namespace | "" | PLEXSPHERE_CLOUD_CREDENTIALS_OPENBAO_NAMESPACE, the OpenBao namespace of the requests. |
cloudCredentials.allowInsecureTls | false | PLEXSPHERE_CLOUD_CREDENTIALS_OPENBAO_TLS_SKIP_VERIFY=true: skips TLS verification of the OpenBao server, so an on-path attacker can read the login credential and every cloud credential. The API logs an insecure opt-in warning at boot. Not for production. |
cloudCredentials.appRoleSecret | "" | PLEXSPHERE_CLOUD_CREDENTIALS_OPENBAO_APPROLE_ROLE_ID and PLEXSPHERE_CLOUD_CREDENTIALS_OPENBAO_APPROLE_SECRET_ID, from the keys ROLE_ID and SECRET_ID. The API uses AppRole auth when this and cloudCredentials.kubernetesRole are both set. |
cloudCredentials.kubernetesRole | "" | PLEXSPHERE_CLOUD_CREDENTIALS_OPENBAO_K8S_ROLE, the OpenBao Kubernetes auth role. |
cloudCredentials.kubernetesJwtPath | "" (/var/run/secrets/kubernetes.io/serviceaccount/token) | PLEXSPHERE_CLOUD_CREDENTIALS_OPENBAO_K8S_JWT_PATH, the service-account token Kubernetes auth presents. |
cloudCredentials.defaultTtl | "" (24h) | PLEXSPHERE_CLOUD_CREDENTIALS_DEFAULT_TTL, the lifetime of a credential issued without one of its own, a Go duration. |
managementFleet.crossplaneNamespace | "" (crossplane-system) | PLEXSPHERE_MANAGEMENT_FLEET_CROSSPLANE_CONTROLLER_NAMESPACE |
managementFleet.externalSecretsNamespace | "" (external-secrets) | PLEXSPHERE_MANAGEMENT_FLEET_EXTERNAL_SECRETS_CONTROLLER_NAMESPACE |
networkPolicy.enabled | true | The three ingress NetworkPolicies: plexsphere-default-deny, which selects every Pod of the release and admits nothing, plexsphere over the API, and plexsphere-console over the console. |
networkPolicy.gatewayFrom | [] | The peers admitted to the API and to the console on TCP 8080. Entries are NetworkPolicyPeer maps (podSelector, namespaceSelector, ipBlock), each naming at least one label; an entry whose selectors name nothing matches every source and is refused. Required when gateway.enabled is true. |
networkPolicy.metricsFrom | [] | The peers admitted to the API on TCP 8080 beside networkPolicy.gatewayFrom, the port /metrics is served on. Same entry shape; empty admits nobody. |
gateway.enabled | false | The HTTPRoutes plexsphere-api (/v1 and /downloads to the API) and plexsphere-console (/ to the console). |
gateway.parentRefs | [] | Both routes' parentRefs. Required when gateway.enabled is true. |
gateway.hostnames | [] | Both routes' hostnames. |
spicedb.enabled | true | Installs the spicedb chart as a dependency. Every other key under spicedb: passes to it. |
The plexsphere-signer chart
| Key | Default | Drives |
|---|---|---|
fullnameOverride | "" | The name of every resource, and the host the API dials. |
image.repository | ghcr.io/plexsphere/plexsphere-signer | The signer image. |
image.tag | "" (the chart's appVersion) | The signer image tag. |
image.pullPolicy | IfNotPresent | The signer image pull policy. |
imagePullSecrets | [] | The signer Pod's imagePullSecrets, a list of {name} entries naming kubernetes.io/dockerconfigjson Secrets you create before helm install. Only name is rendered, and an empty list renders no key. |
resources | requests 25m / 64Mi, limits 500m / 256Mi | The signer container. |
keyProvider | "" | PLEXSPHERE_SIGNER_KEY_PROVIDER, one of pkcs11, aws-kms, gcp-kms and software. It has no default, and the render fails until it names one: software needs the devMode.enabled acknowledgement, and the other three need a key reference only you own. |
awsKms.region | "" | PLEXSPHERE_SIGNER_AWS_KMS_REGION. Required under aws-kms. |
awsKms.keyArn | "" | PLEXSPHERE_SIGNER_AWS_KMS_KEY_ARN, the ARN of the pre-provisioned Ed25519 KMS key. Required under aws-kms. |
gcpKms.keyVersion | "" | PLEXSPHERE_SIGNER_GCP_KMS_KEY_VERSION, the CryptoKeyVersion resource name projects/<p>/locations/<l>/keyRings/<r>/cryptoKeys/<k>/cryptoKeyVersions/<v>. Required under gcp-kms. |
pkcs11.modulePath | "" | PLEXSPHERE_SIGNER_PKCS11_MODULE, the absolute path of the vendor module inside the container. The image ships no vendor module; the extra* lists below carry one in. Required under pkcs11. |
pkcs11.tokenLabel | "" | PLEXSPHERE_SIGNER_PKCS11_TOKEN_LABEL. Exactly one of pkcs11.tokenLabel and pkcs11.slot is required under pkcs11: a soft token gets a random slot id at init and is addressable by label alone, an appliance with fixed slot numbering by id. |
pkcs11.slot | "" | PLEXSPHERE_SIGNER_PKCS11_SLOT, the numeric slot id. It is a string, so slot "0" stays expressible. |
pkcs11.label | plexsphere | PLEXSPHERE_SIGNER_PKCS11_LABEL, the label prefix the signer stores its keypairs under. |
pkcs11.pin.existingSecret | "" | The Secret holding the user PIN, mounted read-only at /etc/plexsphere-signer/pkcs11. Required under pkcs11. |
pkcs11.pin.key | pin | The key of that Secret PLEXSPHERE_SIGNER_PKCS11_PIN_FILE points at. The PIN reaches the process as that file alone: no value, environment variable or flag carries it. |
scope | platform | PLEXSPHERE_SIGNER_SCOPE |
deploymentProfile | selfhosted-single | PLEXSPHERE_SIGNER_DEPLOYMENT_PROFILE |
allowSpiffeId | "" | PLEXSPHERE_SIGNER_ALLOW_SPIFFE_ID, the SPIFFE ID of the API's client certificate. Empty boots the signer but denies every caller. |
devMode.enabled | false | PLEXSPHERE_ENV=dev on the signer container only. The software provider refuses to run without it, and the chart refuses every other provider with it, because the variable marks the whole release as a dev deployment. See the install how-to. |
database.existingSecret | plexsphere-database | PLEXSPHERE_SIGNER_DB_DSN |
database.key | DATABASE_URL | The key read from that Secret. |
tls.existingSecret | plexsphere-signer-tls | --tls-cert and --tls-key, mounted at /etc/plexsphere-signer/tls. |
clientCA.existingSecret | plexsphere-signer-client-ca | --client-ca, mounted at /etc/plexsphere-signer/client-ca. |
seed.existingSecret | plexsphere-signer-seed | PLEXSPHERE_SIGNER_KEY_PROVIDER_SEED_FILE, mounted at /etc/plexsphere-signer/seed. Rendered under keyProvider: software alone. |
serviceAccount.annotations | {} | The annotations on the signer's ServiceAccount: eks.amazonaws.com/role-arn for the IAM role the AWS KMS provider assumes, iam.gke.io/gcp-service-account for the service account the GCP KMS provider impersonates. Empty renders no annotations key. |
extraEnv | [] | Appended verbatim to the signer container's env, after everything the chart renders. |
extraEnvFrom | [] | The signer container's envFrom. This is where static cloud credentials belong on a cluster without workload identity: an entry names a Secret, while extraEnv holds its value in the Pod spec and in the Helm release history, readable by anyone who can read Deployments, Pods or release Secrets in the namespace. |
extraVolumes | [] | Appended verbatim to the Pod's volumes. |
extraVolumeMounts | [] | Appended verbatim to the signer container's volumeMounts. |
extraInitContainers | [] | The Pod's initContainers. With the three volume and mount lists it is how a vendor PKCS #11 module reaches the container: an init container copies the module out of a vendor image into an emptyDir both containers mount, and pkcs11.modulePath points at it. An image of your own that already holds the module needs none of them. |
networkPolicy.enabled | true | The NetworkPolicy admitting TCP 8443 only from the API pods. |
networkPolicy.apiPodLabels | app.kubernetes.io/name: plexsphere | The labels of the pods the NetworkPolicy admits. Follow a fullnameOverride of the plexsphere chart here; as a separate release this chart cannot compare the two and cannot refuse the render, and the mismatch surfaces only as a runtime 503 on access issuance. |
networkPolicy.metricsFrom | [] | The peers admitted to the HTTP metrics port 8081, a second ingress rule beside the one on 8443. Entries are NetworkPolicyPeer maps; empty admits nobody. |
The spicedb chart
| Key | Default | Drives |
|---|---|---|
fullnameOverride | "" | The name of every resource. The plexsphere chart follows it when it derives SPICEDB_ENDPOINT. |
image.repository | authzed/spicedb | The SpiceDB image. |
image.tag | v1.39.0 | The SpiceDB release, the one the dev stack runs. |
image.digest | sha256:b98123b44d730cbdfef29494ff9b8ff603d64418affe8fd64d90406ee4e0d9c5 | Pins the image; rendered as repository:tag@digest. |
image.pullPolicy | IfNotPresent | The SpiceDB image pull policy. |
imagePullSecrets | [] | The SpiceDB Pod's imagePullSecrets, a list of {name} entries naming kubernetes.io/dockerconfigjson Secrets you create, for a SpiceDB image mirrored into a private registry. Only name is rendered, and an empty list renders no key. As the plexsphere chart's dependency the key is spicedb.imagePullSecrets. |
replicas | 1 | spec.replicas of the SpiceDB Deployment; above 1 the chart also renders the PodDisruptionBudget and the topology spread constraint. Dispatch between replicas is not configured, and the Service is a ClusterIP with one virtual IP, so a count above 1 buys availability rather than throughput. |
logLevel | info | --log-level |
extraArgs | [] | Appended verbatim to the SpiceDB container's serve arguments, after the ones the chart renders. A repeated flag takes its last value. The datastore connection-pool flags are reachable only here. Never carry a credential in the list: it is rendered verbatim into the Pod spec and kept in the Helm release history, readable by anyone who can read Deployments, Pods or release Secrets in the namespace. The chart refuses a render that repeats --datastore-conn-uri or --grpc-preshared-key here; point datastore.existingSecret and presharedKey.existingSecret at your own Secret instead. |
resources | requests 50m / 64Mi, limits 500m / 256Mi | The migrate init container and the SpiceDB container. |
presharedKey.existingSecret | spicedb-credentials | --grpc-preshared-key |
presharedKey.key | SPICEDB_GRPC_PRESHARED_KEY | The key read from that Secret. |
datastore.existingSecret | spicedb-datastore | --datastore-conn-uri, for migrate head and serve. |
datastore.key | SPICEDB_DATASTORE_CONN_URI | The key read from that Secret. |
tls.enabled | true | --grpc-tls-cert-path=/etc/spicedb/tls/tls.crt and --grpc-tls-key-path=/etc/spicedb/tls/tls.key, and the Secret volume they read, mounted read-only at /etc/spicedb/tls. False serves plaintext gRPC and needs authz.spicedb.insecure: true on the parent chart. |
tls.existingSecret | "" | A kubernetes.io/tls Secret you create (tls.crt, tls.key, and the issuing CA as ca.crt) in place of the one cert-manager writes. Naming it renders no cert-manager resource, the path for a cluster without cert-manager. Mutually exclusive with tls.certManager.issuerRef. |
tls.certManager.issuerRef | {} | The issuerRef of the leaf Certificate, rendered verbatim (name, kind, group). Empty renders the chart's own self-signed Issuer, its CA Certificate and the Issuer that signs from it. A map without name fails the render. The leaf sets no lifetime and takes cert-manager's own, 90 days renewed 30 days before expiry, and SpiceDB picks up the renewed leaf without a restart. The chart's own CA is fixed at ten years and renews a year before expiry; neither lifetime is a value. |
networkPolicy.enabled | true | The NetworkPolicy admitting TCP 50051 only from the Pods networkPolicy.clientPodLabels selects. |
networkPolicy.clientPodLabels | app.kubernetes.io/name: plexsphere | The labels of the Pods the NetworkPolicy admits. Follow a fullnameOverride of the plexsphere chart here; as that chart's dependency it refuses a render whose labels name Pods its API does not carry. |
Render-time failures
These value combinations stop helm install, helm upgrade and helm template with a message:
| Message | Fix |
|---|---|
rateLimit.clientIpSource must be remote-addr or proxy-headers, got "<value>" | Set one of the two sources. |
provisioning.caBundle.key must name the key of provisioning.caBundle.configMap that holds the CA bundle | Name the key, or clear provisioning.caBundle.configMap for no CA. |
authz.spicedb.endpoint must be set when spicedb.enabled is false | Point the API at your SpiceDB, or keep the bundled one. |
gateway.parentRefs must name at least one Gateway when gateway.enabled is true | Name the Gateway the routes attach to. |
api.replicas must be a whole number of at least 1, got <value> | Set a whole number of at least 1. |
console.replicas must be a whole number of at least 1, got <value> | Set a whole number of at least 1. |
spicedb replicas must be a whole number of at least 1, got <value> | Set a whole number of at least 1. |
imagePullSecrets must be a list, got a <type> | Write the value as a list of {name} entries, such as [{name: plexsphere-registry}]. The message names the type of the value, never the value. The plexsphere and plexsphere-signer charts share this message. |
imagePullSecrets[<index>] must be an object with a non-empty string name, got a <type> | Give the entry a name that holds the Secret's name as a string. A bare string entry, an empty name and a number all fail. For an object entry the message lists its keys, never their values, so an entry that carries a registry password inline does not copy it into helm's output; create a kubernetes.io/dockerconfigjson Secret from that password and name the Secret instead. The plexsphere and plexsphere-signer charts share this message. |
spicedb imagePullSecrets must be a list, got a <type> | The spicedb chart's counterpart, for imagePullSecrets standalone and spicedb.imagePullSecrets as a dependency. |
spicedb imagePullSecrets[<index>] must be an object with a non-empty string name, got a <type> | The spicedb chart's counterpart of the entry refusal. |
spicedb extraArgs must not carry <flag> | Drop --grpc-preshared-key or --datastore-conn-uri from the list; the chart renders both from the Secret named in presharedKey.existingSecret and datastore.existingSecret. |
audit.allowInsecurePepper and audit.pepper.mount are mutually exclusive | Set audit.allowInsecurePepper to false for the OpenBao store, or clear audit.pepper.mount for a dev install. |
audit.pepper.endpoint must be set when audit.pepper.mount is set | Set the OpenBao address. |
audit.pepper needs appRoleSecret or kubernetesRole when audit.pepper.mount is set | Name the AppRole Secret or the OpenBao Kubernetes auth role. |
cloudCredentials.tokenSecret was removed: a published image never read the token; set cloudCredentials.kubernetesRole or cloudCredentials.appRoleSecret | Drop cloudCredentials.tokenSecret and log the API in with Kubernetes or AppRole auth. |
cloudCredentials.openbaoAddress must be set when cloudCredentials.kvMount is set | Set the OpenBao address. |
cloudCredentials needs appRoleSecret or kubernetesRole when cloudCredentials.kvMount is set | Name the AppRole Secret or the OpenBao Kubernetes auth role. |
provisioning.providerConfigApiGroups entries must be API group names, got "<entry>" | List lowercase API group names such as openstack.m.crossplane.io. A wildcard is refused, so the ClusterRole never grants providerconfigs in every group. |
networkPolicy.gatewayFrom must name the Gateway's pods when gateway.enabled and networkPolicy.enabled are true | Name the Gateway's Pods in networkPolicy.gatewayFrom, or the routes reach an API and a console no peer may enter. |
networkPolicy.<list>[<index>] must name a podSelector, namespaceSelector or ipBlock that selects something | Give the entry a selector that names at least one label. A peer with none matches every source, and so do podSelector: {} and namespaceSelector: {}, which carry the key and select every Pod of the namespace and of every namespace. <list> is gatewayFrom or metricsFrom. The plexsphere and plexsphere-signer charts share this message. |
spicedb tls.existingSecret and tls.certManager.issuerRef are mutually exclusive | Two ways to source the same Secret. Keep the Secret you create, or the issuer cert-manager signs from. |
spicedb tls.certManager.issuerRef must set name | Give the reference a name. cert-manager rejects one without it, the Certificate never issues, and every Pod that mounts its Secret waits on one that never appears. |
authz.spicedb.insecure must be false while spicedb.tls.enabled is true, or the API dials the bundled SpiceDB in plaintext and every check fails | Drop the authz.spicedb.insecure override; false is the default. |
authz.spicedb.insecure must be true while spicedb.tls.enabled is false, or the API expects TLS from a plaintext SpiceDB | The two keys move together: set authz.spicedb.insecure: true beside spicedb.tls.enabled: false, or turn TLS back on. |
authz.spicedb.caSecret must be empty while authz.spicedb.insecure is true | A plaintext dial verifies no certificate, so the CA that Secret carries would be mounted and never read. Clear it, or drop the plaintext opt-out. |
spicedb networkPolicy.clientPodLabels must hold at least one label | Name the labels of the Pods allowed on SpiceDB's gRPC port. An empty map selects every Pod in the namespace. |
keyProvider must be one of software, pkcs11, aws-kms, gcp-kms, got "<value>" | Name the provider. The chart has no default: software holds the signing key in the container's memory and the other three reach a key you provisioned, so the one a release wants is never the one a chart can guess. |
keyProvider software requires devMode.enabled: true, because the software provider keeps the signing seed in the container's memory and the signer refuses it outside dev mode | Set devMode.enabled: true for a dev install, or name a production provider. |
devMode.enabled must be false while keyProvider is "<value>", because PLEXSPHERE_ENV=dev marks the whole release as a dev deployment and is the software provider's acknowledgement alone | Drop devMode.enabled; a release with an operator-owned key needs no acknowledgement. |
awsKms.region must be set when keyProvider is aws-kms | Name the region the KMS key lives in. |
awsKms.keyArn must be set when keyProvider is aws-kms | Name the ARN of the pre-provisioned Ed25519 key. |
gcpKms.keyVersion must be set when keyProvider is gcp-kms | Name the CryptoKeyVersion resource name, version included. |
pkcs11.modulePath must be set when keyProvider is pkcs11 | Name the path the vendor module sits at inside the container. |
pkcs11.pin.existingSecret must name the Secret holding the user PIN when keyProvider is pkcs11 | Create the Secret and name it. The PIN reaches the process as a mounted file alone. |
exactly one of pkcs11.tokenLabel and pkcs11.slot must be set when keyProvider is pkcs11, got tokenLabel="<value>" slot="<value>" | Address the token one way. Both leaves the adapter to pick a winner you did not; neither leaves it with no slot to open. |
networkPolicy.apiPodLabels must hold at least one label | The plexsphere-signer chart's counterpart: name the labels of the Pods allowed on the signer's gRPC port. An empty map admits the whole namespace to it. |
spicedb.networkPolicy.enabled must be true while networkPolicy.enabled is true, or the release's default deny leaves the bundled SpiceDB unreachable | The plexsphere chart's default deny selects the bundled SpiceDB's Pods, and the spicedb chart's own policy is what readmits the API to them. Leave spicedb.networkPolicy.enabled on, or switch both keys off together. |
spicedb.networkPolicy.clientPodLabels must select the API pods, and <label> selects none of them, so the bundled SpiceDB's own policy leaves it unreachable from the API | The same rule from the other side: the labels have to match the API Pods'. A fullnameOverride of the plexsphere chart moves them, so spicedb.networkPolicy.clientPodLabels has to follow it. That policy isolates the SpiceDB Pods by itself, so this refusal stands whether networkPolicy.enabled is on or off. |
Security posture
Every Pod the charts render runs as a non-root user with an explicit UID and the RuntimeDefault seccomp profile. Every container runs on a read-only root filesystem, cannot escalate privileges, and drops every capability. Scratch space is a memory-backed emptyDir with a size limit. Only the API mounts a service-account token; it reaches the Kubernetes API for the management fleet and the provisioning broker. The charts install into a namespace that enforces the restricted Pod Security Standard.
The charts isolate ingress with NetworkPolicies. The plexsphere chart's default deny selects every Pod of its own release and admits nothing; one policy per workload then admits the peers the operator names, and the spicedb and plexsphere-signer charts admit the API on their own ports. A release whose networkPolicy.gatewayFrom is still empty admits nobody to the API and the console, which the plexsphere chart's notes say on every helm install and helm upgrade. Every policy sets policyTypes: [Ingress] alone, so egress stays open and restricting it belongs to the operator. A NetworkPolicy is inert unless the cluster's CNI plugin enforces it. Restrict network access has the values.
The bundled SpiceDB serves gRPC over TLS, so the preshared key does not cross the Pod network in cleartext, and neither do the relationship tuples the API writes and reads. The API verifies the served certificate against the CA that issued it and mounts that one key of the Secret, so SpiceDB's private key stays out of the API container. Plaintext gRPC is still reachable, and it takes both spicedb.tls.enabled: false and authz.spicedb.insecure: true: the chart refuses either half on its own, because a mismatched pair leaves the dial failing in its handshake and every permission check with it. The SpiceDB certificate section has the issuer and the existingSecret paths.