diff --git a/swarm/core.yml b/swarm/core.yml index c6a891d..c661061 100644 --- a/swarm/core.yml +++ b/swarm/core.yml @@ -13,6 +13,18 @@ services: - target: 443 published: 443 mode: host + - target: 25 + published: 25 + mode: host + - target: 587 + published: 587 + mode: host + - target: 993 + published: 993 + mode: host + - target: 995 + published: 995 + mode: host - target: 1883 published: 1883 mode: host @@ -123,6 +135,10 @@ services: # Create an entrypoint "postgres-socket" listening on port 5432 # - --entrypoints.postgres-socket.address=:5432 # Others entrypoints can be created, like a TCP entrypoint + - --entrypoints.smtp.address=:25 + - --entrypoints.submission.address=:587 + - --entrypoints.imaps.address=:993 + - --entrypoints.pop3s.address=:995 - --entrypoints.mqtt.address=:1883 - --entrypoints.web-socket.address=:8083 - --entrypoints.web-socket-secure.address=:8084 diff --git a/swarm/mailserver.yml b/swarm/mailserver.yml index 96a850e..b52c2f4 100644 --- a/swarm/mailserver.yml +++ b/swarm/mailserver.yml @@ -2,45 +2,43 @@ services: mail: image: mailserver/docker-mailserver:latest env_file: /var/data/docker-mailserver/docker-mailserver.env + hostname: mail + restart: unless-stopped volumes: - # - /var/data/docker-mailserver/maildata:/var/mail - # - /var/data/docker-mailserver/mailstate:/var/mail-state - # - /var/data/docker-mailserver/config:/tmp/docker-mailserver + - mail_data:/var/mail + - mail_state:/var/mail-state + - mail_logs:/var/log/mail + - mail_config:/tmp/docker-mailserver - /var/data/config/acme.json:/etc/letsencrypt/acme.json:ro - ports: - - target: 25 - published: 25 - protocol: tcp - mode: host - - target: 587 - published: 587 - protocol: tcp - mode: host - - target: 993 - published: 993 - protocol: tcp - mode: host - - target: 995 - published: 995 - protocol: tcp - mode: host - networks: - - internal - - whoami: - image: docker.io/traefik/whoami:latest networks: - internal - traefik-public deploy: + placement: + constraints: + - node.role == manager labels: - traefik.enable=true - traefik.swarm.network=traefik-public - traefik.constraint-label=traefik-public - - traefik.http.routers.mailwhoami.entrypoints=https - - traefik.http.routers.mailwhoami.rule=Host(`mail.${DOMAIN}`) - - traefik.http.routers.mailwhoami.tls.certresolver=le - - traefik.http.services.mailwhoami.loadbalancer.server.port=80 + - traefik.tcp.routers.mail-smtp.entrypoints=smtp + - traefik.tcp.routers.mail-smtp.rule=HostSNI(`*`) + - traefik.tcp.routers.mail-smtp.service=mail-smtp-svc + - traefik.tcp.services.mail-smtp-svc.loadbalancer.server.port=25 + - traefik.tcp.routers.mail-submission.entrypoints=submission + - traefik.tcp.routers.mail-submission.rule=HostSNI(`*`) + - traefik.tcp.routers.mail-submission.service=mail-submission-svc + - traefik.tcp.services.mail-submission-svc.loadbalancer.server.port=587 + - traefik.tcp.routers.mail-imaps.entrypoints=imaps + - traefik.tcp.routers.mail-imaps.rule=HostSNI(`*`) + - traefik.tcp.routers.mail-imaps.tls.passthrough=true + - traefik.tcp.routers.mail-imaps.service=mail-imaps-svc + - traefik.tcp.services.mail-imaps-svc.loadbalancer.server.port=993 + - traefik.tcp.routers.mail-pop3s.entrypoints=pop3s + - traefik.tcp.routers.mail-pop3s.rule=HostSNI(`*`) + - traefik.tcp.routers.mail-pop3s.tls.passthrough=true + - traefik.tcp.routers.mail-pop3s.service=mail-pop3s-svc + - traefik.tcp.services.mail-pop3s-svc.loadbalancer.server.port=995 # rainloop: # image: nerzhul/rainloop-arm64 @@ -66,4 +64,10 @@ networks: driver: overlay ipam: config: - - subnet: 172.16.2.0/24 \ No newline at end of file + - subnet: 172.16.2.0/24 + +volumes: + mail_data: {} + mail_state: {} + mail_logs: {} + mail_config: {} \ No newline at end of file diff --git a/swarm/mailserver/README.md b/swarm/mailserver/README.md new file mode 100644 index 0000000..e66cd79 --- /dev/null +++ b/swarm/mailserver/README.md @@ -0,0 +1,58 @@ +# Self-hosted Mail Server (Docker Swarm) + +This stack uses Docker Mailserver and is deployed with: + +make deploy-mailserver + +## 1) Configure mail domain + +Edit swarm/mailserver/docker-mailserver.env: + +- OVERRIDE_HOSTNAME: mail host FQDN (example: mail.example.com) +- OVERRIDE_DOMAINNAME: root mail domain (example: example.com) +- POSTMASTER_ADDRESS: postmaster mailbox +- SSL_DOMAIN: certificate domain for mail TLS + +## 2) DNS records (required) + +Create these DNS records for your mail domain: + +- A: mail.example.com -> your public IP +- MX: example.com -> mail.example.com (priority 10) +- SPF TXT: v=spf1 mx a:mail.example.com -all +- DKIM TXT: mail._domainkey.example.com -> generated DKIM key +- DMARC TXT: _dmarc.example.com -> v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com + +## 3) Open ports + +Ensure inbound TCP is open to your Traefik manager node for: + +- 25 (SMTP) +- 587 (Submission) +- 993 (IMAPS) +- 995 (POP3S) + +Mail ports are exposed by the Traefik service in [swarm/core.yml](swarm/core.yml), then routed to the mail service using TCP labels in [swarm/mailserver.yml](swarm/mailserver.yml). + +## 4) Deploy + +make deploy-mailserver + +## 5) Create mailbox accounts + +Use the Docker Mailserver setup helper from a manager node: + +docker exec -it $(docker ps --filter name=mailserver_mail --format '{{.ID}}' | head -n 1) setup email add user@example.com 'StrongPasswordHere' + +docker exec -it $(docker ps --filter name=mailserver_mail --format '{{.ID}}' | head -n 1) setup alias add postmaster@example.com user@example.com + +## 6) Verify + +- Check service status: docker service ls | grep mailserver +- Check logs: docker service logs -f mailserver_mail +- Send a test from an external mailbox and confirm delivery. + +## Notes + +- Mail data is persisted in named volumes: mail_data, mail_state, mail_logs, mail_config. +- For high deliverability, add PTR/rDNS with your server provider and keep SPF/DKIM/DMARC aligned. diff --git a/swarm/mailserver/docker-mailserver.env b/swarm/mailserver/docker-mailserver.env index 98fdcea..4269a51 100644 --- a/swarm/mailserver/docker-mailserver.env +++ b/swarm/mailserver/docker-mailserver.env @@ -3,8 +3,8 @@ ENABLE_CLAMAV=1 ENABLE_POSTGREY=1 ONE_DIR=1 OVERRIDE_HOSTNAME=mail.furyhawk.lol -OVERRIDE_DOMAINNAME=mail.furyhawk.lol -POSTMASTER_ADDRESS=admin@furyhawk.lol +OVERRIDE_DOMAINNAME=furyhawk.lol +POSTMASTER_ADDRESS=postmaster@furyhawk.lol PERMIT_DOCKER=network SSL_TYPE=letsencrypt SSL_DOMAIN=mail.furyhawk.lol \ No newline at end of file