Add new infra architecture 2

This commit is contained in:
Urtzi Alfaro
2026-01-19 12:12:19 +01:00
parent 35f164f0cd
commit 8461226a97
11 changed files with 737 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: bakery-ia
resources:
- mailu-configmap.yaml
- mailu-secrets.yaml
- mailu-pvc.yaml
- mailu-deployment.yaml
- mailu-services.yaml
- mailu-antispam.yaml
- mailu-networkpolicy.yaml
- mailu-nginx-config.yaml
labels:
- includeSelectors: true
pairs:
app: mailu
platform: mail
managed-by: kustomize

View File

@@ -0,0 +1,48 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-antispam
namespace: bakery-ia
labels:
app: mailu
component: antispam
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: antispam
template:
metadata:
labels:
app: mailu
component: antispam
spec:
containers:
- name: antispam
image: ghcr.io/mailu/rspamd:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 11333
name: rspamd
- containerPort: 11334
name: rspamd-admin
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data

View File

@@ -0,0 +1,79 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mailu-config
namespace: bakery-ia
labels:
app: mailu
component: config
data:
# Domain configuration
DOMAIN: "DOMAIN_PLACEHOLDER"
HOSTNAMES: "mail.DOMAIN_PLACEHOLDER"
POSTMASTER: "admin"
# Kubernetes-specific settings
# These help Mailu components discover each other in K8s
FRONT_ADDRESS: "mailu-front.bakery-ia.svc.cluster.local"
ADMIN_ADDRESS: "mailu-admin.bakery-ia.svc.cluster.local"
SMTP_ADDRESS: "mailu-smtp.bakery-ia.svc.cluster.local"
IMAP_ADDRESS: "mailu-imap.bakery-ia.svc.cluster.local"
ANTISPAM_ADDRESS: "mailu-antispam.bakery-ia.svc.cluster.local"
# Redis Configuration - Using shared cluster Redis (database 15 reserved for Mailu)
# The shared Redis has 16 databases (0-15), Mailu uses db 15 for isolation
# Using plain TCP port 6380 for internal cluster communication (TLS on 6379 for external)
# Primary configuration: Redis URL is configured in mailu-secrets.yaml as REDIS_URL
# Format: redis://:password@host:port/db
# Fallback configuration: REDIS_ADDRESS, REDIS_DB, and REDIS_PW
REDIS_ADDRESS: "redis-service.bakery-ia.svc.cluster.local:6380"
REDIS_DB: "15"
# REDIS_PW is set from secrets for Redis authentication
# External SMTP Relay Configuration
# Mailu relays outbound emails through an external service for better deliverability
# Supported providers: Mailgun, SendGrid, AWS SES, Postmark
#
# Provider RELAYHOST examples:
# Mailgun: [smtp.mailgun.org]:587
# SendGrid: [smtp.sendgrid.net]:587
# AWS SES: [email-smtp.us-east-1.amazonaws.com]:587
# Postmark: [smtp.postmarkapp.com]:587
#
# IMPORTANT: Update RELAY_PASSWORD in mailu-secrets.yaml with your provider's API key
RELAYHOST: "[smtp.mailgun.org]:587"
RELAY_LOGIN: "postmaster@DOMAIN_PLACEHOLDER"
# Security settings
TLS_FLAVOR: "cert"
AUTH_RATELIMIT_IP: "60/hour"
AUTH_RATELIMIT_USER: "100/day"
# Message limits
MESSAGE_SIZE_LIMIT: "52428800" # 50MB
MESSAGE_RATELIMIT: "200/day"
# Features - disable ClamAV in dev to save resources (enable in prod)
WEBMAIL: "roundcube"
ANTIVIRUS: "none"
ANTISPAM: "rspamd"
# Postfix configuration
POSTFIX_MESSAGE_SIZE_LIMIT: "52428800"
POSTFIX_QUEUE_MINIMUM: "1"
POSTFIX_QUEUE_LIFETIME: "7d"
# DKIM configuration
DKIM_SELECTOR: "mailu"
DKIM_KEY_LENGTH: "2048"
# Webmail settings
WEB_WEBMAIL: "/webmail"
WEB_ADMIN: "/admin"
WEBMAIL_ADMIN: "admin@DOMAIN_PLACEHOLDER"
# Logging
LOG_LEVEL: "INFO"
# Disable welcome email during development
WELCOME: "false"

View File

