# Scale the whole chart down without uninstalling it: scaleDown: true renders # every Deployment/StatefulSet with replicas: 0 (and suspends CronJobs) and # takes precedence over any per-workload replicas value. PVCs, Secrets and # Services stay in place; scaleDown: false brings the workloads back up. scaleDown: false secrets: xxxSecretRef: null xxx: # Replica count for this workload; 0 is allowed and scales it down. # The chart-global scaleDown flag takes precedence. replicas: 1 resources: # Limits are optional: memory and ephemeral-storage limits default to # limitFactor x the request, an explicitly set limit always wins, and a # CPU limit is only set when configured explicitly under "limits". limitFactor: 3 requests: cpu: 0.1 memory: 512Mi ephemeral-storage: 10Mi livenessProbe: httpGet: path: / port: http periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: / port: http periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 # The startup probe gates liveness/readiness, so those need no initialDelaySeconds. # Startup budget = periodSeconds * failureThreshold = 150s. startupProbe: httpGet: path: / port: http periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 30 securityContext: pod: runAsNonRoot: true runAsUser: 20000 runAsGroup: 1000 fsGroup: 1000 fsGroupChangePolicy: Always # RuntimeDefault applies the container runtime's default seccomp filter, # which blocks the syscalls a normal workload never makes. It sits in the # pod context on purpose: from here it covers every container in the pod # including init containers, while a container-level seccompProfile would # have to be repeated for each one (and silently missed on the next one # added). A container that genuinely needs a different filter can still # override it under securityContext.container, which wins over this value. seccompProfile: type: RuntimeDefault container: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true capabilities: drop: - ALL add: [] ports: # An unprivileged port on purpose (AVD-KSV-0117 flags containerPort < 1024). # # Note what the reason is NOT: on current Kubernetes a non-root container # can bind port 80 just fine. The kubelet sets # net.ipv4.ip_unprivileged_port_start=0 in the pod network namespace, so # the classic "ports below 1024 need NET_BIND_SERVICE" rule does not apply # inside a pod. Measured in k3d (k3s v1.35.5): the sysctl reads 0, and a # container running as runAsUser 20000 with capabilities.drop: [ALL] binds # port 80 successfully. Anyone who "fixes" this back to 80 after testing it # once will find that it works - which is exactly why the reason is written # down here. # # The reason to stay above 1024 is that this is a runtime default, not a # guarantee: a cluster that restores ip_unprivileged_port_start=1024 (node # sysctl or a hardened distribution) breaks every workload that copied the # 80, and the failure then appears in the copied chart, not here. Every # real chart in this repo listens above 1024 (3000, 8080, 8123, 9998, ...); # the one exception is samba, which needs 139/445 and adds NET_BIND_SERVICE # with a documented justification. # # containerPort is declarative - it does not force the application onto the # port. Point the application at the same port when copying this template. - containerPort: 8080 protocol: TCP name: http env: # Reference for guideline 1: ingress.domain is read HERE, outside the # Ingress template, so it needs its own required(). Without it, switching # the Ingress off via ingress.enabled would render an empty value and the # workload would start with a broken address - silently. Copy this shape # whenever a chart uses the domain for anything but the Ingress host. - name: NORMAL_ENV value: '{{ required "A Domain/Host must be provided (ingress.domain)" .Values.ingress.domain }}' - name: ENV_FROM_SECRET valueFrom: secretKeyRef: name: "{{ .Release.Name }}-{{ .Chart.Name }}-secret" key: "secretKey" - name: ENV_FROM_CONFIGMAP valueFrom: configMapKeyRef: name: "{{ .Release.Name }}-{{ .Chart.Name }}-config-map" key: "configMapKey" storage: amount: 10Gi storageClass: null ingress: # Gates the whole Ingress template. Note that ingress.domain is ALSO read by # the env block above AND by the HTTPRoute below, so it stays required even # with the Ingress switched off; only clusterIssuer becomes unnecessary. # Check this per chart instead of assuming either way (guideline 1). # # Mutually exclusive with httpRoute.enabled below: with both true the render # aborts, naming both values. enabled: true clusterIssuer: null domain: null className: nginx # Extra annotations for the Ingress; merged with the cert-manager # cluster-issuer annotation the chart always sets. # # NO COUNTERPART IN GATEWAY MODE. The Gateway API has no annotation channel # for controller behaviour, so everything set here is gone when # httpRoute.enabled is used instead. rewrite-target has an equivalent (the # URLRewrite filter in an HTTPRoute rule); an upload limit # (proxy-body-size) has none at all, and a chart that depends on one must say # so in its NOTES.txt for that mode. annotations: {} # Gateway-API alternative to the Ingress above (Gitea #114). Off by default, so # adding it to an existing chart is additive; exactly one of ingress.enabled # and httpRoute.enabled may be true. # # The chart renders ONLY an HTTPRoute with parentRefs - no Gateway, no # gatewayClassName, no Certificate and no implementation-specific policy # object. Class and certificate belong to the Gateway and therefore to the # platform, which is what keeps a chart independent of whether the cluster runs # nginx-gateway-fabric, Cilium, Envoy Gateway or Istio. # # The Gateway-API CRDs must be installed in the cluster: the chart does not and # cannot detect them (.Capabilities.APIVersions is unreliable at template # time), so with httpRoute.enabled: true and no CRDs the INSTALL fails. httpRoute: enabled: false # The Gateway(s) this route attaches to - a platform object, usually in # another namespace, which must allow this namespace in its listener's # allowedRoutes. One entry per parent; the fields are the Gateway API's # ParentReference (name, and optionally namespace, sectionName, port, group, # kind), passed through unchanged: # # parentRefs: # - name: public # namespace: gateway # sectionName: https # # REQUIRED while httpRoute.enabled is true - the render aborts on an empty # list, because a route without a parent is created, is valid and routes # nothing, with no event to say so. parentRefs: [] # Empty means [ingress.domain], so the hostname has one source of truth. Set # explicitly only to publish under a different or an additional name - which # is how a migration from the Ingress runs: the HTTPRoute takes a second # hostname while the Ingress keeps ingress.domain. hostnames: [] # --------------------------------------------------------------------------- # NetworkPolicy (Gitea #5) — the reference block. Copy it with the template. # --------------------------------------------------------------------------- networkPolicy: # ON BY DEFAULT, which is the decision this whole rollout rests on: a # NetworkPolicy that ships switched off is reached only when somebody # remembers to switch it on, and then the issue is closed while nothing is # filtered. # # READ THIS BEFORE RELYING ON IT: a NetworkPolicy is only a boundary if the # cluster's CNI enforces policies. Where it does not, the API server still # accepts the object and everything looks configured while nothing is # filtered - an unenforced policy is a false sense of security, not a weak # one. Verify on the cluster (try to connect from an unrelated namespace) # rather than reading it off this value. # # WHAT IS ACTUALLY CAPPED, and what is not, is written out in the template's # header - read it once before assuming this value means "the workload is # isolated". In short: only the declared ports are reachable, egress is # capped by PORT (never by host - the object cannot express a hostname), and # the probed port stays open inside the cluster until probeSources is set. enabled: true # Namespaces whose pods may reach this workload, in addition to the release's # own namespace - which is always allowed. Empty means: own namespace only. # # Matched by the "kubernetes.io/metadata.name" label, which Kubernetes sets on # every namespace itself (>= 1.21), so no labelling work is needed on a # namespace this chart does not own - but also no typo protection: a # namespace name that does not exist matches nothing and is silently # ineffective. # # NOTE THE GRANULARITY: an entry here opens the port to EVERY pod in that # namespace. For the ingress controller use ingressController below instead, # which pins the source down to the controller's pods. allowedNamespaces: [] # Source CIDRs allowed to reach the port the probes address. This is the # single most dangerous value in this block, in both directions, so it is # spelled out in full. # # EMPTY MEANS OPEN, NOT BLOCKED. With no entry the rule for that port is # rendered WITHOUT a "from" selector, so the port is reachable from anywhere # the CNI routes to - which, since this port carries no NodePort, means from # inside the cluster and not from the internet. # # That is the deliberate choice between two bad defaults. The kubelet's # liveness/readiness/startup probes originate from the IP of the WORKER NODE # running the pod, an address this chart cannot know and must not guess: a # guessed CIDR that excludes the real node addresses blocks every probe, the # pod never becomes Ready, the rollout times out, and the symptom reads as a # broken application rather than as a policy. Shipping "open" instead is # honest about what it does. # # Narrow it by listing the worker node IPs (e.g. ["10.0.1.11/32", # "10.0.1.12/32"]) — and mind the companion trap: once this list is # non-empty the rule IS restricted, which also cuts off everything else that # reaches this port. The chart adds the release's own namespace, the # ingressController below and allowedNamespaces to the same rule for exactly # that reason. # # A probe that is an "exec" probe is NOT affected by any of this: it runs # inside the container through the kubelet's runtime and puts no packet on # the network, which is why a chart with exec-only probes (valkey) can # restrict its port by source with no probeSources at all. probeSources: [] # The ingress controller allowed to reach the routed port once probeSources # restricts it. Rendered ONLY while ingress.enabled or httpRoute.enabled is # true, and only inside the restricted branch - with the default (open) rule # the controller reaches the port anyway and naming it would be noise. # # The two keys are rendered as ONE "from" entry, which makes them an AND: # pods carrying podLabels IN namespace. That is the point of having them # here at all - putting the controller's namespace into allowedNamespaces # instead would open the port to every pod in kube-system. # # THE DEFAULT IS MEASURED ON THIS CLUSTER, NOT ASSUMED (2026-10-14): # ingress-nginx is gone (no DaemonSet, no pods), the only IngressClass is # "traefik" and it is the default, and the controller is the DaemonSet # rke2-traefik in kube-system with the pod label # app.kubernetes.io/name=rke2-traefik. The same Traefik also serves the # Gateway API here (GatewayClass "traefik", controller # traefik.io/gateway-controller, no pods of its own in the Gateway's # namespace), so this one source covers BOTH the Ingress and the HTTPRoute # path. # # A policy written against the WRONG controller is the expensive mistake in # this block: it is accepted, it looks strict, and it silently blocks all # web traffic. Check the cluster before changing these two keys, and note # that ingress.className says nothing about them - the class names the # controller's implementation, not the pods its traffic comes from. ingressController: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and # has no switch - a knob whose only other setting breaks every name lookup # in the pod is a trap, not a feature. # # PORTS, NOT DESTINATIONS, and that is a limit of the object rather than # laziness: a NetworkPolicy matches ipBlock CIDRs and pod/namespace # selectors, never hostnames, so "only to the database host" cannot be # written at all. Each entry here permits its port to ANY destination. # What the cap still buys is real: a compromised pod cannot open arbitrary # outbound connections. # # Add what the deployment actually needs: 443 for an outbound HTTPS call # (an OIDC provider's discovery document, an S3 endpoint, an app that # fetches), 5432 for Postgres, 3306 for MariaDB, 6379 for Valkey/Redis, # 587 or 465 for SMTP submission. A chart that KNOWS its outbound port # from its own values should derive it in the template instead of asking # for it twice (stalwart does this with dataStore.postgres.port). extraPorts: []