# 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 gotenberg: # Replica count for this workload; 0 is allowed and scales it down. # The chart-global scaleDown flag takes precedence. replicas: 1 # The official image runs as the dedicated gotenberg user (uid 1001). /tmp # and /home/gotenberg are writable emptyDirs so Chromium/LibreOffice keep # working with a read-only root filesystem. securityContext: pod: runAsNonRoot: true runAsUser: 1001 runAsGroup: 1001 # 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. # # Verified against both conversion engines, because a browser sandbox is # the classic case where a syscall filter bites: Chromium HTML->PDF and # LibreOffice document->PDF both still return real PDFs under the filter, # and /health reports chromium and libreoffice as "up". seccompProfile: type: RuntimeDefault container: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true capabilities: drop: - ALL # Gotenberg exposes a documented health endpoint at /health. livenessProbe: httpGet: path: /health port: gotenberg-port periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /health port: gotenberg-port periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 # The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s. startupProbe: httpGet: path: /health port: gotenberg-port periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 30 # Extra environment variables. Values are rendered through tpl; value, # secretKeyRef and configMapKeyRef forms are supported. env: [] 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 # The requests stay small on purpose: gotenberg idles at a few dozen MB, # so reserving conversion-sized capacity on the node would waste it # around the clock. Conversions are short bursts, and the limits below # cover the burst instead (issue #106). requests: cpu: 0.1 memory: 512Mi ephemeralStorage: 10Mi # Set explicitly rather than left to limitFactor x request, because the # computed limits (1536Mi / 30Mi) were too small for real documents. # Measured with the official image, HTML converted via Chromium: # - 18 MB HTML: OOMKilled at 2Gi and 2560Mi, converts at 3Gi in ~22s # (21 MB PDF). 12 MB HTML already OOM-kills at the old 1536Mi. # - Raising memory alone only moves the failure: with 3Gi memory and # the old 30Mi ephemeral limit, /tmp peaked at 40 MB on a single # conversion and at ~125 MB over a short series, and the pod was # evicted ("ephemeral local storage usage exceeds the total limit of # containers 30Mi") instead of OOM-killed. # Memory beyond 3Gi buys nothing: gotenberg's own request timeout is 30s # and the chart passes no flags to change it, so a document that needs # more memory than this has already run out of time. limits: memory: 3Gi ephemeralStorage: 256Mi # --------------------------------------------------------------------------- # NetworkPolicy (Gitea #5). On by default. The reference implementation with # the full reasoning is template-chart; the convention is in README, "Chart # style guidelines", and the operator's view on this chart's wiki page. # --------------------------------------------------------------------------- networkPolicy: # WHAT THIS POLICY ALLOWS: inbound 3000 only and outbound nothing but DNS. # # THE EGRESS CAP IS THE POINT OF THIS ONE. Gotenberg drives a headless # Chromium, so allowing 80/443 outbound would let anything that can POST to # this service fetch arbitrary URLs through it - and 3000 is the probe port, # so the whole cluster can POST to it by default. The cost is the # remote-asset half of HTML conversion: a converted page keeps its inline # content and silently loses images it would have fetched. That is a degraded # PDF, not a dead pod, which is exactly why it is written down here. A # deployment that needs remote assets sets egress.extraPorts: [80, 443] # knowingly. # # And like every chart here: 3000 is not restricted by source in the default # (probe port), and a policy is only a boundary where the CNI enforces one. 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 on the "kubernetes.io/metadata.name" label Kubernetes sets on every # namespace itself, so no labelling work is needed on a namespace this chart # does not own - and no typo protection either: a name that does not exist # matches nothing and is silently ineffective. Note the granularity: an entry # opens the port to EVERY pod in that namespace. allowedNamespaces: [] # Source CIDRs allowed to reach port 3000, which is the port all three probes address. # # EMPTY MEANS OPEN, NOT BLOCKED. With no entry that rule is rendered WITHOUT a # "from" selector, so the port is reachable from anywhere the CNI routes to - # inside the cluster, since it carries no NodePort. # # That is the deliberate choice between two bad defaults. The kubelet's probes # originate from the IP of the WORKER NODE running the pod, an address this # chart cannot know and must not guess: a CIDR that excludes the real node # 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. # # Narrow it by listing the worker node IPs (e.g. ["10.0.1.11/32"]) - and mind # the companion trap: once this list is non-empty the rule IS restricted, so # everything else that needs the port has to be in it. The chart adds the own # namespace and allowedNamespaces to the same rule for exactly that reason. probeSources: [] egress: # REMOTE ASSETS: lets Chromium fetch over TCP 80 and 443, to any # destination. OFF by default, and both halves of that are deliberate. # # WHAT IT BUYS, measured rather than assumed. paperless-ngx splits a mail # into two steps with two separate cluster-internal endpoints: the .eml goes # to TIKA for parsing (PAPERLESS_TIKA_ENDPOINT) and the resulting HTML comes # here for rendering (PAPERLESS_TIKA_GOTENBERG_ENDPOINT). Of the images in # that HTML: # - EMBEDDED images are MIME parts INSIDE the file. Neither Tika nor # Gotenberg needs the network for them - which is why apache-tika stays # DNS-only, and why a mail that carries its pictures with it needs # nothing from this switch. # - REMOTE images ( in the body) are fetched by # CHROMIUM, here, at render time. With this off they are SILENTLY # DROPPED: the PDF is produced and is simply missing them. Nothing # fails, nothing is logged by this chart, no probe notices. # # NOT CHECKED, and deliberately not claimed: whether paperless-ngx re-embeds # the images Tika extracted before handing the HTML over. If it does not, # embedded images would be lost as well - but that would be pre-existing # paperless behaviour and not this policy. Do not read this switch as a fix # for it. # # WHY IT IS OFF BY DEFAULT. Gotenberg drives a headless Chromium and 3000 is # its probe port, so the service is reachable from the whole cluster by # default. Allowing 80/443 outbound therefore turns anything that can POST # here into a way of fetching arbitrary URLs - an SSRF pivot with the # cluster's internal address space behind it. Switching this on is a trade, # and it should be one somebody made on purpose. remoteAssets: false # 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. # # Use remoteAssets above rather than putting 80 and 443 here: the named # value says WHY the ports are open, and this list cannot. # # PORTS, NOT DESTINATIONS, and that is a limit of the object rather than # laziness: a NetworkPolicy matches CIDRs and selectors and never hostnames, # so "only to the database host" cannot be written at all. Each entry # permits its port to ANY destination. What the cap still buys is real: a # compromised pod cannot open arbitrary outbound connections. # # [80, 443] is the entry to add if this release must fetch remote assets or # convert URLs - and see the SSRF note above before adding it. A # paperless-ngx that only converts mail bodies does not need it. extraPorts: []