--- apiVersion: v1 kind: Namespace metadata: name: stalwart --- apiVersion: v1 kind: Service metadata: name: stalwart-smtp namespace: stalwart spec: type: LoadBalancer externalTrafficPolicy: Cluster selector: app: stalwart ports: - name: smtp port: 25 targetPort: 25 protocol: TCP - name: submission port: 587 targetPort: 587 protocol: TCP # Port 465 (SMTPS) removed per DEV-359 approval - enforce secure protocols --- apiVersion: v1 kind: Service metadata: name: stalwart-imap namespace: stalwart spec: type: LoadBalancer externalTrafficPolicy: Cluster selector: app: stalwart ports: # Port 143 (IMAP) removed per DEV-359 approval - enforce secure protocols - name: imaps port: 993 targetPort: 993 protocol: TCP --- apiVersion: v1 kind: Service metadata: name: stalwart-http namespace: stalwart spec: type: ClusterIP selector: app: stalwart ports: - name: http port: 8080 targetPort: 8080 protocol: TCP --- apiVersion: apps/v1 kind: StatefulSet metadata: name: stalwart namespace: stalwart annotations: # Automatically restart this StatefulSet when the TLS certificate secret is updated # This ensures the pod reloads new certificates after cert-manager renews them secret.reloader.stakater.com/reload: "stalwart-tls" spec: serviceName: stalwart-http replicas: 1 selector: matchLabels: app: stalwart template: metadata: labels: app: stalwart spec: initContainers: # Copy ConfigMap files to emptyDir to avoid subPath mounting race - name: copy-config image: busybox:latest command: - sh - -c - | set -e echo "Copying bootstrap config..." cp /tmp/bootstrap-config/* /etc/stalwart/ echo "Copying stalwart.toml..." cp /tmp/stalwart-config/stalwart.toml /etc/stalwart/stalwart.toml echo "Config files copied successfully:" ls -la /etc/stalwart/ volumeMounts: - name: bootstrap-config-source mountPath: /tmp/bootstrap-config readOnly: true - name: stalwart-config-source mountPath: /tmp/stalwart-config readOnly: true - name: config mountPath: /etc/stalwart containers: - name: stalwart image: stalwartlabs/stalwart:v0.16.18 ports: - containerPort: 25 name: smtp - containerPort: 587 name: submission # Port 465 (SMTPS) removed - enforce STARTTLS on 587 # Port 143 (IMAP) removed - enforce TLS on 993 - containerPort: 993 name: imaps - containerPort: 8080 name: http env: #- name: STALWART_RECOVERY_ADMIN # valueFrom: # secretKeyRef: # name: stalwart-admin-credentials # key: admin-password # optional: false - name: TLS_CERTIFICATE value: "/etc/stalwart/certs/tls.crt" - name: TLS_PRIVATE_KEY value: "/etc/stalwart/certs/tls.key" # DEV-476: bootstrap config.json resolves authSecret via this env var. - name: PGPASSWORD valueFrom: secretKeyRef: name: stalwart-postgres-credentials key: POSTGRES_PASSWORD volumeMounts: - name: data mountPath: /var/lib/stalwart - name: config mountPath: /etc/stalwart - name: tls-certs mountPath: /etc/stalwart/certs readOnly: true # DEV-474: /var/lib/stalwart is no longer RocksDB storage (state now lives in # the stalwart-postgres StatefulSet). Kept as emptyDir so stalwart still has # a writable path if it needs one at runtime. resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "2Gi" cpu: "2000m" startupProbe: exec: command: - curl - -f - --max-time - "3" - http://localhost:8080/healthz/live initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6 livenessProbe: exec: command: - curl - -f - --max-time - "3" - http://localhost:8080/healthz/live initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: exec: command: - curl - -f - --max-time - "3" - http://localhost:8080/healthz/ready initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 volumes: # DEV-474: was persistentVolumeClaim.claimName=stalwart-data (RocksDB). # Postgres-backed now; replaced with emptyDir. - name: data emptyDir: {} # emptyDir for config files (populated by copy-config init-container) - name: config emptyDir: {} # ConfigMap sources for init-container (no longer mounted directly via subPath) - name: bootstrap-config-source configMap: name: stalwart-bootstrap-config - name: stalwart-config-source configMap: name: stalwart-config - name: tls-certs secret: secretName: stalwart-tls defaultMode: 0444 --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: stalwart-web namespace: stalwart annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" traefik.ingress.kubernetes.io/router.entrypoints: websecure spec: ingressClassName: traefik tls: - hosts: - mail.basicstack.de secretName: stalwart-tls rules: - host: mail.basicstack.de http: paths: - path: / pathType: Prefix backend: service: name: stalwart-http port: number: 8080