apiVersion: apps/v1 kind: Deployment metadata: name: {{ taskserv.name }} namespace: {{ taskserv.namespace }} labels: app.kubernetes.io/name: {{ taskserv.name }} app.kubernetes.io/managed-by: provisioning spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app.kubernetes.io/name: {{ taskserv.name }} template: metadata: labels: app.kubernetes.io/name: {{ taskserv.name }} app.kubernetes.io/managed-by: provisioning spec: securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 imagePullSecrets: - name: {{ taskserv.registry_secret }} {% if taskserv.data_pvc.enabled and taskserv.render_mode == "leptos_hydration" %} # (leptos_hydration only) Sync image-baked WASM/JS bundle into the PV so the runtime serves # artifacts version-matched to the server binary. Without this, the PV # keeps the previous rollout's pkg/ and produces server-fn hash drift # (WASM URL hash != binary registered hash → 400 from handle_server_fns). # The image ships pkg/ at /usr/local/share/{{ taskserv.name }}/pkg/ and # the PV mount shadows /var/www/site/pkg/, so we must copy each pod start. initContainers: - name: sync-pkg image: {{ taskserv.image }}:{{ taskserv.image_tag }} imagePullPolicy: IfNotPresent command: - sh - -c - | set -eu dest="{{ taskserv.data_pvc.mount_path }}/site/pkg" src="/usr/local/share/{{ taskserv.name }}/pkg" mkdir -p "$dest" cp -f "$src"/{{ taskserv.leptos_output_name }}.wasm "$dest"/{{ taskserv.leptos_output_name }}.wasm cp -f "$src"/{{ taskserv.leptos_output_name }}.js "$dest"/{{ taskserv.leptos_output_name }}.js cp -f "$src"/{{ taskserv.leptos_output_name }}.d.ts "$dest"/{{ taskserv.leptos_output_name }}.d.ts cp -f "$src"/{{ taskserv.leptos_output_name }}_bg.wasm.d.ts "$dest"/{{ taskserv.leptos_output_name }}_bg.wasm.d.ts echo "[sync-pkg] copied $(ls "$src" | wc -l) artifacts from $src to $dest" volumeMounts: - name: data mountPath: {{ taskserv.data_pvc.mount_path }} resources: requests: cpu: "10m" memory: "16Mi" limits: cpu: "100m" memory: "64Mi" {% endif %} containers: - name: {{ taskserv.name }} image: {{ taskserv.image }}:{{ taskserv.image_tag }} imagePullPolicy: IfNotPresent ports: - name: http containerPort: {{ taskserv.port }} protocol: TCP envFrom: - configMapRef: name: {{ taskserv.name }}-env - secretRef: name: {{ taskserv.name }}-credentials # Declared inline (not only via the ConfigMap) so `kubectl apply` owns the # container's env field and reconciles stale entries left by a past # `kubectl set env`. Inline env also takes precedence over envFrom. env: - name: SITE_SERVER_CONTENT_ROOT value: "{{ taskserv.data_pvc.mount_path }}/site/public/r" {% if taskserv.render_mode == "htmx_ssr" %} # htmx-templates is a sibling of the site/ tree (mirrors the source # project layout and run.sh's HTMX_TEMPLATE_PATH=htmx-templates, # relative to the project root == container WORKDIR /var/www), not a # child of site/. The engine builds its template environment from # this path at startup; pointing inside site/ yields an empty # environment and every render fails with "not found in environment". - name: HTMX_TEMPLATE_PATH value: "{{ taskserv.data_pvc.mount_path }}/htmx-templates" {% endif %} startupProbe: httpGet: path: /api/health port: {{ taskserv.port }} periodSeconds: 10 failureThreshold: 120 readinessProbe: httpGet: path: /api/health port: {{ taskserv.port }} initialDelaySeconds: 10 periodSeconds: 10 failureThreshold: 6 livenessProbe: httpGet: path: /api/health port: {{ taskserv.port }} initialDelaySeconds: 30 periodSeconds: 30 failureThreshold: 3 resources: requests: cpu: "50m" memory: "64Mi" limits: cpu: "500m" memory: "256Mi" {% if taskserv.data_pvc.enabled %} volumeMounts: - name: data mountPath: {{ taskserv.data_pvc.mount_path }} {% endif %} terminationGracePeriodSeconds: 30 {% if taskserv.data_pvc.enabled %} volumes: - name: data persistentVolumeClaim: claimName: {{ taskserv.name }}-data {% endif %}