apiVersion: apps/v1 kind: Deployment metadata: name: api namespace: honeydue labels: app.kubernetes.io/name: api app.kubernetes.io/part-of: honeydue spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 selector: matchLabels: app.kubernetes.io/name: api template: metadata: labels: app.kubernetes.io/name: api app.kubernetes.io/part-of: honeydue spec: serviceAccountName: api # Explicit pod-level opt-out (audit F11) — defense-in-depth on top of # the ServiceAccount-level setting in rbac.yaml. automountServiceAccountToken: false imagePullSecrets: - name: gitea-credentials securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 seccompProfile: type: RuntimeDefault containers: - name: api image: IMAGE_PLACEHOLDER # Replaced by 03-deploy.sh imagePullPolicy: IfNotPresent # audit CODE-L4 — explicit; images are SHA/digest-pinned ports: - containerPort: 8000 protocol: TCP securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] envFrom: - configMapRef: name: honeydue-config # Audit CODE-F8: secrets are NOT injected as environment variables. # Env vars are readable for the life of the pod via /proc//environ # and leak into crash dumps / child processes. honeydue-secrets is # mounted read-only at /etc/honeydue/secrets (mode 0400) and the Go # config layer (config.loadFileSecrets) reads each key from its file. # Non-secret config still arrives via the configMapRef above. volumeMounts: - name: app-secrets mountPath: /etc/honeydue/secrets readOnly: true - name: apns-key mountPath: /secrets/apns readOnly: true - name: tmp mountPath: /tmp resources: requests: cpu: 100m memory: 128Mi limits: cpu: "1" memory: 512Mi startupProbe: httpGet: path: /api/health/ port: 8000 # Schema migrations run separately in the honeydue-migrate Job # *before* this Deployment rolls — the api itself does not migrate # (it only verifies goose_db_version at boot). Cold start still # pays the DB pool warm-up + Redis connect + APNs/FCM client init # before /api/health/ goes green. 48 × 5s = 240s grace keeps the # probe from killing a still-starting replica. failureThreshold: 48 periodSeconds: 5 readinessProbe: httpGet: path: /api/health/ port: 8000 initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 5 livenessProbe: httpGet: path: /api/health/ port: 8000 initialDelaySeconds: 30 periodSeconds: 30 timeoutSeconds: 10 volumes: # Audit CODE-F8: the whole honeydue-secrets Secret, projected as files. # defaultMode 0400 → readable only by the container's runAsUser (1000). - name: app-secrets secret: secretName: honeydue-secrets defaultMode: 0400 - name: apns-key secret: secretName: honeydue-apns-key items: - key: apns_auth_key.p8 path: apns_auth_key.p8 - name: tmp emptyDir: sizeLimit: 64Mi