@@ -0,0 +1,218 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-front
namespace: bakery-ia
labels:
app: mailu
component: front
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: front
template:
metadata:
labels:
app: mailu
component: front
spec:
containers:
- name: front
image: ghcr.io/mailu/nginx:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
- containerPort: 443
name: https
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
- name: mailu-tls
mountPath: /certs
readOnly: true
- name: nginx-config
mountPath: /overrides/ingress-fix.conf
subPath: ingress-fix.conf
readOnly: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
- name: mailu-tls
secret:
# TLS secret name is environment-specific:
# - Dev: bakery-dev-tls-cert (self-signed, from dev-certificate.yaml)
# - Prod: bakery-ia-prod-tls-cert (Let's Encrypt, from prod-certificate.yaml)
# Patched via kustomize overlays in dev/prod kustomization.yaml
secretName: MAILU_TLS_SECRET_PLACEHOLDER
items:
- key: tls.crt
path: cert.pem
- key: tls.key
path: key.pem
- name: nginx-config
configMap:
name: mailu-nginx-config
items:
- key: ingress-fix.conf
path: ingress-fix.conf
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-admin
namespace: bakery-ia
labels:
app: mailu
component: admin
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: admin
template:
metadata:
labels:
app: mailu
component: admin
spec:
containers:
- name: admin
image: ghcr.io/mailu/admin:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 300m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: smtp
template:
metadata:
labels:
app: mailu
component: smtp
spec:
containers:
- name: smtp
image: ghcr.io/mailu/postfix:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 25
name: smtp
- containerPort: 587
name: submission
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-imap
namespace: bakery-ia
labels:
app: mailu
component: imap
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: imap
template:
metadata:
labels:
app: mailu
component: imap
spec:
containers:
- name: imap
image: ghcr.io/mailu/dovecot:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 143
name: imap
- containerPort: 993
name: imaps
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data

View File

@@ -0,0 +1,93 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mailu-network-policy
namespace: bakery-ia
labels:
app: mailu
component: network-policy
spec:
# Apply to all Mailu pods (matches mailu-deployment.yaml labels)
podSelector:
matchLabels:
app: mailu
policyTypes:
- Ingress
- Egress
ingress:
# Allow SMTP from notification-service
- from:
- podSelector:
matchLabels:
app: notification-service
ports:
- port: 25
- port: 587
# Allow SMTP from other internal services that may need to send email
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: bakery-ia
ports:
- port: 587
# Allow webmail/admin access via ingress controller
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: 80
- port: 443
# Allow internal Mailu component communication
- from:
- podSelector:
matchLabels:
app: mailu
ports:
- port: 25
- port: 587
- port: 143
- port: 993
- port: 80
- port: 11333
- port: 11334
egress:
# Allow relay to external SMTP (Mailgun)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- port: 587
- port: 465
- port: 25
# Allow internal Mailu component communication
- to:
- podSelector:
matchLabels:
app: mailu
ports:
- port: 25
- port: 587
- port: 143
- port: 993
- port: 80
- port: 11333
- port: 11334
# Allow connection to shared Redis (database 15)
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: redis
ports:
- port: 6379
# Allow DNS lookups
- to: []
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

View File

@@ -0,0 +1,31 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mailu-nginx-config
namespace: bakery-ia
labels:
app: mailu
component: nginx-config
data:
# Custom Nginx configuration to prevent redirect loops when behind ingress
# This file is mounted as /overrides/ingress-fix.conf in the Mailu frontend container
ingress-fix.conf: |
# Override the default HTTP to HTTPS redirect behavior
# When behind ingress controller, we should trust X-Forwarded-Proto header
# and avoid redirect loops
# Disable the HTTP to HTTPS redirect by overriding the redirect condition
# This prevents the redirect loop by setting the proxy protocol to https
set $proxy_x_forwarded_proto "https";
# Override the map directive to always return https when behind ingress
map "" $proxy_x_forwarded_proto {
default "https";
}
# Trust the X-Forwarded-* headers from the ingress controller
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

View File

@@ -0,0 +1,21 @@
# Mailu data storage - shared across all Mailu components
# Contains: mail data, SQLite database, DKIM keys, SSL certificates, queue
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mailu-data
namespace: bakery-ia
labels:
app: mailu
component: storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
# NOTE: Change storageClassName based on your cluster's storage provisioner
# For local development (kind): standard
# For AWS EKS: gp2 or gp3
# For GKE: standard or premium-rwo
# For AKS: managed-premium or managed-csi

View File

