Files
helm-charts/bentopdf/templates/bentopdf.deployment.yaml
jklein 8df1913c5c bentopdf: new chart for the PDF toolbox (1.0.0+up2.8.7)
BentoPDF computes entirely in the visitor's browser; the container only serves
static files through nginx. The chart is therefore close to cyberchef - same
nginx-unprivileged base, uid 101, port 8080, probes on "/" - with four
differences that shaped the design.

Runtime config.json. The one setting reachable at runtime is the tool list.
It is a static file in the middle of the web root, so it is mounted by
subPath: a directory mount would hide the whole application. subPath mounts
are never refreshed by the kubelet, which makes the checksum/config annotation
(#82) load-bearing rather than decorative. bentopdf.config is passed through
verbatim, like searxng's settings, so a key upstream adds tomorrow is a values
change and not a chart change.

Three env switches that would kill the pod. PORT, DISABLE_IPV6 and
ROBOTS_NOINDEX are implemented as "sed -i" against files in the image, run
under "set -e" in /docker-entrypoint.d/, and the base image's entrypoint
aborts when one of them fails. With readOnlyRootFilesystem: true the pod
therefore never starts instead of ignoring the switch - measured in k3d as
CrashLoopBackOff, exit 1, "sed: can't create temp file
'/etc/nginx/nginx.confXXXXXX': Read-only file system". None of the three is
offered as a first-class value; values.yaml names the failure picture and the
escape hatch (env block plus readOnlyRootFilesystem: false).

Writable paths were measured, not copied from the upstream chart.
/etc/nginx/tmp holds the pid file and /var/cache/nginx is where nginx creates
its temp directories at startup - without it the master aborts with
'mkdir() "/var/cache/nginx/client_temp" failed'. /var/run is deliberately not
mounted: the running pod proves it is unnecessary.

Everything else about BentoPDF is a build-time argument and out of reach for
any chart; values.yaml says so explicitly rather than leaving it to be
discovered. Two accepted consequences are named there too: some tools fetch
WASM modules from a public CDN at runtime, and the served pages carry a
canonical link to the vendor's site.

Fixes #174
2026-08-28 18:22:30 +02:00

121 lines
5.9 KiB
YAML

apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
spec:
replicas: {{ include "bentopdf.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.bentopdf.replicas) }}
selector:
matchLabels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
template:
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
annotations:
# Hash of the rendered ConfigMap: a changed config.json changes the pod
# template and thus rolls the pods. Mandatory here rather than nice to
# have - config.json is a subPath mount, which the kubelet never
# refreshes, so without this annotation a changed tool list would stay
# invisible until someone restarted the pods for an unrelated reason.
checksum/config: {{ include (print $.Template.BasePath "/bentopdf.configmap.yaml") . | sha256sum }}
spec:
automountServiceAccountToken: false
securityContext:
{{- include "bentopdf.podSecurityContext" .Values.bentopdf.securityContext | nindent 8 }}
containers:
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
# The "-simple" variant on purpose: it is built from the same
# Dockerfile as ghcr.io/alam00000/bentopdf, the difference being the
# SIMPLE_MODE build argument, which strips the marketing surface of
# the commercial site. That has no place on an internal instance.
image: "ghcr.io/alam00000/bentopdf-simple:{{ .Chart.AppVersion }}"
imagePullPolicy: IfNotPresent
{{- if .Values.bentopdf.env }}
env:
{{- range $value := .Values.bentopdf.env }}
{{- if $value.value }}
- name: {{ $value.name | quote }}
value: {{ tpl $value.value $ | quote }}
{{- else if $value.valueFrom }}
{{- if $value.valueFrom.secretKeyRef }}
- name: {{ $value.name | quote }}
valueFrom:
secretKeyRef:
name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }}
key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }}
{{- else if $value.valueFrom.configMapKeyRef }}
- name: {{ $value.name | quote }}
valueFrom:
configMapKeyRef:
name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }}
key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
securityContext:
{{- include "bentopdf.containerSecurityContext" .Values.bentopdf.securityContext | nindent 12 }}
volumeMounts:
- name: config
mountPath: /usr/share/nginx/html/config.json
subPath: config.json
readOnly: true
# The two writable paths below are what keeps
# readOnlyRootFilesystem: true possible. Both are image facts, not
# user configuration, so neither is a value - and both are here
# because a run without them failed, not because the upstream
# chart mounts them.
- name: nginx-tmp
# The nginx.conf puts its pid file at /etc/nginx/tmp/nginx.pid.
mountPath: /etc/nginx/tmp
- name: nginx-cache
# NOT optional: nginx creates its temp directories at startup and
# the image ships /var/cache/nginx without them. Missing this
# mount, the master process aborts before serving anything, with
# [emerg] mkdir() "/var/cache/nginx/client_temp" failed
# (30: Read-only file system)
mountPath: /var/cache/nginx
# /var/run is deliberately NOT mounted. The upstream chart mounts
# it, but this image keeps its pid file under /etc/nginx/tmp, and a
# run with only the two mounts above serves / and /config.json with
# 200. An emptyDir nobody writes to is a claim the chart cannot
# back up.
ports:
# 8080 is the image's EXPOSE and the port its nginx.conf listens
# on. It is not a value, because a value here would lie: the image
# switch that moves the port (PORT) rewrites nginx.conf with sed
# and therefore cannot run with a read-only root filesystem at all
# - see the comment in values.yaml. Moving the port means changing
# the chart, on purpose.
#
# The port NAME is load-bearing and must not be renamed: the
# Service selects it by name and so do all three probes.
- containerPort: 8080
protocol: TCP
name: http
# Probes, resources and both security contexts go through the
# null-safe helpers (#99): an erased block means "chart defaults
# apply" instead of a vanished probe or an unhardened container.
# Never wrap these in an "if" - a guard hides the erased case
# completely instead of rendering a visible null.
livenessProbe:
{{- include "bentopdf.livenessProbe" .Values.bentopdf.livenessProbe | nindent 12 }}
readinessProbe:
{{- include "bentopdf.readinessProbe" .Values.bentopdf.readinessProbe | nindent 12 }}
startupProbe:
{{- include "bentopdf.startupProbe" .Values.bentopdf.startupProbe | nindent 12 }}
resources:
{{- include "bentopdf.effectiveResources" .Values.bentopdf.resources | nindent 12 }}
volumes:
- name: config
configMap:
name: "{{ .Release.Name }}-{{ .Chart.Name }}-configmap"
- name: nginx-tmp
emptyDir: {}
- name: nginx-cache
emptyDir: {}
restartPolicy: Always