# docker-mailserver - Postfix + Dovecot + rspamd # ArgoCD managed - BlueJay Lab # Credentials: 1Password → OnePasswordItem CRD → K8s Secret --- apiVersion: v1 kind: Namespace metadata: name: mail labels: app.kubernetes.io/part-of: bluejay-infra --- # 1Password → K8s Secret sync for mail credentials apiVersion: onepassword.com/v1 kind: OnePasswordItem metadata: name: mail-credentials namespace: mail spec: itemPath: "vaults/IAmWorkin/items/Mail Postmaster" --- # Mail data PVC apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mail-data namespace: mail spec: accessModes: [ReadWriteOnce] resources: requests: storage: 5Gi --- # Mail state PVC apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mail-state namespace: mail spec: accessModes: [ReadWriteOnce] resources: requests: storage: 1Gi --- # docker-mailserver Deployment apiVersion: apps/v1 kind: Deployment metadata: name: mailserver namespace: mail labels: app: mailserver spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: mailserver template: metadata: labels: app: mailserver spec: hostname: mail initContainers: - name: inject-accounts image: busybox:1.36 command: - sh - -c - | ADMIN_EMAIL=$(cat /credentials/Admin-Email) ADMIN_HASH=$(cat /credentials/Admin-Hash) NOREPLY_EMAIL=$(cat /credentials/Noreply-Email) NOREPLY_HASH=$(cat /credentials/Noreply-Hash) echo "${ADMIN_EMAIL}|${ADMIN_HASH}" > /accounts/postfix-accounts.cf echo "${NOREPLY_EMAIL}|${NOREPLY_HASH}" >> /accounts/postfix-accounts.cf volumeMounts: - name: mail-credentials mountPath: /credentials readOnly: true - name: mail-accounts-generated mountPath: /accounts containers: - name: mailserver image: docker.io/mailserver/docker-mailserver:latest ports: - containerPort: 25 name: smtp - containerPort: 465 name: smtps - containerPort: 587 name: submission - containerPort: 143 name: imap - containerPort: 993 name: imaps env: - name: ENABLE_SPAMASSASSIN value: "1" - name: ENABLE_CLAMAV value: "0" - name: ENABLE_RSPAMD value: "1" - name: TZ value: America/Chicago - name: POSTMASTER_ADDRESS value: postmaster@iamwork.in - name: OVERRIDE_HOSTNAME value: mail.iamwork.in - name: ENABLE_FAIL2BAN value: "0" - name: ENABLE_POSTGREY value: "0" - name: ONE_DIR value: "1" - name: PERMIT_DOCKER value: network - name: SSL_TYPE value: manual - name: SSL_CERT_PATH value: /etc/ssl/mail/tls.crt - name: SSL_KEY_PATH value: /etc/ssl/mail/tls.key - name: ACCOUNT_PROVISIONER value: FILE volumeMounts: - name: mail-data mountPath: /var/mail - name: mail-state mountPath: /var/mail-state - name: mail-tls mountPath: /etc/ssl/mail readOnly: true - name: mail-accounts-generated mountPath: /tmp/docker-mailserver/postfix-accounts.cf subPath: postfix-accounts.cf readOnly: true resources: requests: memory: 512Mi cpu: 200m limits: memory: 2Gi cpu: "1" securityContext: capabilities: add: - NET_ADMIN - SYS_PTRACE volumes: - name: mail-data persistentVolumeClaim: claimName: mail-data - name: mail-state persistentVolumeClaim: claimName: mail-state - name: mail-tls secret: secretName: mail-tls - name: mail-credentials secret: secretName: mail-credentials - name: mail-accounts-generated emptyDir: {} --- # SMTP LoadBalancer Service (external) apiVersion: v1 kind: Service metadata: name: mail-smtp namespace: mail annotations: metallb.universe.tf/loadBalancerIPs: 10.0.56.202 spec: type: LoadBalancer selector: app: mailserver ports: - port: 25 targetPort: 25 name: smtp protocol: TCP - port: 465 targetPort: 465 name: smtps protocol: TCP - port: 587 targetPort: 587 name: submission protocol: TCP --- # IMAP ClusterIP Service (internal) apiVersion: v1 kind: Service metadata: name: mail-imap namespace: mail spec: selector: app: mailserver ports: - port: 143 targetPort: 143 name: imap - port: 993 targetPort: 993 name: imaps # --- mail-tls Certificate REMOVED 2026-06-01 --- # mail-tls is now managed OUTSIDE cert-manager: issued from step-ca's JWK 'admin' # provisioner and auto-renewed by a systemd timer on noc1 (step ca renew), which # writes the mail-tls secret directly. step-ca-acme only has an HTTP-01 (Traefik) # solver, but mail.iamworkin.lan must resolve to the dedicated MetalLB IP 10.0.56.202 # (SMTP/IMAP), so HTTP-01 cannot validate. Do NOT re-add a cert-manager Certificate # here unless a DNS-01 solver is deployed for step-ca-acme. --- # Traefik IngressRoute - Webmail placeholder apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: mail-webmail namespace: mail spec: entryPoints: - websecure routes: - match: Host(`mail.iamworkin.lan`) kind: Rule services: - name: mail-imap port: 993 tls: secretName: mail-tls --- # Public IngressRoute - Webmail (flowercore.io with Cloudflare origin cert) apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: mail-webmail-public namespace: mail spec: entryPoints: - websecure routes: - match: Host(`webmail.flowercore.io`) kind: Rule services: - name: mail-webmail port: 8080 tls: secretName: cf-origin-flowercore-io