Configuration Profiles¶
Overview¶
Profiles provide baseline configurations for different deployment scenarios, offering sane defaults for resources, probes, persistence, and other settings. This allows you to quickly deploy Nextcloud instances optimized for specific use cases without manually configuring every detail.
How Profiles Work¶
Profiles use a four-layer configuration system (last wins):
- Built-in profile defaults (lowest priority) — Baseline from
production,testing, ordevelopment - NextcloudProfile CRD — Custom profile that overrides or extends built-in profiles
- Spec fields (medium priority) — Your database, redis, s3, admin, mail configurations
- Helm values (highest priority) —
helm.valuesoverrides everything
This layering allows you to:
- Start with sensible defaults from a profile
- Customize via NextcloudProfile CRDs for organization standards
- Add your infrastructure specifics (database credentials, etc.)
- Fine-tune with custom Helm values when needed
Available Profiles¶
production¶
Purpose: Production-ready configuration with high availability, security hardening, and resource limits.
Key Features:
- Replicas: 2 pods for high availability
- Resources: 500m-2000m CPU, 512Mi-2Gi memory
- Persistence: 20Gi storage
- Probes: All probes enabled with production-safe timeouts
- Database: External database required (internal disabled); managed PostgreSQL gets pgBouncer enabled (
database.postgres.proxy.enabled: true) - Redis: HA replication topology (
redis.architecture: replication, 3 replicas) when managed Redis is used - PHP: Optimized with opcache settings (512M memory limit)
- Security: Pod security contexts and fsGroup set
- Strategy: RollingUpdate with zero downtime
Example:
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudInstance
metadata:
name: nextcloud-prod
spec:
profile: production
database:
type: postgresql
credentialsSecret: db-creds
testing¶
Purpose: Testing and staging environments with moderate resources.
Key Features:
- Replicas: 1 pod
- Resources: 250m-1000m CPU, 256Mi-1Gi memory
- Persistence: 10Gi storage
- Database: External database by default (internal disabled)
Example:
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudInstance
metadata:
name: nextcloud-staging
spec:
profile: testing
database:
type: postgresql
credentialsSecret: staging-db-creds
development¶
Purpose: Local development with minimal resources and fast startup.
Key Features:
- Replicas: 1 pod
- Resources: 100m-500m CPU, 128Mi-512Mi memory
- Persistence: 5Gi storage
- Probes: Liveness disabled, minimal readiness (fast startup)
- Database: Internal SQLite enabled (no external database needed)
Example:
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudInstance
metadata:
name: nextcloud-dev
spec:
profile: development
# That's it! Uses SQLite, minimal resources, default credentials
Profile Comparison¶
| Setting | production | testing | development |
|---|---|---|---|
| Replicas | 2 | 1 | 1 |
| CPU Request | 500m | 250m | 100m |
| CPU Limit | 2000m | 1000m | 500m |
| Memory Request | 512Mi | 256Mi | 128Mi |
| Memory Limit | 2Gi | 1Gi | 512Mi |
| Storage | 20Gi | 10Gi | 5Gi |
| Liveness Probe | 120s delay | 60s delay | Disabled |
| Readiness Probe | 60s delay | 30s delay | 10s delay |
| Startup Probe | 30s delay | 10s delay | Disabled |
| Internal DB | No | No | Yes (SQLite) |
| Redis architecture | replication (3 replicas) |
unset (standalone for new) | unset (standalone for new) |
| pgBouncer (managed PG) | enabled | disabled | disabled |
| PHP Memory | 512M | 256M | 128M |
| Security Context | Yes | No | No |
| Opcache | Yes | No | No |
Configuration Layering Examples¶
Profile + Database Override¶
spec:
profile: production # Get production defaults
database: # Override to use your database
type: postgresql
credentialsSecret: my-db-creds
Result: Production resource limits and probes + your PostgreSQL database
Profile + Helm Values Override¶
spec:
profile: production
database:
credentialsSecret: my-db-creds
helm:
values:
replicaCount: 5 # Override profile's 2 replicas
resources:
requests:
cpu: "1000m"
memory: "1Gi"
limits:
cpu: "4000m"
memory: "4Gi"
Result: Custom replicas and resources, but keeps production probes and PHP configs
When Profiles Apply¶
Profile defaults reach an instance in two separate ways with two separate
rules, and it's important not to conflate them: a one-time spec write-back
(persists profile defaults into the instance's own spec, e.g.
spec.database/spec.s3) and, since 0.21.1, an always-re-resolved Helm
values render (what the operator actually renders into the HelmRelease).
The spec write-back is still once, at instance creation — not continuous,
and unaffected by 0.21.1. The gate is status.appliedProfile: once the
instance's very first reconcile has run and written that status field, every
later reconcile — an ordinary spec-field update, a manual
k8s.bnerd.com/reconcile annotation, or the periodic timer — skips
re-merging the profile into the persisted spec, for as long as the instance
exists. This is deliberate: profile configuration is meant to be sticky
once an instance is running, so editing a shared NextcloudProfile (or an
instance's spec.profile pointing at a different one) doesn't silently
reconfigure every tenant using it the next time any of them happens to
reconcile — the same "avoid surprise reconfiguration of running tenants"
rationale that already governs assigned pool instances, which are likewise
never recreated or reconfigured in place when the pool's profile changes
(only unassigned spares are, and only if recreateOnProfileChange: true —
see Pool Provisioning → Lifecycle
Policies).
The Helm values render now always re-resolves the profile (0.21.1, review
finding E-1). Before 0.21.1, rendering skipped re-resolving the profile
entirely once status.appliedProfile was set, on the assumption that the
spec write-back above had already copied every profile-derived value where
the renderer would find it again. That assumption held for the common case
but not for instances the write-back never ran for — see below. 0.21.1
removed the skip: the profile now resolves fresh on every render. In
practice, because an instance's own declared values (spec.helm.values
above all) are still applied last and still win:
- A profile edit to a key the instance already has a value for — whether
from the write-back or your own
spec.helm.values/spec.<field>— still never propagates. The instance's existing value wins the merge exactly as before; re-resolving the profile underneath it changes nothing observable. - A key added to the profile after the instance was created — one the write-back never had a chance to capture — now renders, on the instance's first reconcile after upgrading to 0.21.1 and on every render after that. Previously it would have silently never appeared.
- Instances whose spec write-back never ran at all — created before
operator v0.10.2, or GitOps-managed, where the write-back is deliberately
skipped by design (see Operations → Profile Propagation at
Upgrade) — see the biggest
change: all profile-derived Helm values that aren't otherwise overridden
(
replicaCount,securityContext,podSecurityContext,nextcloud.strategy, etc.) now render correctly, where before they silently never did. - Persisted spec fields (
spec.database/spec.s3/etc., as opposed to the rendered Helm values derived from them) are untouched by any of the above — there is still no supported way to force a re-apply of those to an existing instance; the write-back remains create-time-only. - Only instances created after a profile edit get the edited value copied into their spec by the write-back; every existing instance's persisted spec fields stay exactly as materialized at creation, same as always.
Exception: managed-PostgreSQL pgBouncer tracks the live profile continuously¶
One field breaks the once-only rule: database.postgres.proxy (the
managed-PostgreSQL pgBouncer configuration) is re-read from the live
profile on every reconcile, not just at creation. This is deliberate —
pgBouncer is HA-critical (a missing or wrong spec.proxy.pgBouncer block can
crash the underlying Percona cluster; see Managed PostgreSQL)
and it needs to self-heal for instances whose spec never correctly picked it
up in the first place, including pre-0.20.0 instances and GitOps-managed
instances (whose CR spec is deliberately never written back to by the
operator — see Operations → Profile Propagation at Upgrade).
Practically: if a profile edit changes database.postgres.proxy.enabled
and, say, an existing resources.requests.cpu value in the same
update, an existing managed-PostgreSQL instance using that profile sees a
genuinely split outcome — the live PerconaPGCluster's pgBouncer state
changes on the next reconcile, but the rendered Helm resources value does
not (the instance's own materialized value still wins that merge — see
the Helm values render rule above). The split
narrows for the two vintages 0.21.1 restores full rendering for (instances
predating the spec write-back, and GitOps-managed ones): for those, a
resources change does render alongside the pgBouncer change, since
there's no materialized value overriding it.
One-time heal: spec.mail gap-fill for instances materialized before 0.20.0 (0.21.0+)¶
Mail configuration is Helm-values-only: spec.mail.fromAddress (and the rest
of spec.mail) maps straight to the chart's nextcloud.mail.* values, which
the image's smtp.config.php renders as pod env (MAIL_FROM_ADDRESS,
MAIL_DOMAIN, SMTP_*) after config.php is loaded — so an occ
config:system:set mail_from_address write is silently masked by the env
value whenever SMTP env vars are present, which they always are once
spec.mail is configured. There is no working occ-based path for this
field; the only mechanism the chart actually honors is the rendered Helm
value.
Before 0.20.0's merge-kernel fix (the "Behavior change in 0.20.0" section
below), a partial spec.mail override — say, an instance or pool template
that set domain/smtpHost but not fromAddress — caused the entire
profile-provided mail block to be discarded, including fromAddress, at
the instance's one-time creation-time materialization. 0.20.0 fixed the
merge kernel for instances created from that point on, but it did not
retroactively repair instances that had already materialized under the old,
lossy merge: build_helm_values still skips profile re-resolution once
status.appliedProfile is set (the same once-only rule as everywhere else
in this section), so a pre-0.20.0 instance's spec.mail stayed permanently
missing fromAddress with no convergence mechanism — the operator would
never look at it again.
0.21.0 adds a narrow, one-time exception to close that gap, in addition to
the pgBouncer exception above: on the periodic instance-status timer, for
any instance with status.appliedProfile set and that is not
GitOps-managed, the operator re-checks whether the profile's defaults.mail
provides any key the instance's materialized spec.mail is still missing.
If so, it writes the filled mail section back to spec.mail — instance-set
keys always win, only genuine gaps get filled — and emits a
MailConfigMigrated event recording which profile supplied the fill. This
is a one-time convergence, not a continuous sync like pgBouncer: once
spec.mail has been filled, the next tick's comparison finds nothing left
to fill and makes no further write (idempotent). GitOps-managed instances
are skipped entirely for the spec write — their Helm values are already
recomputed live from the current profile + spec on every sync, so they
converge without needing spec.mail touched at all.
Operational consequence: the write to spec.mail changes the rendered
Helm values, which updates the instance's HelmRelease, which changes the
chart's MAIL_FROM_ADDRESS/etc. env — so an affected instance's Nextcloud
pod(s) restart once, the next time the operator's status timer runs
after upgrading to 0.21.0. This is expected and is the entire point of the
fix (the alternative, an occ-based write, would restart nothing but also
fix nothing, per the masking behavior above) — see Operations → Profile
Propagation at Upgrade for
the fleet-wide convergence timeline.
fromAddress default-literal override (0.21.1)¶
The gap-fill above only fills a missing fromAddress key. Field verification of
the 0.21.0 upgrade found the dominant affected-fleet shape
wasn't a missing key at all: creation-era materialization for many
instances had written the historic operator/chart default literal
"nextcloud" into spec.mail.fromAddress as a present value — which
"instance wins" correctly leaves alone, so the gap-fill never touches it.
0.21.1 adds a second, narrower heal that runs alongside the gap-fill on the
same periodic instance-status timer: for a non-GitOps instance with an
applied profile, when spec.mail.fromAddress is exactly the literal
string "nextcloud" and the profile's defaults.mail.fromAddress
declares a different value, the operator overwrites it.
Override rule: the literal "nextcloud" is treated as provably
operator-authored — the only place the operator or chart ever writes it is
the historical default fallback in build_helm_values, not something a real
user would type unless they deliberately wanted it, the same justification
as the 0.21.0 generated-password migration. Any other value in
spec.mail.fromAddress, including a user who deliberately declared the
literal "nextcloud" themselves, is never touched — that instance value
always wins. This is an accepted, documented risk: the sentinel is
indistinguishable from a genuine user choice of the same string, but the
profile explicitly declaring something else is the same signal the gap-fill
above already trusts.
Otherwise identical to the gap-fill's mechanism: one MailConfigMigrated
event, idempotent (a healed value no longer matches the sentinel on the next
tick), skipped for GitOps-managed instances, and the same pod restart
once consequence — see Operational
consequence
above.
An instance with both a genuine gap and the sentinel takes two ticks, not
one. The gap-fill and this heal run in the same if/elif on the
instance-status timer — only one of them can fire per tick, in that order.
If an instance is missing a key the gap-fill would fill (say
spec.mail.domain was dropped by the same pre-0.20.0 lossy merge) and
spec.mail.fromAddress still carries the sentinel literal, the first tick
runs the gap-fill only (it finds something to fill, so the elif never
evaluates) — domain gets filled, fromAddress stays "nextcloud"
untouched, one MailConfigMigrated event. Only on the next tick, once the
gap-fill finds nothing left to fill (domain is no longer missing), does the
elif run the sentinel heal and fix fromAddress — a second
MailConfigMigrated event, and a second pod restart. This is expected, not a
bug: each write only ever touches spec.mail once per tick (single atomic
patch), and the two mechanisms are deliberately kept as separate,
independently-idempotent checks rather than merged into one, so a partial
failure in one doesn't block the other from ever running.
Parent Nextcloud CR also heals now (0.21.2)¶
The 0.21.1 heal above runs on the NextcloudInstance — but Rails-created
logical Nextcloud CRs can independently carry the same frozen literal
default in their own spec.mail.fromAddress. Before 0.21.2, the Nextcloud
handler's periodic status-sync timer (sync_status) copies the parent's spec
to the assigned instance whenever it detects drift — so a stale parent
literal got pushed straight back onto an instance the 0.21.1 heal had just
converged, reverting it on the very next 30s tick. Observed on a production
fleet: 35–37 MailConfigMigrated events per instance and continuous
spec/HelmRelease churn on roughly 28 tenants.
0.21.2 mirrors the identical heal (same heal_historic_mail_from_address
function, same override rule above — literal "nextcloud" and the
profile actively declaring something else) on the parent Nextcloud CR
itself, in the same sync_status tick and before the drift check runs, so
a healed parent never pushes the stale value back onto an already-healed
instance. Both layers now converge by construction:
- Two-tick shape: on the first tick where drift is detected, the parent
is healed (its own
spec.mail.fromAddressis patched) and the drift push (if one still fires that same tick) carries the healed value, never the stale literal. Once both the parent and the instance carry the samefromAddress, no furtherfromAddressvalue churn occurs — a content-no-op drift PATCH can still fire on that same 30s cadence for other, unrelated materialized keys (a pre-existing, fleet-wide, out-of-scope-for-0.21.2 behavior — see the Helm Values Ownership internals below), so "the loop is closed" means no repeat heal and no repeat revert, not literally zero writes forever. - Two-event shape: a fleet instance affected by both bugs can see up to
two
MailConfigMigratedevents total across its lifetime — one from the 0.21.1 NCI-side heal, one from the 0.21.2 parent-side heal — not per-tick repeats of either. - Why the refill's secret-mode guard doesn't apply here:
refill_mail_defaults's mode-switch guard (documented above, under the gap-fill) exists because several mail keys are credential-governed once acredentialsSecretis set.fromAddressis never one of those keys — it's never resolved from a Secret, inline or otherwise — so the heal ignores that guard entirely and applies uniformly regardless of which credential mode the instance is in. - Profile resolution falls back to the assigned instance's own
status.appliedProfilewhen the parent has nospec.profileof its own (0.21.2 review I-2) — pool mode and reference mode never setspec.profileon the parent; the profile is resolved via the pool template and recorded only on the instance. This fallback is what makes the heal fire at all — observed in a live fleet where no affected parent carriedspec.profile. - A GitOps-managed parent can never be healed — its write is
unconditionally skipped (Flux would just revert it) — so its
spec.mail.fromAddressstays the stale literal forever if it was ever set that way. To stop that permanently-unheal-able parent from reverting an already-healed instance anyway, the drift push itself ignores the sentinel literal (0.21.2 review I-1): when pushing the parent's spec onto the instance, afromAddressthat is still exactly"nextcloud"is replaced with the instance's own current value before the push, treating the literal as "no opinion" rather than an instruction to revert. This only filters what gets pushed to the instance — it never writes anything back to the GitOps-managed parent itself.
How Defaults Merge¶
Profile defaults reach an instance through the four-layer precedence chain from
How Profiles Work above (built-in profile → NextcloudProfile
CRD → instance spec → helm.values), but how a given defaults.<key> combines with
the instance's own spec.<key> depends on which bucket the key falls into. The
operator classifies every top-level defaults key at profile-resolution time:
| Bucket | What it means | Merge behavior |
|---|---|---|
| Spec-level | One of s3, mail, database, redis, admin, ingress, oidc, version, image, apps, backups, maintenance, persistence, cronjob, livenessProbe, readinessProbe, startupProbe, resources, replicas, php, extraVolumes, extraVolumeMounts, hooks, placement, upgradePolicy — consumed as a NextcloudInstance spec field. |
Deep-merged, with one exception (hooks — see below). Where both the profile and the instance set the same key as an object, they merge field by field at every depth — the instance wins on conflicts, the profile fills in whatever the instance didn't set. If the instance's value is a scalar or a list instead, the instance's value wins outright (lists are never concatenated/merged). |
| Known Helm value | Not spec-level, but a recognized top-level key in the upstream nextcloud Helm chart's values.yaml (e.g. hpa, nginx, metrics, securityContext, mariadb). |
Passed straight through as a raw Helm value — the operator doesn't interpret it. |
| Unknown | Neither of the above — most likely a typo, or a key from a different chart version. | Still passed through unchanged (nothing is ever dropped or rejected), but the operator emits a Kubernetes warning event on the consuming instance with reason UnknownProfileKey so you notice and can fix it. |
A key present in both the spec-level and known-Helm-value lists (e.g. ingress,
redis, resources, persistence, cronjob, livenessProbe, readinessProbe)
always classifies as spec-level — its deep-merge semantics take precedence.
Exception: hooks replaces wholesale, never deep-merges. Every other
spec-level key deep-merges field by field, but hooks follows the lifecycle-hooks
cascade's own documented contract instead — "last source wins" (replace, not
concatenate), see Commands → Lifecycle hooks → Cascade. If
the instance sets spec.hooks at all, its value replaces the profile's hooks
block entirely, even field by field within it (e.g. an instance setting only
hooks.onEveryReady does not pick up the profile's hooks.onFirstReady —
that field is simply gone, not merged in). The profile's hooks is only used
when the instance doesn't set hooks at all. This is deliberate: deep-merging two
independently authored onFirstReady/onAssignmentReady/onEveryReady lists
would silently union two different lifecycle configs instead of letting the more
specific source take over cleanly.
Merge behavior no longer depends on which resource references the profile:
Nextcloud and standalone NextcloudInstance both go through the same deep-merge
logic (this used to differ — see the behavior-change note below).
Example — a profile with defaults.ingress.tls.certManager: false and an
instance that only sets ingress.host:
kind: NextcloudInstance # or Nextcloud — merge behavior is now identical
spec:
profile: byo-cert
ingress:
host: cloud.example.com
# Result: certManager: false and every other profile-provided ingress field
# (tls.secretName, className, trustedProxies, ...) survive; only host comes
# from the instance.
Behavior change in 0.20.0¶
Before 0.20.0, only apps and placement deep-merged; every other spec-level key
was all-or-nothing — if the instance set any part of that key at all, the
profile's entire value for that key was silently discarded, not merged (the example
above used to lose certManager: false on a standalone NextcloudInstance, though
not on a Nextcloud). This is fixed in 0.20.0: every spec-level key now deep-merges
the same way, on every resource. This is a real, user-visible behavior change —
an existing instance that partially overrides a spec-level key it shares with its
profile will start receiving the profile's other fields for that key on its next
reconcile after upgrading, which can change the rendered Helm values and trigger a
rolling update. If you were relying on the old all-or-nothing behavior to fully
replace a profile-provided block, see the caveat below.
Caveat: connection-shaped keys (s3, oidc, mail, database)¶
Most spec-level keys are independent toggles (ingress, resources, placement, …)
where merging field-by-field is unambiguous. A few — s3, oidc, mail,
database — describe one coherent external target (bucket + region + endpoint;
issuer + client ID + discovery URL; SMTP host + port + auth). Deep-merging these the
same generic way means a partial instance override only replaces the fields it
names; the profile's other fields for that same block keep applying:
# Profile: defaults.s3 = {bucket: shared-bucket, endpoint: s3.eu-west.example.com, region: eu-west-1}
# Instance only wants a different endpoint:
spec:
profile: my-s3-profile
s3:
endpoint: s3.eu-central.example.com
# Result: bucket and region still come from the profile -- the instance now
# points at a mix of an "eu-central" endpoint with an "eu-west" bucket/region,
# which may not be what was intended.
Rule of thumb: if you mean to fully redirect a connection-shaped block
(s3, oidc, mail, database) to a different target, override every field that
identifies that target, not just the one that changed.
Custom Profiles (NextcloudProfile CRD)¶
The operator supports custom profiles via the NextcloudProfile CRD, which is cluster-scoped. Custom profiles take precedence over built-in profiles of the same name.
Creating a Custom Profile¶
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudProfile
metadata:
name: enterprise
spec:
description: "Enterprise profile with HA and backups"
defaults:
replicaCount: 3
resources:
requests:
cpu: "1000m"
memory: "1Gi"
limits:
cpu: "4000m"
memory: "4Gi"
persistence:
enabled: true
size: "200Gi"
storageClass: "fast-ssd"
internalDatabase:
enabled: false
# Optional: set default container image for all instances using this profile
image:
registry: my-registry.example.com
repository: nextcloud
pullSecrets:
- name: enterprise-registry
helm:
version: "5.0.0"
Custom Images
Profiles can set defaults.image to configure a custom container image for all instances using the profile. This is useful for organizations with private registries. See Version Management for the full image resolution priority.
Using a Custom Profile¶
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudInstance
metadata:
name: my-instance
spec:
profile: enterprise
database:
type: postgresql
credentialsSecret: db-creds
Pinning the Nextcloud Version¶
spec.defaults accepts both Helm chart values and instance spec-level fields — see How Defaults Merge above for the full key-classification table and merge semantics. Setting a spec-level key in defaults pre-fills it on every instance using the profile; where both profile and instance set the same key, they deep-merge (instance wins conflicts, profile fills gaps).
The most common use case is pinning the Nextcloud version:
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudProfile
metadata:
name: nextcloud-31-pinned
spec:
description: Pin all instances using this profile to the latest Nextcloud 31.x
defaults:
version: "31" # Resolved via NextcloudVersionMap → latest 31.x
replicaCount: 2
Then instances inherit the version automatically:
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudInstance
metadata:
name: tenant-a
spec:
profile: nextcloud-31-pinned
# no spec.version — picks up "31" from the profile
defaults.version follows the same resolution rules as spec.version -- exact versions, prefix shorthand, and aliases (latest, stable). To override on a single instance, set spec.version explicitly. See Version Management for the full version-resolution chain. A complete example lives at examples/profile-pinned-version.yaml.
Overriding Built-in Profiles¶
Create a NextcloudProfile with the same name as a built-in profile:
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudProfile
metadata:
name: production # Overrides built-in 'production'
spec:
description: "Our customized production profile"
defaults:
replicaCount: 3 # Different from built-in (2)
Profile Management Notes¶
- A profile edit to an already-materialized key never propagates to an existing instance's persisted spec or its rendered Helm values — profile defaults are written into the spec once, at instance creation, and nothing — not a spec-field update, not the
k8s.bnerd.com/reconcileannotation (see the Operations & Annotations guide), not the periodic timer — re-applies them afterward. Since 0.21.1, a new key added to the profile does render on the next reconcile (it was never materialized, so nothing overrides it), and instances whose spec write-back never ran (pre-v0.10.2, or GitOps-managed) get the full profile rendered instead of silently missing it. See When Profiles Apply above for the full mechanism and the pgBouncer exception. - Don't delete in-use profiles: Instances will fail to reconcile until the profile is recreated or the profile field is changed.
- Validation: Invalid Helm values in profiles will cause HelmRelease failures. Test profiles in a non-production environment first.
Troubleshooting¶
Profile Not Applied¶
Symptom: Resources don't match expected profile values
Check:
- Verify profile field:
kubectl get nci <name> -o yaml | grep profile - Check for
helm.valuesoverrides that might be taking precedence - Look at generated HelmRelease:
kubectl get helmrelease <name> -o yaml - If the profile was edited after the instance was created, whether this is expected depends on which key: an edit to a key the instance already has a materialized value for is expected, not a bug — see When Profiles Apply: that value only applies once, at creation (except managed-PostgreSQL pgBouncer).
kubectl get nci <name> -o jsonpath='{.status.appliedProfile}'non-empty confirms the instance is already materialized. But since 0.21.1, a key genuinely new to the profile since creation, or the profile as a whole for an instance whose spec write-back never ran (pre-v0.10.2 or GitOps-managed), should render on the next reconcile — if it doesn't, that's a real bug, not this expected behavior.
Profile Conflicts with Helm Values¶
Helm values have highest priority and will override profile defaults:
spec:
profile: production # Sets replicaCount: 2
helm:
values:
replicaCount: 1 # This wins! Final result: 1 replica
Invalid Profile Name¶
Use one of: production, testing, development (case-sensitive, lowercase), or the name of a custom NextcloudProfile CRD.