530 lines
13 KiB
Markdown
530 lines
13 KiB
Markdown
# Lightweight Mail Stack for k3s GitOps Lab
|
|
|
|
Complete deployment of **Stalwart Mail Server** + **SnappyMail** webmail using GitOps with ArgoCD.
|
|
|
|
## 📋 Overview
|
|
|
|
This repository deploys a lightweight, modern mail stack designed for self-hosting and lab environments:
|
|
|
|
- **Stalwart Mail Server**: All-in-one mail server (SMTP, IMAP, Admin UI)
|
|
- **SnappyMail**: Modern, lightweight webmail client
|
|
- **GitOps**: Managed by ArgoCD
|
|
- **Storage**: NFS-based persistent volumes
|
|
- **Ingress**: Traefik for web UI access
|
|
|
|
## 🏗️ Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Traefik Ingress │
|
|
│ mail.dvirlabs.com webmail.dvirlabs.com │
|
|
└───────────┬──────────────────────┬──────────────┘
|
|
│ │
|
|
│ │
|
|
┌───────▼────────┐ ┌────────▼────────┐
|
|
│ Stalwart │◄───│ SnappyMail │
|
|
│ Mail Server │ │ Webmail │
|
|
│ │ │ │
|
|
│ • SMTP │ │ Connects via: │
|
|
│ • IMAP │ │ • IMAP:993 │
|
|
│ • Admin UI │ │ • SMTP:587 │
|
|
└────────┬───────┘ └─────────────────┘
|
|
│
|
|
│
|
|
┌────────▼───────┐
|
|
│ NFS Storage │
|
|
│ Mail Data │
|
|
└────────────────┘
|
|
```
|
|
|
|
## 📁 Repository Structure
|
|
|
|
```
|
|
mail-services/
|
|
├── argocd-apps/
|
|
│ ├── stalwart.yaml # ArgoCD Application for Stalwart
|
|
│ └── snappymail.yaml # ArgoCD Application for SnappyMail
|
|
├── charts/
|
|
│ ├── stalwart/ # Local Helm chart for Stalwart
|
|
│ │ ├── Chart.yaml
|
|
│ │ ├── values.yaml # Default values
|
|
│ │ └── templates/
|
|
│ │ ├── namespace.yaml
|
|
│ │ ├── secret.yaml
|
|
│ │ ├── statefulset.yaml
|
|
│ │ ├── service.yaml
|
|
│ │ └── ingress.yaml
|
|
│ └── snappymail/ # Local Helm chart for SnappyMail
|
|
│ ├── Chart.yaml
|
|
│ ├── values.yaml # Default values
|
|
│ └── templates/
|
|
│ ├── deployment.yaml
|
|
│ ├── pvc.yaml
|
|
│ ├── service.yaml
|
|
│ ├── ingress.yaml
|
|
│ └── configmap.yaml
|
|
└── manifests/
|
|
├── stalwart/
|
|
│ └── values.yaml # Custom values for dvirlabs.com
|
|
└── snappymail/
|
|
└── values.yaml # Custom values for dvirlabs.com
|
|
```
|
|
|
|
## 🚀 Quick Start
|
|
|
|
### Prerequisites
|
|
|
|
- k3s cluster running
|
|
- ArgoCD installed and configured
|
|
- Traefik ingress controller
|
|
- NFS storage class (`nfs-client`)
|
|
- DNS records pointing to your cluster
|
|
|
|
### Step 1: Update Configuration
|
|
|
|
1. **Update ArgoCD Application manifests** with your Git repository URL:
|
|
|
|
```bash
|
|
# Edit both files and replace YOUR_USERNAME with your actual repo
|
|
vim argocd-apps/stalwart.yaml
|
|
vim argocd-apps/snappymail.yaml
|
|
```
|
|
|
|
2. **Change the Stalwart admin password**:
|
|
|
|
```bash
|
|
# Edit and set a strong password
|
|
vim manifests/stalwart/values.yaml
|
|
```
|
|
|
|
Find this section and change `CHANGE_ME_PLEASE_USE_STRONG_PASSWORD`:
|
|
|
|
```yaml
|
|
secret:
|
|
create: true
|
|
name: stalwart-credentials
|
|
adminPassword: "YOUR_STRONG_PASSWORD_HERE"
|
|
```
|
|
|
|
3. **Update domain names** (if not using dvirlabs.com):
|
|
|
|
```bash
|
|
# Update in both files
|
|
vim manifests/stalwart/values.yaml
|
|
vim manifests/snappymail/values.yaml
|
|
```
|
|
|
|
### Step 2: Deploy with ArgoCD
|
|
|
|
```bash
|
|
# Apply ArgoCD Applications
|
|
kubectl apply -f argocd-apps/stalwart.yaml
|
|
kubectl apply -f argocd-apps/snappymail.yaml
|
|
|
|
# Check deployment status
|
|
kubectl get applications -n argocd
|
|
|
|
# Watch pods come up
|
|
kubectl get pods -n mail -w
|
|
```
|
|
|
|
### Step 3: Verify Deployment
|
|
|
|
```bash
|
|
# Check all resources in mail namespace
|
|
kubectl get all -n mail
|
|
|
|
# Check PVCs
|
|
kubectl get pvc -n mail
|
|
|
|
# Check ingresses
|
|
kubectl get ingress -n mail
|
|
```
|
|
|
|
Expected output:
|
|
```
|
|
NAME READY STATUS RESTARTS AGE
|
|
pod/stalwart-0 1/1 Running 0 2m
|
|
pod/snappymail-xxx-xxx 1/1 Running 0 2m
|
|
|
|
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
|
|
service/stalwart ClusterIP 10.43.x.x <none> 8080/TCP,25/TCP,587/TCP,993/TCP
|
|
service/snappymail ClusterIP 10.43.x.x <none> 8888/TCP
|
|
|
|
NAME CLASS HOSTS
|
|
ingress.networking.k8s.io/stalwart traefik mail.dvirlabs.com
|
|
ingress.networking.k8s.io/snappymail traefik webmail.dvirlabs.com
|
|
```
|
|
|
|
## 🌐 Access the Services
|
|
|
|
### Stalwart Admin UI
|
|
|
|
URL: `https://mail.dvirlabs.com`
|
|
|
|
Default credentials:
|
|
- Username: `admin@dvirlabs.com`
|
|
- Password: (the one you set in manifests/stalwart/values.yaml)
|
|
|
|
### SnappyMail Webmail
|
|
|
|
URL: `https://webmail.dvirlabs.com`
|
|
|
|
First-time setup:
|
|
1. Access the admin panel: `https://webmail.dvirlabs.com/?admin`
|
|
2. Default admin password: `12345` (change immediately!)
|
|
3. Configure mail server connection:
|
|
- **IMAP Server**: `stalwart.mail.svc.cluster.local`
|
|
- **IMAP Port**: `993`
|
|
- **IMAP Security**: SSL/TLS
|
|
- **SMTP Server**: `stalwart.mail.svc.cluster.local`
|
|
- **SMTP Port**: `587`
|
|
- **SMTP Security**: STARTTLS
|
|
|
|
## 📧 Configuring Real Mail Service
|
|
|
|
### Important: Cloudflare Tunnel Limitations
|
|
|
|
⚠️ **WARNING**: While Cloudflare Tunnel works fine for web UIs (admin panel and webmail), it **CANNOT** be used for actual email protocols (SMTP/IMAP).
|
|
|
|
**What works through Cloudflare Tunnel:**
|
|
- ✅ Stalwart admin UI (HTTPS)
|
|
- ✅ SnappyMail webmail (HTTPS)
|
|
|
|
**What does NOT work through Cloudflare Tunnel:**
|
|
- ❌ Receiving mail from other servers (SMTP port 25)
|
|
- ❌ Sending mail to other servers (SMTP port 25)
|
|
- ❌ External email clients (IMAP/SMTP)
|
|
|
|
### Required for Real Email
|
|
|
|
To receive and send real email, you need:
|
|
|
|
#### 1. DNS Records
|
|
|
|
```dns
|
|
; MX Record (Mail Exchange)
|
|
@ IN MX 10 mail.dvirlabs.com.
|
|
|
|
; A Record (pointing to your public IP - NOT Cloudflare Tunnel)
|
|
mail IN A YOUR_PUBLIC_IP
|
|
|
|
; SPF Record (Sender Policy Framework)
|
|
@ IN TXT "v=spf1 mx ~all"
|
|
|
|
; DMARC Record
|
|
_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:admin@dvirlabs.com"
|
|
|
|
; DKIM Record (generated by Stalwart)
|
|
; Get this from Stalwart admin UI after setup
|
|
default._domainkey IN TXT "v=DKIM1; k=rsa; p=YOUR_PUBLIC_KEY_HERE"
|
|
```
|
|
|
|
#### 2. Port Forwarding
|
|
|
|
You need to expose these ports directly (NOT through Cloudflare):
|
|
|
|
```
|
|
Port 25 (SMTP) - Required for receiving mail from other servers
|
|
Port 587 (SMTP) - Required for sending mail (submission)
|
|
Port 465 (SMTPS) - Optional, secure SMTP submission
|
|
Port 993 (IMAPS) - Required for IMAP access
|
|
Port 143 (IMAP) - Optional, plaintext IMAP
|
|
```
|
|
|
|
**Option A: NodePort Service**
|
|
|
|
```bash
|
|
kubectl apply -f - <<EOF
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: stalwart-external
|
|
namespace: mail
|
|
spec:
|
|
type: NodePort
|
|
ports:
|
|
- name: smtp
|
|
port: 25
|
|
targetPort: 25
|
|
nodePort: 30025
|
|
- name: submission
|
|
port: 587
|
|
targetPort: 587
|
|
nodePort: 30587
|
|
- name: imaps
|
|
port: 993
|
|
targetPort: 993
|
|
nodePort: 30993
|
|
selector:
|
|
app.kubernetes.io/name: stalwart
|
|
EOF
|
|
```
|
|
|
|
Then forward ports 25, 587, 993 from your router to your k3s node on ports 30025, 30587, 30993.
|
|
|
|
**Option B: LoadBalancer with MetalLB**
|
|
|
|
If you have MetalLB configured:
|
|
|
|
```bash
|
|
kubectl apply -f - <<EOF
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: stalwart-lb
|
|
namespace: mail
|
|
spec:
|
|
type: LoadBalancer
|
|
loadBalancerIP: YOUR_LB_IP
|
|
ports:
|
|
- name: smtp
|
|
port: 25
|
|
targetPort: 25
|
|
- name: submission
|
|
port: 587
|
|
targetPort: 587
|
|
- name: imaps
|
|
port: 993
|
|
targetPort: 993
|
|
selector:
|
|
app.kubernetes.io/name: stalwart
|
|
EOF
|
|
```
|
|
|
|
#### 3. PTR (Reverse DNS) Record
|
|
|
|
Contact your ISP or VPS provider to set a PTR record:
|
|
|
|
```
|
|
YOUR_PUBLIC_IP -> mail.dvirlabs.com
|
|
```
|
|
|
|
This is **critical** for email deliverability. Without it, many servers will reject your mail.
|
|
|
|
## 🔧 Configuration Management
|
|
|
|
### Using External Secrets (Recommended for Production)
|
|
|
|
Instead of storing passwords in Git, use External Secrets Operator:
|
|
|
|
1. Install External Secrets Operator
|
|
2. Create a secret in your secret backend (Vault, AWS Secrets Manager, etc.)
|
|
3. Update manifests/stalwart/values.yaml:
|
|
|
|
```yaml
|
|
secret:
|
|
create: false # Don't create the secret
|
|
name: stalwart-credentials # Reference external secret
|
|
```
|
|
|
|
4. Create an ExternalSecret:
|
|
|
|
```yaml
|
|
apiVersion: external-secrets.io/v1beta1
|
|
kind: ExternalSecret
|
|
metadata:
|
|
name: stalwart-credentials
|
|
namespace: mail
|
|
spec:
|
|
refreshInterval: 1h
|
|
secretStoreRef:
|
|
name: your-secret-store
|
|
kind: SecretStore
|
|
target:
|
|
name: stalwart-credentials
|
|
data:
|
|
- secretKey: STALWART_ADMIN_PASSWORD
|
|
remoteRef:
|
|
key: mail/stalwart/admin-password
|
|
```
|
|
|
|
## 🛠️ Maintenance
|
|
|
|
### View Stalwart Logs
|
|
|
|
```bash
|
|
kubectl logs -n mail stalwart-0 -f
|
|
```
|
|
|
|
### View SnappyMail Logs
|
|
|
|
```bash
|
|
kubectl logs -n mail -l app.kubernetes.io/name=snappymail -f
|
|
```
|
|
|
|
### Access Stalwart Shell
|
|
|
|
```bash
|
|
kubectl exec -it -n mail stalwart-0 -- /bin/sh
|
|
```
|
|
|
|
### Backup Mail Data
|
|
|
|
```bash
|
|
# Backup Stalwart data
|
|
kubectl exec -n mail stalwart-0 -- tar czf /tmp/mail-backup.tar.gz /opt/stalwart-mail
|
|
kubectl cp mail/stalwart-0:/tmp/mail-backup.tar.gz ./mail-backup-$(date +%Y%m%d).tar.gz
|
|
|
|
# Backup SnappyMail config
|
|
kubectl exec -n mail -l app.kubernetes.io/name=snappymail -- tar czf /tmp/snappymail-backup.tar.gz /var/lib/snappymail
|
|
kubectl cp mail/snappymail-xxx:/tmp/snappymail-backup.tar.gz ./snappymail-backup-$(date +%Y%m%d).tar.gz
|
|
```
|
|
|
|
### Restore from Backup
|
|
|
|
```bash
|
|
# Restore Stalwart
|
|
kubectl cp ./mail-backup.tar.gz mail/stalwart-0:/tmp/mail-backup.tar.gz
|
|
kubectl exec -n mail stalwart-0 -- tar xzf /tmp/mail-backup.tar.gz -C /
|
|
kubectl rollout restart statefulset -n mail stalwart
|
|
```
|
|
|
|
## 🔍 Troubleshooting
|
|
|
|
### Pods Not Starting
|
|
|
|
```bash
|
|
# Check pod events
|
|
kubectl describe pod -n mail stalwart-0
|
|
kubectl describe pod -n mail -l app.kubernetes.io/name=snappymail
|
|
|
|
# Check PVC status
|
|
kubectl get pvc -n mail
|
|
```
|
|
|
|
### Ingress Not Working
|
|
|
|
```bash
|
|
# Check ingress status
|
|
kubectl describe ingress -n mail
|
|
|
|
# Check Traefik logs
|
|
kubectl logs -n kube-system -l app.kubernetes.io/name=traefik
|
|
|
|
# Test internal connectivity
|
|
kubectl run -it --rm debug --image=busybox -n mail -- wget -O- http://stalwart:8080
|
|
```
|
|
|
|
### SnappyMail Can't Connect to Stalwart
|
|
|
|
```bash
|
|
# Test IMAP connectivity from SnappyMail pod
|
|
kubectl exec -it -n mail -l app.kubernetes.io/name=snappymail -- nc -zv stalwart.mail.svc.cluster.local 993
|
|
|
|
# Check Stalwart service
|
|
kubectl get svc -n mail stalwart
|
|
```
|
|
|
|
### Email Not Being Delivered
|
|
|
|
Common issues:
|
|
1. **No PTR record**: Check reverse DNS
|
|
2. **Port 25 blocked**: Many ISPs block outbound port 25
|
|
3. **Missing SPF/DKIM/DMARC**: Check DNS records
|
|
4. **IP on blacklist**: Check https://mxtoolbox.com/blacklists.aspx
|
|
|
|
## 📊 Monitoring
|
|
|
|
### Check Mail Queue
|
|
|
|
```bash
|
|
# Access Stalwart admin UI
|
|
# https://mail.dvirlabs.com
|
|
# Navigate to Queue section
|
|
```
|
|
|
|
### Resource Usage
|
|
|
|
```bash
|
|
# Check pod resource usage
|
|
kubectl top pods -n mail
|
|
|
|
# Check PVC usage
|
|
kubectl exec -n mail stalwart-0 -- df -h /opt/stalwart-mail
|
|
```
|
|
|
|
## 🔐 Security Hardening
|
|
|
|
### Recommended Post-Deployment Steps
|
|
|
|
1. **Change SnappyMail admin password** immediately
|
|
2. **Enable fail2ban** for brute force protection
|
|
3. **Set up TLS certificates** with cert-manager (if not using Cloudflare)
|
|
4. **Enable DKIM signing** in Stalwart
|
|
5. **Configure rate limiting** in Stalwart
|
|
6. **Regular backups** of mail data
|
|
7. **Monitor logs** for suspicious activity
|
|
|
|
### TLS Certificates with cert-manager
|
|
|
|
If you want Let's Encrypt certificates instead of Cloudflare:
|
|
|
|
```yaml
|
|
# Add to ingress annotations
|
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
```
|
|
|
|
And configure TLS:
|
|
|
|
```yaml
|
|
tls:
|
|
- hosts:
|
|
- mail.dvirlabs.com
|
|
secretName: stalwart-tls
|
|
```
|
|
|
|
## 📚 Additional Resources
|
|
|
|
- [Stalwart Documentation](https://stalw.art/docs)
|
|
- [SnappyMail Documentation](https://snappymail.eu/)
|
|
- [Email Deliverability Best Practices](https://www.mail-tester.com/)
|
|
- [DKIM Setup Guide](https://www.cloudflare.com/learning/dns/dns-records/dns-dkim-record/)
|
|
|
|
## ⚙️ Customization
|
|
|
|
### Adjust Storage Size
|
|
|
|
Edit `manifests/stalwart/values.yaml` or `manifests/snappymail/values.yaml`:
|
|
|
|
```yaml
|
|
persistence:
|
|
size: 50Gi # Increase as needed
|
|
```
|
|
|
|
### Change Resource Limits
|
|
|
|
Edit `manifests/*/values.yaml`:
|
|
|
|
```yaml
|
|
resources:
|
|
requests:
|
|
memory: "2Gi"
|
|
cpu: "1000m"
|
|
limits:
|
|
memory: "8Gi"
|
|
cpu: "4000m"
|
|
```
|
|
|
|
### Add Multiple Domains
|
|
|
|
Configure in Stalwart admin UI after deployment.
|
|
|
|
## 🤝 Contributing
|
|
|
|
This is a personal lab setup, but feel free to fork and adapt for your needs!
|
|
|
|
## 📝 License
|
|
|
|
MIT License - Use at your own risk
|
|
|
|
## ⚠️ Disclaimer
|
|
|
|
This setup is designed for lab/self-hosting environments. For production use:
|
|
- Use External Secrets for credentials
|
|
- Set up proper TLS certificates
|
|
- Configure backup automation
|
|
- Enable monitoring and alerting
|
|
- Review security best practices
|
|
- Test email deliverability thoroughly
|