Skip to content

Adopt a Kubernetes cluster via the ESO pattern

The customer-side External Secrets Operator (ESO) pattern lets a Kubernetes cluster you already operate join the mesh as an Adopted Resource without exposing your secret store to plexsphere. You mint a single-use BootstrapToken in plexsphere, push it into your own secret store (Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault), define an ExternalSecret that materialises it as a Kubernetes Secret, and the plexd DaemonSet consumes it on first start. plexsphere never reads from your secret store and never writes into your cluster.

Prerequisites

  • An authenticated plexsphere session — see Log in with plexctl.
  • The owning Project UUID — see Manage Projects.
  • A target Kubernetes cluster with External Secrets Operator installed and a working ClusterSecretStore (or SecretStore) bound to the customer secret store the token will live in.
  • Write access to that secret store (Vault path, AWS Secrets Manager ARN, etc.) — plexsphere never touches it.
  • kubectl and either helm or a GitOps pipeline (Argo CD, Flux, Kustomize) reaching the target cluster.

Steps

1. Issue a BootstrapToken

shell
plexctl bootstrap-token issue \
  --project <project-uuid> \
  --kind node \
  --env-prefix prod \
  --ttl 1h

--kind is node; a bridge token is rejected by the Node endpoint. Capture the plaintext from the response immediately — the API returns it exactly once. The same token works for every channel, including the systemd channel documented under Register a Node.

2. Push the token into your secret store

Land the plaintext under a path your existing tooling already manages. The exact command depends on the store:

shell
# HashiCorp Vault (KV-v2)
vault kv put secret/plexsphere/<env-prefix>/bootstrap-token \
  token='<plaintext-from-step-1>'

# AWS Secrets Manager
aws secretsmanager create-secret \
  --name plexsphere/<env-prefix>/bootstrap-token \
  --secret-string '{"token":"<plaintext-from-step-1>"}'

Rotate or revoke the upstream secret once registration completes — the token is single-use, so the materialised value becomes inert after the first POST /v1/register.

3. Define the ExternalSecret

Apply an ExternalSecret that pulls the value into a Kubernetes Secret named plexd-bootstrap-token in the plexsphere-system namespace. The plexd DaemonSet mounts the Secret by that exact name; renaming it requires editing the chart values.

yaml
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: plexd-bootstrap-token
  namespace: plexsphere-system
spec:
  refreshInterval: 0s
  secretStoreRef:
    name: <your-cluster-secret-store>
    kind: ClusterSecretStore
  target:
    name: plexd-bootstrap-token
    creationPolicy: Owner
  data:
    - secretKey: token
      remoteRef:
        key: secret/plexsphere/<env-prefix>/bootstrap-token
        property: token

refreshInterval: 0s disables polling — the token is one-shot, so re-fetching it after consumption serves no purpose and risks re-materialising a value the upstream store has since rotated.

4. Install or apply the plexd chart

Install the plexd Helm chart (or apply the equivalent DaemonSet manifest the Enrollment Workshop emits) into the same namespace. The chart wires plexd to read /etc/plexd/bootstrap-token from the Secret mount and call POST /v1/register on first start.

shell
helm upgrade --install plexd plexsphere/plexd \
  --namespace plexsphere-system --create-namespace \
  --set controlPlaneUrl=https://control.example.com \
  --set bootstrapTokenSecret=plexd-bootstrap-token

When the DaemonSet rolls out, each node redeems the token and reports itself as an Adopted Resource. Because the token is single-use, only the first replica that wins the race consumes it; the rest receive 403 token_consumed and crash-loop until the operator either issues fresh tokens for them or removes the DaemonSet from those nodes.

Verification

shell
# 1. Confirm the ExternalSecret materialised the value.
kubectl -n plexsphere-system get externalsecret plexd-bootstrap-token \
  -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}'

# 2. Watch plexd come up and consume the token.
kubectl -n plexsphere-system logs daemonset/plexd -f \
  | grep --line-buffered 'register'

# 3. Confirm the Resource is visible in plexsphere with origin=Adopted.
#    Operator-facing verification today: read the row directly from the
#    plexsphere primary. A typed `GET /v1/projects/{id}/resources?origin=Adopted`
#    surface and a `plexctl resources` subcommand are tracked as a
#    follow-up; until they land, the canonical projection is the
#    plexsphere.resources row that the registration transaction
#    committed.
psql "${PLEXSPHERE_DSN}" \
  -c "SELECT id, kind, external_ref, origin
        FROM plexsphere.resources
       WHERE project_id = '<project-uuid>'
         AND origin = 'Adopted';"

The plexctl bootstrap-token get output now carries a populated consumed_at field; the Workshop Resources list shows the new row with an Adopted badge.

Out of scope

This how-to covers only the ESO pattern from token issuance through cluster materialisation. Customer Vault provisioning is not covered — installing or configuring HashiCorp Vault, AWS Secrets Manager, or any other secret backend is your own platform concern. plexsphere never reads from these stores and imposes no plexsphere-specific schema on them.

See also

  • ./cloud-init-daemonset.md — the bounded-context reference for the broker's bootstrap-injection renderers. Its DaemonSet bundle's External-Secrets-Operator mode emits the same plexd-bootstrap-token Secret name, plexsphere-system namespace, ClusterSecretStore ExternalSecret, and /etc/plexd/bootstrap-token mount this how-to assembles by hand — so a broker-rendered ESO bundle and the manifests below are interchangeable.
  • Issue a bootstrap token — mint the credential consumed here.
  • Register a Node — the underlying POST /v1/register contract every channel terminates at.
  • ./credentials.md — bounded-context reference for the OpenBao Credential Broker that owns plexsphere-side secrets.
  • External Secrets Operator documentation — vendor reference for the customer-side controller.