@@ -0,0 +1,37 @@
apiVersion: v1
kind: Secret
metadata:
name: mailu-secrets
namespace: bakery-ia
labels:
app: mailu
component: secrets
type: Opaque
data:
# Admin credentials (base64 encoded)
# IMPORTANT: Replace with real credentials before production deployment
# Generate with: openssl rand -base64 24 | tr -d '\n' | base64
ADMIN_PASSWORD: "VzJYS2tSdUxpT25ZS2RCWVFTQXJvbjFpeWtFU1M1b2I=" # W2XKkRuLiOnYKdBYQSAron1iykESS5ob
# Mailu secret key for internal encryption
# Generate with: openssl rand -base64 32
SECRET_KEY: "Y2I2MWI5MzRkNDcwMjlhNjQxMTdjMGU0MTEwYzkzZjY2YmJjZjVlYWExNWM4NGM0MjcyN2ZhZDc4Zjc=" # cb61b934d47029a64117c0e4110c93f66bbcf5eaa15c84c42727fad78f7
# External SMTP relay credentials (Mailgun)
# For Mailgun: use postmaster@domain as username
RELAY_USER: "cG9zdG1hc3RlckBET01BSU5fUExBQ0VIT0xERVI=" # postmaster@DOMAIN_PLACEHOLDER
RELAY_PASSWORD: "bWFpbGd1bi1hcGkta2V5LXJlcGxhY2UtaW4tcHJvZHVjdGlvbg==" # mailgun-api-key-replace-in-production
# Database credentials
DB_PASSWORD: "RThLejQ3WW1WekRsSEdzMU05d0FiSnp4Y0tuR09OQ1Q=" # E8Kz47YmVzDlHGs1M9wAbJzxcKnGONCT
# Dovecot admin password (moved from ConfigMap for security)
DOVEADM_PASSWORD: "WnZhMzNoaVBJc2ZtV3RxUlBWV29taTRYZ2xLTlZPcHY=" # Zva33hiPIsfmWtqRPVWomi4XglKNVOpv
# Redis password - same as shared cluster Redis (redis-secrets)
# Mailu uses database 15 for isolation from other services
# REDIS_PW is required by Mailu for Redis authentication
REDIS_PASSWORD: "SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0E=" # J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A
REDIS_PW: "SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0E=" # J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A
# Redis URL for Mailu - using plain TCP port 6380 for internal cluster communication
REDIS_URL: "cmVkaXM6Ly86SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0FAcmVkaXMtc2VydmljZS5iYWtlcnktaWEuc3ZjLmNsdXN0ZXIubG9jYWw6NjM4MC8xNQ==" # redis://:J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A@redis-service.bakery-ia.svc.cluster.local:6380/15

View File

@@ -0,0 +1,126 @@
# Mailu Services - Routes traffic to Mailu stack components
# All services use app: mailu selectors to match mailu-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: mailu-front
namespace: bakery-ia
labels:
app: mailu
component: front
spec:
type: ClusterIP
selector:
app: mailu
component: front
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
---
apiVersion: v1
kind: Service
metadata:
name: mailu-admin
namespace: bakery-ia
labels:
app: mailu
component: admin
spec:
type: ClusterIP
selector:
app: mailu
component: admin
ports:
- name: http
port: 80
targetPort: 80
---
# Primary SMTP service - used by notification-service and other internal services
apiVersion: v1
kind: Service
metadata:
name: mailu-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
type: ClusterIP
selector:
app: mailu
component: smtp
ports:
- name: smtp
port: 25
targetPort: 25
- name: submission
port: 587
targetPort: 587
---
# Alias for backwards compatibility with services expecting 'email-smtp'
apiVersion: v1
kind: Service
metadata:
name: email-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
type: ClusterIP
selector:
app: mailu
component: smtp
ports:
- name: smtp
port: 25
targetPort: 25
- name: submission
port: 587
targetPort: 587
---
apiVersion: v1
kind: Service
metadata:
name: mailu-imap
namespace: bakery-ia
labels:
app: mailu
component: imap
spec:
type: ClusterIP
selector:
app: mailu
component: imap
ports:
- name: imap
port: 143
targetPort: 143
- name: imaps
port: 993
targetPort: 993
---
apiVersion: v1
kind: Service
metadata:
name: mailu-antispam
namespace: bakery-ia
labels:
app: mailu
component: antispam
spec:
type: ClusterIP
selector:
app: mailu
component: antispam
ports:
- name: rspamd
port: 11333
targetPort: 11333
- name: rspamd-admin
port: 11334
targetPort: 11334

View File

@@ -0,0 +1,32 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: dev-
patches:
- target:
kind: ConfigMap
name: mailu-config
patch: |-
- op: replace
path: /data/DOMAIN
value: "bakery-ia.local"
- op: replace
path: /data/HOSTNAMES
value: "mail.bakery-ia.local"
- op: replace
path: /data/RELAY_LOGIN
value: "postmaster@bakery-ia.local"
- op: replace
path: /data/WEBMAIL_ADMIN
value: "admin@bakery-ia.local"
- target:
kind: Secret
name: mailu-secrets
patch: |-
- op: replace
path: /data/RELAY_USER
value: "cG9zdG1hc3RlckBiYWtlcnktaWEubG9jYWw=" # postmaster@bakery-ia.local

View File

@@ -0,0 +1,32 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: prod-
patches:
- target:
kind: ConfigMap
name: mailu-config
patch: |-
- op: replace
path: /data/DOMAIN
value: "bakewise.ai"
- op: replace
path: /data/HOSTNAMES
value: "mail.bakewise.ai"
- op: replace
path: /data/RELAY_LOGIN
value: "postmaster@bakewise.ai"
- op: replace
path: /data/WEBMAIL_ADMIN
value: "admin@bakewise.ai"
- target:
kind: Secret
name: mailu-secrets
patch: |-
- op: replace
path: /data/RELAY_USER
value: "cG9zdG1hc3RlckBiYWtld2lzZS5haQ==" # postmaster@bakewise.ai