--- apiVersion: v1 kind: Namespace metadata: name: stalwart --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: stalwart-data namespace: stalwart spec: accessModes: - ReadWriteOnce storageClassName: hcloud-volumes-encrypted resources: requests: storage: 20Gi --- apiVersion: v1 kind: Service metadata: name: stalwart-smtp namespace: stalwart spec: type: LoadBalancer selector: app: stalwart ports: - name: smtp port: 25 targetPort: 25 protocol: TCP - name: submission port: 587 targetPort: 587 protocol: TCP - name: submissions port: 465 targetPort: 465 protocol: TCP --- apiVersion: v1 kind: Service metadata: name: stalwart-imap namespace: stalwart spec: type: LoadBalancer selector: app: stalwart ports: - name: imap port: 143 targetPort: 143 protocol: TCP - 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: - name: fix-permissions image: busybox:latest command: ["sh", "-c", "chown -R 2000:2000 /var/lib/stalwart && chmod -R 755 /var/lib/stalwart"] volumeMounts: - name: data mountPath: /var/lib/stalwart containers: - name: stalwart image: stalwartlabs/stalwart:v0.16.11 ports: - containerPort: 25 name: smtp - containerPort: 587 name: submission - containerPort: 465 name: submissions - containerPort: 143 name: imap - 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" volumeMounts: - name: data mountPath: /var/lib/stalwart - name: bootstrap-config mountPath: /etc/stalwart/config.json subPath: config.json - name: tls-certs mountPath: /etc/stalwart/certs readOnly: true resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "2Gi" cpu: "2000m" startupProbe: httpGet: path: / port: 8080 initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6 livenessProbe: httpGet: path: / port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: / port: 8080 initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 volumes: - name: data persistentVolumeClaim: claimName: stalwart-data - name: bootstrap-config configMap: name: stalwart-bootstrap-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 --- apiVersion: batch/v1 kind: CronJob metadata: name: stalwart-backup namespace: stalwart spec: schedule: "0 3 * * *" # 3 AM daily successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: spec: template: metadata: labels: app: stalwart-backup spec: restartPolicy: OnFailure containers: - name: backup image: restic/restic:latest command: - /bin/sh - -c - | set -e echo "Starting Stalwart backup at $(date)" # Build restic repository URL from env vars (K8s doesn't expand $(VAR) in value fields) export RESTIC_REPOSITORY="s3:${S3_ENDPOINT}/${S3_BUCKET}/stalwart" echo "Using repository: $RESTIC_REPOSITORY" # Initialize restic repo if needed restic snapshots || restic init # Backup the data directory restic backup /var/lib/stalwart \ --tag stalwart \ --tag daily \ --host stalwart-k8s # Prune old backups (keep 7 daily, 4 weekly, 6 monthly) restic forget \ --keep-daily 7 \ --keep-weekly 4 \ --keep-monthly 6 \ --prune echo "Backup completed successfully at $(date)" env: - name: RESTIC_PASSWORD valueFrom: secretKeyRef: name: stalwart-s3-backup key: restic-password - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: stalwart-s3-backup key: access-key - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: stalwart-s3-backup key: secret-key - name: S3_ENDPOINT valueFrom: secretKeyRef: name: stalwart-s3-backup key: endpoint - name: S3_BUCKET valueFrom: secretKeyRef: name: stalwart-s3-backup key: bucket volumeMounts: - name: data mountPath: /var/lib/stalwart readOnly: true volumes: - name: data persistentVolumeClaim: claimName: stalwart-data