Chart Style Guide
Justin Klein edited this page 2026-10-15 17:51:52 +00:00
Clone

Wiki Page Revisions

19 Commits

Author SHA1 Message Date
jklein 42e5f416f0 paperless-ngx, CI/CD, style guide, lessons: an egress rule names the pod port
Gitea #136. The paperless-ngx page said an egress rule names the port on the
destination as the client dials it, and that paperless dials the Tika and
Gotenberg Services on 80. The chart acted on it and the release cut Paperless
off from both services on 2026-10-15.

kube-proxy does a ClusterIP Service's destination NAT before the egress policy
is evaluated at the pod's interface, so the policy sees podIP:targetPort - the
rule names the pod port, 9998 and 3000 here. The old sentence is quoted as
refuted rather than removed, together with the symptom that identifies it: a
timeout, not connection refused.

- paperless-ngx: the rule's ports and the quick repair for 14.0.0, the new
  networkPolicy.egress.mailImport (993, off), extraPorts as pod ports inside the
  cluster, and the install-test's Tika gate being off, so a green install-test is
  not cover for the corrected path.
- CI-CD: the cross-chart assertion as a fifth row, why it needs two charts, the
  gate-on/gate-off pair, and 210 assertions over 30 charts.
- Chart-Style-Guide: pod ports also in egress, and the bar for a named egress
  switch - no second switch for a port another value already decides.
- Lessons-Learned: the lesson, plus how the wrong sentence passed every check
  while the correct mechanism stood two days older in the style guide.
