From 02c8abb67b48ce048ca8c79ba6c92d245542ec6b81c9126dfc2a6aa43313ff32 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 26 Apr 2026 22:53:56 -0400 Subject: [PATCH] feat(helm): app Deployment (nginx + php-fpm sidecar) Two-container pod sharing source via emptyDir populated by init container. Nginx vhost in a separate ConfigMap. OAuth keys mounted from the chart Secret as files under /var/www/html/secrets/, copied into storage/ by the prod entrypoint. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../templates/configmap-nginx.yaml | 48 +++++++++ .../templates/deployment-app.yaml | 100 ++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 helm/ezscale-website/templates/configmap-nginx.yaml create mode 100644 helm/ezscale-website/templates/deployment-app.yaml diff --git a/helm/ezscale-website/templates/configmap-nginx.yaml b/helm/ezscale-website/templates/configmap-nginx.yaml new file mode 100644 index 0000000..84f7ce3 --- /dev/null +++ b/helm/ezscale-website/templates/configmap-nginx.yaml @@ -0,0 +1,48 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "ezscale-website.fullname" . }}-nginx + labels: {{- include "ezscale-website.labels" . | nindent 4 }} +data: + default.conf: | + server { + listen 80 default_server; + server_name _; + root /var/www/html/public; + index index.php index.html; + + client_max_body_size 50M; + charset utf-8; + + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + fastcgi_param HTTP_PROXY ""; + fastcgi_param HTTPS $http_x_forwarded_proto; + fastcgi_buffers 16 16k; + fastcgi_buffer_size 32k; + fastcgi_read_timeout 300; + } + + location ~ /\.(?!well-known).* { + deny all; + access_log off; + log_not_found off; + } + } diff --git a/helm/ezscale-website/templates/deployment-app.yaml b/helm/ezscale-website/templates/deployment-app.yaml new file mode 100644 index 0000000..a31cdd9 --- /dev/null +++ b/helm/ezscale-website/templates/deployment-app.yaml @@ -0,0 +1,100 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ezscale-website.fullname" . }}-app + labels: + {{- include "ezscale-website.labels" . | nindent 4 }} + app.kubernetes.io/component: app +spec: + {{- if not .Values.app.autoscaling.enabled }} + replicas: {{ .Values.app.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "ezscale-website.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: app + template: + metadata: + labels: + {{- include "ezscale-website.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: app + annotations: + # Restart pods when env or nginx config changes + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/nginx: {{ include (print $.Template.BasePath "/configmap-nginx.yaml") . | sha256sum }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + - name: copy-source + image: {{ include "ezscale-website.image" (dict "ctx" . "role" "app") }} + command: + - sh + - -c + - | + cp -a /var/www/html/. /shared/ + volumeMounts: + - name: shared + mountPath: /shared + containers: + - name: nginx + image: nginx:1.30-alpine + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: shared + mountPath: /var/www/html + readOnly: true + - name: nginx-config + mountPath: /etc/nginx/conf.d + readOnly: true + livenessProbe: + httpGet: + path: {{ .Values.healthCheck.livenessPath }} + port: http + initialDelaySeconds: {{ .Values.healthCheck.initialDelaySeconds }} + periodSeconds: {{ .Values.healthCheck.periodSeconds }} + timeoutSeconds: {{ .Values.healthCheck.timeoutSeconds }} + failureThreshold: {{ .Values.healthCheck.failureThreshold }} + readinessProbe: + httpGet: + path: {{ .Values.healthCheck.readinessPath }} + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + - name: app + image: {{ include "ezscale-website.image" (dict "ctx" . "role" "app") }} + ports: + - name: php-fpm + containerPort: 9000 + envFrom: + - configMapRef: + name: {{ include "ezscale-website.fullname" . }}-env + - secretRef: + name: {{ include "ezscale-website.secretName" . }} + volumeMounts: + - name: shared + mountPath: /var/www/html + - name: oauth-keys + mountPath: /var/www/html/secrets + readOnly: true + resources: + {{- toYaml .Values.app.resources | nindent 12 }} + volumes: + - name: shared + emptyDir: {} + - name: nginx-config + configMap: + name: {{ include "ezscale-website.fullname" . }}-nginx + - name: oauth-keys + secret: + secretName: {{ include "ezscale-website.secretName" . }} + items: + - key: oauth-private.key + path: oauth-private.key + - key: oauth-public.key + path: oauth-public.key + optional: true