Skip to content

CRD Mental Model

The operator ships eight CRDs and it's not obvious which to use when. This page is the decision tree.

TL;DR

You want to... Use this
Run one Nextcloud, simple case NextcloudInstance
Onboard many tenants quickly NextcloudPool + Nextcloud
Share config defaults across instances NextcloudProfile
Pin Nextcloud version → chart version mapping NextcloudVersionMap
Run occ commands declaratively NextcloudCommand
Register a Talk HPB backend SignalingServer
Register a Talk recording backend RecordingServer

The four core CRDs

These interact and form a cascade. The other four (NextcloudVersionMap, NextcloudCommand, SignalingServer, RecordingServer) are independent add-ons.

flowchart LR
    Profile[NextcloudProfile<br/>defaults]
    Pool[NextcloudPool<br/>template]
    NC[Nextcloud<br/>tenant-facing]
    NCI[NextcloudInstance<br/>runs Nextcloud]

    Profile --> Pool
    Profile --> NCI
    Pool --> NCI
    NC --> NCI

    classDef core fill:#6366f1,stroke:#4f46e5,color:#fff
    class NCI core

Only NextcloudInstance represents a running Nextcloud. Everything else exists to produce or configure one.

Decision tree

flowchart TD
    Start([I want to run Nextcloud on Kubernetes]) --> Q1{How many<br/>Nextclouds?}

    Q1 -->|One, and I<br/>won't change<br/>profiles| Simple[Create a<br/>NextcloudInstance<br/>directly]
    Q1 -->|One or two,<br/>but I want<br/>reusable config| Q2{Is the config<br/>the same<br/>profile everywhere?}
    Q1 -->|Many tenants,<br/>on demand| Q3{Do I need<br/>fast onboarding<br/>under 30s?}

    Q2 -->|Yes| Profile[Create a NextcloudProfile,<br/>reference it from<br/>NextcloudInstance.spec.profile]
    Q2 -->|No, each differs| Simple

    Q3 -->|Yes| Pool[Create NextcloudPool +<br/>Nextcloud per tenant]
    Q3 -->|Slower is fine| FreshNC[Create Nextcloud per tenant<br/>without poolSelector —<br/>operator creates fresh<br/>NextcloudInstance each time]

    style Simple fill:#16a34a,stroke:#15803d,color:#fff
    style Profile fill:#16a34a,stroke:#15803d,color:#fff
    style Pool fill:#16a34a,stroke:#15803d,color:#fff
    style FreshNC fill:#16a34a,stroke:#15803d,color:#fff

"I want one Nextcloud"

Use NextcloudInstance directly. It's the simplest path. You write one YAML file, kubectl apply it, and the operator creates the Helm release.

apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudInstance
metadata:
  name: company
  namespace: nextcloud
spec:
  profile: production
  ingress:
    host: cloud.company.com
  database:
    managed: true
    type: postgresql
  admin:
    username: admin
    password: from-secret-manager

"I want reusable config across several Nextclouds"

Create a NextcloudProfile (cluster-scoped), then reference it by name from each NextcloudInstance.spec.profile. Profiles are the right answer to "my three test environments share 90% of their config."

Built-in profiles (production, testing, development) already exist — create custom ones only when the built-ins don't fit.

"I want to onboard tenants quickly"

Create a NextcloudPool (cluster-scoped) that pre-provisions N unassigned NextcloudInstance resources. Then tenants create a Nextcloud (namespaced, in their own namespace) with a poolSelector. The operator assigns a pool instance in ~30s.

If spec.poolSelector is empty, the operator creates a fresh instance (~2min). You can mix: some tenants pool-backed, others fresh.

"I want to run an occ command"

Create a NextcloudCommand targeting an existing instance. The operator runs the commands inside a Nextcloud pod via K8s exec, captures stdout/stderr/exit codes, and writes the results to status. Much better than kubectl exec for auditable, declarative maintenance.

See Running occ Commands.

The 4-layer configuration cascade

When the operator builds Helm values for a NextcloudInstance, it applies four layers in order, last wins:

flowchart LR
    B[1. Built-in profile<br/>production / testing / development]
    C[2. Custom NextcloudProfile CRD<br/>if spec.profile references one]
    S[3. Instance spec fields<br/>database, redis, s3, mail, admin, ...]
    H[4. spec.helm.values<br/>raw chart overrides]

    B --> C --> S --> H

    style H fill:#6366f1,stroke:#4f46e5,color:#fff
Layer Source Use when
1 Built-in profile Always active — gives sensible defaults
2 NextcloudProfile CRD You want org-wide defaults that differ from built-in
3 NextcloudInstance.spec Per-instance config that the operator knows about
4 spec.helm.values Raw Helm values that bypass the operator's schema — escape hatch

Use layer 4 sparingly. Anything you put there isn't validated by the operator and bypasses its invariants (e.g. secret name conventions).

When pool-based, the effective order becomes:

Built-in profile → NextcloudProfile → NextcloudPool.spec.template → Nextcloud.spec → NextcloudInstance.spec → spec.helm.values

When each CRD is not the right answer

CRD Don't use it for
NextcloudInstance Multi-tenant onboarding (no indirection, no pre-provisioning)
Nextcloud Running the operator without Flux — the instance it assigns needs Flux to install the chart
NextcloudPool High availability of a single tenant (use spec.replicas on an instance instead)
NextcloudProfile Per-tenant overrides — profiles are shared defaults, not per-instance config
NextcloudVersionMap Overriding a single instance's version — use spec.version or spec.helm.version
NextcloudCommand Scheduled periodic maintenance (use spec.maintenance on the instance)
SignalingServer / RecordingServer Anything other than Talk HPB backend registration

Scope: cluster vs. namespaced

CRD Scope Why
NextcloudInstance Namespaced Each instance runs in one namespace
Nextcloud Namespaced Lives in the tenant's namespace
NextcloudPool Cluster Pools span instance namespaces
NextcloudProfile Cluster Shared defaults across the cluster
NextcloudVersionMap Cluster One map per cluster; the operator uses the one named default
NextcloudCommand Namespaced Targets an instance in the same namespace
SignalingServer Cluster Backends serve all tenants
RecordingServer Cluster Backends serve all tenants

See also