2026-10-15 17:51:52 +00:00
jklein f775e451d1 NetworkPolicy follow-up (Gitea #5): the gotenberg switch, and three things that were load-bearing and written nowhere
Chart-Style-Guide, gotenberg and keycloak.

THE CONVENTION SENTENCE that explains the whole shape of this rollout, and was
missing: no chart ships allowedNamespaces pre-filled, ever. A chart naming
mc-router or stalwart in its defaults would bake in an assumption about this
cluster's namespace layout, which a chart cannot keep true. Restrictive is the
default; filling it is the DEPLOYMENT adaptation, made where the namespace
layout is visible. So the release-side one-liners this sweep produces are the
expected procedure rather than a defect list, and the major on each chart is
what announces them. ingressController is the single exception, because it
names a cluster component and not a tenant.

THE ONE CLUSTER SETTING THIS CONVENTION DEPENDS ON AND CANNOT CHECK: Traefik
must not run with hostNetwork. Measured 2026-10-14 - the rke2-traefik DaemonSet
has none and its pods carry real pod IPs (10.42.3.120, 10.42.4.94, 10.42.0.187
in the pod CIDR 10.42.0.0/16, while the nodes sit on entirely different
addresses), and only because of that does a podSelector match it. On host
networking those pods share the node's network namespace, stop being selectable
as pods, the ingressController entry matches nothing, and every web chart in
the repository goes dark at once - while every policy is still accepted and
still reads as correct. The symptom to recognise is exactly that shape: all web
traffic stops together and every pod stays Ready, because the probe path does
not run through the controller. Same class of dependency as
--enable-certificate-owner-ref in #126, and it is recorded both here and in the
header of template-chart's policy, so whoever changes the controller's
networking meets the consequence on the thing they are changing.

AND WHY THE ingressController RULE WORKS AT ALL: a policy applies to traffic
that came through a Service. A Service is only destination NAT and the policy
is evaluated at the POD's interface, so a ClusterIP exempts nothing. What the
Service changes is the SOURCE ADDRESS - pod to ClusterIP to pod preserves it,
so a podSelector can still match the Traefik pod; NodePort traffic with the
default externalTrafficPolicy: Cluster is masqueraded to a node, which is why
those rules cannot be restricted by source and are left open instead.
stalwart's template already carried the second half; the first is the one
everything rests on.

gotenberg gains networkPolicy.egress.remoteAssets, off by default - the
maintainer's decision, and it keeps restrictive-by-default intact: a named
switch rather than an opening, so the trade is a decision someone made instead
of an unplaceable [80, 443] in extraPorts. Its page now carries both measured
halves: paperless-ngx sends the .eml to TIKA for parsing and the resulting HTML
HERE for rendering, two separate cluster-internal endpoints - so EMBEDDED
images are MIME parts inside the file and need no network from either service
(which is why apache-tika stays DNS-only), while REMOTE images are fetched by
Chromium at render time and are silently dropped with the switch off. The PDF
is produced and is simply missing them. Named as unverified, because it was not
checked: whether paperless re-embeds what Tika extracted before handing the
HTML over. If it does not, embedded images would be lost too - but that would
be pre-existing paperless behaviour and not the policy.

keycloak's page gets the measurement that makes its blast radius smaller than
the page previously implied: every readable in-cluster consumer uses the PUBLIC
name (bulwark's OAUTH_ISSUER_URL, homarr's AUTH_OIDC_ISSUER, the paperless
releases through their socialaccount block), so their traffic arrives from
Traefik and is permitted as shipped. A search for the in-cluster Service name
across pod environments and ConfigMaps returned only Keycloak's own pods
reaching keycloak-pg-rw.jlab-pg.svc.cluster.local:5432 - which also confirms
the egress rule derived from keycloak.db.port is right for the live release.
The login test stays the acceptance criterion, and the residual risk is stated
rather than dismissed: a consumer whose issuer URL lives in a secret or a realm
import is not covered by that search. What changed is that there is now a
measured reason to expect the test to pass.
2026-10-14 21:21:24 +00:00
jklein 81980279c4 NetworkPolicies in every chart (Gitea #5): the convention, the gate, and what it does not do
29 chart pages plus Chart-Style-Guide, CI-CD, Charts and Lessons-Learned.

Chart-Style-Guide gains section 15, and its core is the FOUR PORT CLASSES -
the part that is actually hard to get right. A port a network probe addresses
stays open until probeSources is set, because the kubelet probes from the
worker node's IP and no chart can know it. A NodePort-published port stays open
because the source is already masqueraded. Everything else can be and is
restricted by source, which is why the charts with a real inbound restriction
are exactly those whose probes use a different port. A replica-to-replica bus
gets a bare podSelector - and, since policyTypes lists Egress, its outbound
side needs a rule of its own or the cluster never forms while every node looks
healthy.

The exec-probe distinction is written down as load-bearing rather than
pedantic: it is the whole reason valkey and minecraft-server are the tightest
policies in the repository and every HTTP-probed web app is looser.

CI-CD gains the networkpolicy job as section 8 (the later two renumbered), and
it ends with the measurement rather than a claim. Two experiments ran on the PR
and were reverted: cyberchef's probe port restricted to an address no probe can
come from stayed GREEN at 1m9s, and outline with its DNS egress rule removed
went RED at 5m59s. Together those say k3s exempts the kubelet's probe traffic
while enforcing everything else - so the install-test's net is an EGRESS net,
and a wrong ingress port is caught by the render assertion or not at all.

Every chart page gets a networkPolicy values table and an operating note naming
that chart's own trap, not a boilerplate one:

- keycloak carries the blast radius: it is the cluster's identity provider, its
  http rule is the only source-restricted inbound rule in the repo, and nothing
  in CI can catch a wrong controller - so the page asks for a real login and a
  look at consumers' issuer URLs after the first upgrade.
- garage asks the reader to check their S3 consumers, because anything in
  another namespace is blocked until it is in allowedNamespaces.
- bulwark names the quietest failure in the sweep: its probes test its own port,
  so a jmapServerUrl on a blocked port yields a Ready pod serving an empty
  webmail.
- gotenberg explains what its DNS-only cap COSTS - HTML conversion silently
  loses remote assets - rather than only what it buys.
- satisfactory and homeassistant each argue their own Ingress-only policy;
  satisfactory's deciding half is that it is the one chart with no install-test
  at all, so an egress cap there would be the only one with no empirical check
  behind it.
- paperless-ngx names the two pods of its release that the object does NOT
  cover, including the consume CronJob whose pod template carries no labels.
- freesailarr explains why ten containers behind a VPN need only three egress
  rules, and that extraPorts cannot help with an alternative WireGuard port
  because it is TCP-only.

valkey and stalwart are corrected rather than extended. valkey's page now says
why having no probeSources is what makes it the tightest policy here and not an
incomplete one. stalwart's carries a dated correction: this page and the chart
both advised putting the ingress controller's namespace into allowedNamespaces
with ["ingress-nginx"] as the example, and both halves are wrong - ingress-nginx
is gone from the cluster entirely, and allowedNamespaces would have opened http
to every pod in that namespace anyway.

Charts drops the claim that valkey is "the first chart with a NetworkPolicy on
by default" and says instead that every chart has one, with the two shapes a
reader needs before opening any chart page.

Lessons-Learned gains four, all transferable:

- the net that existed for a different failure than the one it was built for,
  and why both experiments were needed before either could be read;
- a comment that could not describe its own syntax (a template comment must not
  spell out its own closing delimiter; the render then fails pointing at the
  template, not at the sentence);
- three values that reached no rule and would have read as controls - a
  convention about value NAMES is not a convention about which values exist;
- the subchart whose comments the parent could not fix, and the rule it
  produced: before making a check a gate, ask which failures the author of this
  diff can actually fix.
2026-10-14 14:57:00 +00:00
jklein 1caabc1a37 paperless-ngx 13.0.0: quoted scalars, the pinned mover, and oidcSecretRef gone (Gitea #11/#101/#112)
paperless-ngx: the removed secrets.oidcSecretRef and why it is not coming back
(one json env var, no _FILE variant; Keycloak marks a client public per
client), tokenAuthMethod restricted to "none" and why client_secret_basic
could never work beside a chart that configures no secret, the new
smbConnection.cron keys, and the env block's quoting with
PAPERLESS_LOGGING_DIR as the worked example - cross-referenced to
jk-cluster-config #360, which it does NOT close.

Two notes were wrong rather than incomplete and are replaced, not appended:
"A schedule may not start with *" documented the bug as a workaround ("use an
equivalent that starts with a digit"), and the probes-exception section said
paperless.replicas: 0 keeps moving PDFs off the share - with the required
podAffinity it no longer can, and what the operator gets instead is a failing
job per schedule.

CI-CD: the new yaml-scalar job as 7 (trivy-config 7->8, install-test 8->9,
following the renumbering the #128 round started), required podAffinity added
to the selector-match job's scope, and the two render-only overlay kinds in the
fixtures table.

Chart-Style-Guide: the | quote half of the env convention, which was never
written down here although template-chart has carried it all along - together
with the six charts that do not follow it, because template-chart's own comment
claimed all of them did while seven did not.

Lessons-Learned: the genus. A value the operator quotes correctly that still
lands bare in the manifest; its quiet half, where the render succeeds and the
API server rejects the object; the defect that survived because no fixture
rendered the object carrying it; and the issue that was wrong about the defect
while being right about the pain.
2026-10-13 19:17:12 +00:00
jklein 6c1c01453e The chart bump follows the app bump (Gitea #128)
The decision from issue #128: app major -> chart major, app minor ->
chart MINOR, patch/digest -> chart patch, as a maximum against the
chart's own classification rather than a replacement. The level comes
from Renovate's update type, since an appVersion comparison is blind to
the update that prompted it - freesailarr's appVersion never moves.

Versioning carries the rule, Renovate the hook and its measurements,
Workflow the wiki exception where "plain" is now mechanically defined,
CI-CD the new bump-level job, and Lessons-Learned the decision together
with its limit: the reading duty was rejected, so the incident that
lesson is about stays repeatable and the honest number is the whole of
the remedy.

Chart-Style-Guide, ytdlp-web-player, bulwark and stalwart each carried
a sentence calling the hook's result a chart patch, which it no longer
always is.
2026-10-13 18:48:54 +00:00
jklein 7159e15564 Style guide: repair the index-naming rule's reason, and align stalwart on index
The rule read "acceptable only because a route holds no state and no
certificate". That was true of homeassistant, the chart it was written for,
and false of every chart it was later applied to: keycloak's realm
Ingresses (#122) and stalwart's MTA-STS Ingresses (#123) both own
cert-manager Certificates, so the rule's own precondition excluded the
cases it was being cited for.

It surfaced only because the two rounds ran in parallel and answered the
same sentence differently - keycloak by index citing the rule, stalwart by
issuer citing the rule's precondition. The maintainer settled it on index
(keycloak was already published) and the REASON is what gets corrected
here, not the naming of two charts.

The reason that holds, in three steps kept apart because their standing
differs: cert-manager here runs without --enable-certificate-owner-ref
(MEASURED - absent from the Deployment args and from the
ControllerConfiguration, which carries only kind and enableGatewayAPI);
by that default the Certificate is collected with its Ingress but the
SECRET IS NOT (DOCUMENTED behaviour); and no rename was ever performed
(NOT APPLIED). So a rename recreates the Certificate against a surviving
secret and issues nothing - and the promise hangs on a cluster flag this
repository does not control.

Also: stalwart added to that rule's examples; the per-entry-issuer
paragraph corrected from "name after the issuer" to "name by index" with
the obligation that follows (NOTES.txt must print index -> issuer -> hosts,
since the name no longer carries it); the sharper reorder hazard recorded
(the index runs over groups derived from the list, so a reorder can
renumber objects that did not move); and the note that the index question
only arises where a mode renders one object per entry - stalwart's Gateway
mode renders a single route, so it has the question on the Ingress side
only. homeassistant stays named as the certificate-free original case.

Lessons learned: my own point 3 said the opposite and is replaced by what
the round actually taught - a rule can be right while its written reason is
wrong, a justification is load-bearing because it is what someone reaches
for when the rule meets an unforeseen case, and parallel rounds are a
conformance test nobody wrote (sequentially the second would have followed
the first and the broken reason would have survived).

stalwart: one half-sentence that was missing from "TLS belongs to the
platform" - cert-manager runs here with enableGatewayAPI: true, so the
listener certificates can be issued by it from an annotation on the
Gateway. The work moves to the Gateway object rather than disappearing.
2026-10-13 11:04:01 +00:00
jklein 28db5e2ba4 stalwart: MTA-STS routes as a maintainable list, in both routing modes
Gitea #123. The policy route for mta-sts.<domain> moves out of
jk-cluster-config into the chart as mtaSts.hosts.

stalwart: a new mtaSts values section - the entry form, the two object
shapes and why they differ, the hardwired RFC 8461 path, the rejected
certificate.extraDnsNames alternative, and the migration order. Three new
traps: the chart registers a route and does not create a domain (404 at a
correctly routed endpoint, silent on the sender's side); a parent wildcard
does not cover mta-sts.sub.<domain>; and why one Ingress per cluster issuer
cannot be one object. CI section: 2 -> 4 assertions, plus the install-test
covering the Ingress half the overlay cannot reach.

CI-CD: 58 assertions over 21 charts, why the chart count does not move, and
the note that an overlay never covers the Ingress half of a both-modes
feature.

Chart-Style-Guide: section 4 gains the rule for a host list with a
per-entry secret AND a per-entry issuer, and the rule that a both-modes
feature must render in both - inert and documented where one mode cannot
carry a part of it, never required.

Lessons-Learned: the two fields cert-manager reads at different scopes.
The annotation is object-scoped and the secret is tls-entry-scoped, so a
per-entry optional issuer cannot live on one object - and ingress-shim
names the Certificate after the secret and owns it from the Ingress, so a
shared secret must sit in exactly one object. Generalises past this chart;
2026-10-13 10:21:46 +00:00
jklein 20d504722e keycloak: one hostname per realm, in both routing modes (#122)
The chart gains keycloak.realmHosts: a list rendering one routing object
per entry - an Ingress with app-root, or an HTTPRoute whose Exact match on
/ carries a RequestRedirect filter to /realms/<realm>/account/. Empty by
default, so nothing renders and no installation changes.

keycloak: the new Configurable-values row, and the section 'Four more
Ingress objects in this namespace, and they are not this chart's' is
replaced by 'One hostname per realm' - the four objects are still named
there, but now as the thing a migration would adopt rather than as a
permanent neighbour. With the per-mode object table, the two required
deviations (per-entry tlsSecretName, optional per-entry issuer), the six
render aborts and what each prevents, the proxy-buffer loss in Gateway
mode phrased for account-console traffic, and the migration order with
its one unchecked question.

Chart-Style-Guide: the third admissible Ingress shape, with both
deviations stated as required rather than tolerated, and keycloak added to
the index-naming rule beside homeassistant.

CI-CD: 56 assertions over 21 charts becomes 60, keycloak 4 -> 8 plus two
'not checked' lines; and why the overlay does not repeat the entries that
the install-test fixture already carries.

Charts: the Ingress cell for keycloak.

Lessons-Learned: 'The precise message that hid the structural one' - a
per-entry check that fired before the mode-conflict abort and answered the
wrong diagnosis, plus the note that a strict schema can make a template
guard unreachable.
2026-10-13 10:13:44 +00:00
jklein 5b416cf210 Chart-Style-Guide: group 3 of the Gateway-API sweep, and what it added to the convention
The sweep is finished bar rss-bridge, so the "remaining" paragraph now says
so - and carries a table of the three descriptions #114 gave of the group-3
shapes that turned out to be wrong, because two of them had already been
copied into README and into a CI script's header where they read as settled
fact. The general lesson is written next to it: a shape read off an issue's
summary table is a hypothesis, not a measurement.

Three new rules in the §4 table:

  - a chart without ingress.domain omits hostnames ENTIRELY and its schema
    rejects the key (freesailarr, and now homeassistant too);
  - several routing objects where the Ingress had several hosts or several
    rules with different backends, because spec.hostnames applies to the whole
    object - with the per-object parentRefs fallback as the counterpart of
    ingress.<x>.className falling back to ingress.className;
  - index-based object names where the count is data-driven, and the price:
    reordering the list recreates rather than edits.

Three new "no counterpart" entries: the LIVE-OBJECT screen (four of group 3's
six charts carried an operator annotation no git grep of this repo finds);
reading the directive before calling an annotation load-bearing
(more_set_headers is the output-header directive, so matrix-synapse's snippet
never reaches Synapse); and "no counterpart" being a claim to check against
the pinned CRDs, which for CORS is false - the filter exists, is Extended, and
is still not rendered, with the general rule that a chart renders a filter
only when it has values for it AND the support level makes the behaviour
promisable. Plus pathType ImplementationSpecific, which is a chart default in
homeassistant.

The ingress.enabled-derived screen is closed at three finds, and the entry now
makes the point group 3 taught: ask what the derived value PROTECTS. In
ytdlp-web-player the guard pre-empted a noisy webhook in Ingress mode and
became, in Gateway mode where the webhook cannot fire, the only thing between
a values file and an authentication bypass.

Also: a subchart does not come along (oauth2-proxy has no Gateway support),
and stalwart still rendering a Certificate in Gateway mode is correct.
2026-10-12 21:39:37 +00:00
jklein 353fb7d6de Gateway-API mode for group 2 of #114: the five annotation charts
PR #117. calibre-web-automated, outline, paperless-ngx, zipline (proxy-body-size)
and freesailarr (rewrite-target) each gain an httpRoute block, off by default.

Per chart page: a Routing row instead of the Ingress row, a Gateway-mode install
snippet, an httpRoute values table, and a "Routing: Ingress or Gateway API"
operating note. The upload-limit disclosure is written in each chart's own terms
rather than copied: an e-book library (calibre), an upload service (zipline), the
web-UI half while the consume directory keeps no limit (paperless), and the open
#12 pair (outline). freesailarr sets no body-size annotation and needs none, but
ingress.annotations is its operator's only lever for one.

outline: #12 now says what Gateway mode does to it. The ingress half of the pair
disappears, so the effective ceiling is the Gateway's and neither documented
number necessarily decides. That moves the defect; it does not fix it.

paperless-ngx: it was the last chart rendering its Ingress unconditionally and
now has ingress.enabled, default true. Recorded with the reason, because the
page previously stated the absence of that switch as a property.

freesailarr: the page gains the finding and the improvement. Its shared-host app
list was gated on ingress.enabled and also feeds the <APP>__SERVER__URLBASE
variables, so Gateway mode would have served radarr/sonarr/prowlarr under their
prefixes with no base path set - broken UI, Ready pod. And its rewrite-target
crutch is gone: a URLRewrite filter is per rule, so qBittorrent needs no second
routing object, no repeated TLS secret, no conditional cert-manager annotation
and no regex path.

Chart-Style-Guide §4: the "needs one, not sets one" screen, the rewrite-target
asymmetry as the one place this mode is less machinery, the ingress.enabled-first
rule for a chart without that switch, the freesailarr hostnames exception, and
the group status. §12.1 gains the two addenda the sweep produced.

CI-CD: the HTTPRoute test figure (4 over 2 at the pilot, 20 over 10 after group 1,
36 over 15 now), the multi-app overlay rule, and the honest note that the Gateway
branch of NOTES.txt is executed by that test but printed by no stage.

Lessons-Learned: the freesailarr case in full, next to sure's. Same helper shape,
different symptom - which is the point: ingress.enabled is almost never asked
"does an Ingress exist", and the property it stands in for does not flip with the
object.
2026-10-12 18:28:32 +00:00
jklein 63e18d4527 Gateway API as the second routing mode (pilot on cyberchef)
Documents Gitea #114's pilot, which is in helm-charts PR #115.

Chart-Style-Guide §4 becomes "Ingress and Gateway API" and carries the
binding form: HTTPRoute with parentRefs only - no Gateway, no
gatewayClassName, no certificate, no implementation-specific policy object,
because that subset is identical on nginx-gateway-fabric, Cilium, Envoy
Gateway and Istio. Plus the four guards (a value instead of auto-detection,
mutual exclusion with ingress.enabled, mandatory parentRefs, one source of
truth for the hostname) and what has no counterpart at all: proxy-body-size
and auth annotations. The §4 anchor changed, so the reference on the
homeassistant page moved with it.

CI-CD gains the httproute-match job as #5 (install-test renumbered to #7)
and a section on the Gateway-API CRD fixture - why it is real rather than a
stub, why --server-side (the CRD is 244792 bytes as JSON against a 262144
byte annotation limit), and why it is not in ci/common/.

cyberchef gains the httpRoute values, a "Routing modes" section, the
attachment check, and the honest version of its CI section: its install-test
now runs in Gateway mode, which gives up installing the Ingress and buys a
route validated against a real schema. What stays unprovable is named -
nothing routes in k3d.

template-chart carries the reference implementation; New-Chart gains the
overlay obligation; Renovate replaces the blanket "ci/** is not managed"
with the distinction that was always meant - a fixture image stays
unmanaged, a pinned tool is tracked wherever it lives.

Lessons-Learned: the stub CRD that would have proved nothing.
2026-10-12 15:23:46 +00:00
jklein aa25dced2b stalwart: per-group distribution and PDB, and the selector-match CI assurance
stalwart: the six new per-group values in the nodes table, a "High availability,
per group" section, and four traps - distribution is a promise and the price is
a Pending pod (including the delete-the-Node-object inversion and the ceiling as
a rule rather than a number), the opposite defaults of the two node-inclusion
policies, nodeAffinityPolicy being pruned silently below Kubernetes 1.26, and
the collision abort. Also: the install-test renders neither object, and the
render-only overlay that covers them instead.

CI-CD: the new selector-match job as step 4, install-test renumbered to 6, and
helm-dependencies.sh now in six places - with the 503 that took both render
tests red while this was written.

Chart-Style-Guide: §11 gains the per-group PDB, the distribution-rule
convention, the chart-filled labelSelector, the explicit nodeAffinityPolicy with
its two separately-argued consequences, the collision abort, and the reminder
not to add updateStrategy or podManagementPolicy for this.

Lessons-Learned: two sections - the two defaults that pointed in opposite
directions (where a review's own authoritative table was the thing that was
wrong), and the duplicate the API rejected while the apply path merged it
silently - plus four entries in the recurring shape.
2026-10-07 11:39:25 +00:00
jklein 0852b72ee2 keycloak: new chart page, index, sidebar, and three rules it bought
The chart page carries the values table, the vault fields, the first-install
checklist with the permanent administrator as a mandatory step, the
minor-upgrade procedure, and the traps - of which two are the point of the
page: hostname-admin is not an access control, and a shared database merges two
clusters.

Chart index counter: 30 directories / 29 installable. That follows the index
TABLE, which already lists convertx - its page landed before its PR, and
convertx/ does not exist on origin/main yet. So the number is one ahead of the
repository on purpose, as it was before this change.

Renovate: the counter-case to garage, measured rather than assumed - four tag
shapes on quay.io/keycloak/keycloak and plain docker versioning already excludes
the three unwanted ones, so that block carries no versioningTemplate. Written
down because an unnecessary constraint is not free: it reads as load-bearing to
the next person and has to stay correct as upstream's tagging changes. Plus a
section on the automerge decision: switching it off for minor and major was
proposed and declined, so the upgrade procedure is documented instead of
prevented, with the obligation that puts on whoever reviews an app-version PR.

Chart style guide section 11: a chart defaulting to more than one replica ships
a PodDisruptionBudget, and it is rendered only while the computed replica count
is >= 2 - minAvailable 1 with a single replica blocks every drain forever with a
message that names the PDB rather than the replica count.

CI/CD: the trusted-registry list for AVD-KSV-0125 is a separate file from the
ignore file on purpose, so a new registry is an entry there rather than a
suppression. quay.io joined it with this chart.

Lessons learned, two entries and three lines in the recurring shape:
- "The application that rewrites its own installation at start" - three correct
  requirements that cannot start a pod together, and why the obvious repair
  (an empty volume over the directory) breaks the command meant to fill it.
- "The upgrade procedure the automerge did not know about" - the general form,
  which applies to any clustered application whose versions must not be mixed:
  a constraint living in documentation is invisible to the machinery that
  performs upgrades, and the better the pipeline, the less likely anybody is
  standing in the path to notice.
2026-10-04 17:09:56 +00:00
jklein 43aef80b2e paperless-ngx: the optional valkey subchart, and two findings it produced
paperless-ngx (Gitea #78) takes our own valkey chart as an optional subchart,
off by default. Its page gets the two new value blocks, the five Redis
consumers that argue the queue-vs-cache choice, the three operator traps and
the CI section, which now describes the first real installation of the
valkey-subchart path anywhere here.

Two findings are general enough to leave the chart page:

- A rule about the relationship between a parent's and a subchart's values
  cannot live in a template. Helm renders a subchart's templates first, so the
  subchart's own required() always wins and reports the path as the subchart
  sees it. Schema validation runs before any rendering, so that is where such a
  rule goes. The first draft was a fail() in the parent that could never fire.
- Erasing a dependency switch turns the subchart ON: a null override deletes
  the key, an unresolvable condition counts as enabled. The one block in a
  chart that must not be null-safe, and the opposite of the rule for every
  other block.

Both in Chart-Style-Guide section 14 and Lessons-Learned; valkey's page names
its second consumer and the queue/cache pair of worked examples; Charts gets
the row.
2026-10-02 21:29:18 +00:00
jklein 1eb6831a58 stalwart: the subchart is our own valkey chart, from the published index
Follows the decision on #65 to depend on our published valkey chart rather
than upstream's. Four pages, and the choice of pages is deliberate.

stalwart.md: the subchart section rewritten - our chart, not upstream's, so
"consumed as it comes" no longer applies; the condition is
valkeySubchart.enabled with the subchart's own error message quoted and the
reason it only appears once the dependency is switched on; the auth-conf field
is a complete "requirepass <pw>" line, not a password; and the cache-vs-queue
choice argued with its cost (allkeys-lru makes the rate limiters and the
auto-ban fail open under memory pressure - size memory, do not switch to
noeviction). Plus a section on the PR-ordering rule.

valkey.md: the same rule, stated FROM THIS SIDE. This is the page someone
editing valkey/ will actually be reading, and this repo's own lesson is to put
the warning where the next change will be made. It also warns against the
tempting repair of adding an "enabled" property here for a key this chart does
not read.

Chart-Style-Guide.md, section 14: two charts carry a dependency now, and point
2 ("we consume their chart as it comes") explicitly does not apply when the
subchart is ours. Adds the PR-ordering rule, the condition-key trap, and the
two measurements that back the published-version form.

Lessons-Learned.md: two entries - the green check that tested the wrong
artifact, and the condition key the subchart rejected (including the part that
makes it dangerous: Helm skips a disabled subchart's validation, so CI stays
green and the error waits for the first operator who enables it). Three lines
in "The recurring shape".
2026-10-02 20:40:43 +00:00
jklein 72fdda3bbf satisfactory: server.storage stays the documented exception (#39)
Walks back the part of the previous commit that described storage as
null-safe. It is not: an erased size still renders a PVC without one and
fails at apply time, which is the decision values.schema.json has always
documented. Overturning it belonged in its own issue, #39, not in the
null-safety bugfix - so the chart page now marks it as an exception and
names where the trade is argued, including the part that is easy to miss:
the difference only matters on a new install, because an existing release
cannot shrink its PVC either.

The erased-key table stays and gains a "now" column, so the one key that did
not change is visible rather than implied.

Style guide 2 and the lessons entry lose the claim that the exception did
not hold and gain the rule that replaced it: an exception is allowed, an
unmarked one is not, and a bugfix that settles an open design question has
stopped being a bugfix.
2026-09-22 20:09:11 +00:00
jklein d19d0bd89f required() for the four mandatory OnePasswordItem references (#18)
Chart pages: calibre-web-automated 7.0.0, minecraft-server 6.0.0 and
urlaubsverwaltung 10.0.0 now guard their secret references, so the values
tables no longer say "not guarded by a required in the template". searxng
1.0.6 is a version snapshot only - its valkey reference was already correctly
optional and stays that way.

Style guide 12: the bullet on how null reaches the schema was wrong in the
case it was being applied to. A null chart default is NOT coalesced away; only
a user null over a non-null default is. Added the rule that follows from it -
a value with no valid default cannot be enforced by values-schema at all while
helm lint --strict has to pass on the defaults, so required() is the whole
enforcement and a required array would be a guard that cannot fire.

Lessons learned: both halves of that, since a safeguard that looks like one
and never fires is the same failure shape as the missing guard it replaced.
2026-09-22 20:06:08 +00:00
jklein 313ec47607 satisfactory: dev and server are null-safe as of 7.0.1
Chart page: version snapshot, the dev and server blocks documented as
erasable, a table of what a single nulled key used to render (empty Service
ports, a sizeless PVC), and why server.resources is the one key without its
own default there. CI section: the notes are rendered by no stage of this
chart's pipeline, and how to render them without a cluster.

Style guide 2: the rule is about the shape, not about probes, security
contexts and resources specifically - it covers every chart-owned block a
template dereferences, and a half-protected chart is worse than an
unprotected one.

Lessons learned: new entry for the half-protected chart and for NOTES.txt as
template code the pipeline is blind to.
2026-09-22 20:01:15 +00:00
jklein f62f2e5871 Fill the wiki as the repository's knowledge base
One page per chart - prerequisites, installation walkthrough, the required
1Password fields, the complete values reference and the chart's operating
traps - plus the long form of the workflow, the forge and its measured
differences, the pipelines, versioning, Renovate, the chart style guide, the
procedure for a new chart, local development, and the incidents each rule was
bought with.

Chart pages quote versions as snapshots; Chart.yaml stays authoritative,
because Renovate moves app versions continuously and never edits the wiki.
2026-09-18 08:48:01 +00:00