🐳 راهاندازی با داکر
شروع سریع
docker pull ghcr.io/themadorg/madmail:latest
تگهای تصویر
| Tag | توضیحات |
|---|---|
latest |
آخرین نسخه پایدار از شاخه main |
X.Y.Z |
نسخه مشخص (مثلاً 0.18.0) |
روش ۱: ساده با آیپی (SQLite)
docker run -d --name madmail \
-p 25:25 \
-p 143:143 \
-p 465:465 \
-p 587:587 \
-p 993:993 \
-p 80:80 \
-p 443:443 \
-p 1080:1080 \
-v ./maddy.conf:/data/maddy.conf \
-v ./data:/data \
-v ./tls:/data/tls \
-e MADDY_HOSTNAME=SERVER_IP \
-e MADDY_DOMAIN="[SERVER_IP]" \
-e MADDY_PUBLIC_IP=SERVER_IP \
ghcr.io/themadorg/madmail:latest
یک فایل docker-compose.yml و یک فایل maddy.conf نیاز دارید.
در این حالت گواهینامه TLS خودامضا (self-signed) به صورت خودکار تولید میشود.
docker-compose.yml
services:
madmail:
image: ghcr.io/themadorg/madmail:latest
restart: always
ports:
# SMTP: Incoming/Outgoing email (Standard)
- "25:25"
# IMAP: Reading email (STARTTLS)
- "143:143"
# SUBMISSION: Outgoing email (TLS/SSL)
- "465:465"
# SUBMISSION: Outgoing email (STARTTLS)
- "587:587"
# IMAPS: Reading email (TLS/SSL)
- "993:993"
# API: Admin and Registration (HTTP)
- "80:80"
# API: Admin and Registration (HTTPS)
- "443:443"
# PROXY: Shadowsocks service
- "1080:1080"
volumes:
- ./maddy.conf:/data/maddy.conf
- ./data:/data
- ./tls:/data/tls
environment:
- MADDY_HOSTNAME=SERVER_IP
- MADDY_DOMAIN=[SERVER_IP]
- MADDY_PUBLIC_IP=SERVER_IP
- MADDY_SQLITE_UNSAFE_SYNC_OFF=1
maddy.conf
$(hostname) = {env:MADDY_HOSTNAME}
$(primary_domain) = {env:MADDY_DOMAIN}
$(local_domains) = $(primary_domain) $(hostname)
$(public_ip) = {env:MADDY_PUBLIC_IP}
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
state_dir /data
auth.pass_table local_authdb {
auto_create yes
table sql_table {
driver sqlite3
dsn /data/credentials.db
table_name passwords
}
}
storage.imapsql local_mailboxes {
auto_create yes
driver sqlite3
dsn /data/imapsql.db
}
hostname $(hostname)
table.chain local_rewrites {
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
optional_step static {
entry postmaster postmaster@$(primary_domain)
}
}
msgpipeline local_routing {
destination postmaster $(local_domains) {
modify {
replace_rcpt &local_rewrites
}
deliver_to &local_mailboxes
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
smtp tcp://0.0.0.0:25 {
dmarc yes
check {
require_mx_record
dkim
spf
}
source $(local_domains) {
reject 501 5.1.8 "Use Submission for outgoing SMTP"
}
default_source {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
}
submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
auth &local_authdb
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
source $(local_domains) {
check {
authorize_sender {
prepare_email &local_rewrites
user_to_email identity
}
}
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
modify {
dkim $(primary_domain) $(local_domains) default
}
deliver_to &remote_queue
}
}
default_source {
reject 501 5.1.8 "Non-local sender domain"
}
}
target.remote outbound_delivery {
mx_auth {
dane
mtasts {
cache fs
fs_dir mtasts_cache/
}
local_policy {
min_tls_level encrypted
min_mx_level none
}
}
}
target.queue remote_queue {
target &outbound_delivery
autogenerated_msg_domain $(primary_domain)
bounce {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
}
}
}
imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
auth &local_authdb
storage &local_mailboxes
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
}
chatmail tls://0.0.0.0:443 tcp://0.0.0.0:80 {
mail_domain $(primary_domain)
mx_domain $(hostname)
web_domain $(hostname)
public_ip $(public_ip)
auth_db local_authdb
storage local_mailboxes
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
ss_addr 0.0.0.0:1080
}
نکته: وقتی از آیپی بجای دامنه استفاده میکنید، باید آن را در
MADDY_DOMAIN داخل براکت قرار دهید: [203.0.113.50]
روش ۲: با PostgreSQL
مشکل database is locked در SQLite با استفاده از PostgreSQL برطرف میشود.
همچنین pub/sub برای آپدیتهای IMAP بلادرنگ فعال میشود.
docker network create mad-net && \
docker run -d --name db --network mad-net \
-e POSTGRES_PASSWORD=pass \
-e POSTGRES_DB=maddy \
postgres:16-alpine && \
docker run -d --name madmail --network mad-net \
-p 25:25 \
-p 143:143 \
-p 465:465 \
-p 587:587 \
-p 993:993 \
-p 80:80 \
-p 443:443 \
-p 1080:1080 \
-v ./maddy.conf:/data/maddy.conf \
-v ./data:/data \
-v ./tls:/data/tls \
-e MADDY_HOSTNAME=SERVER_IP \
-e MADDY_DOMAIN="[SERVER_IP]" \
-e MADDY_PUBLIC_IP=SERVER_IP \
ghcr.io/themadorg/madmail:latest
docker-compose.yml
services:
madmail:
image: ghcr.io/themadorg/madmail:latest
restart: always
ports:
# SMTP: Incoming/Outgoing email (Standard)
- "25:25"
# IMAP: Reading email (STARTTLS)
- "143:143"
# SUBMISSION: Outgoing email (TLS/SSL)
- "465:465"
# SUBMISSION: Outgoing email (STARTTLS)
- "587:587"
# IMAPS: Reading email (TLS/SSL)
- "993:993"
# API: Admin and Registration (HTTP)
- "80:80"
# API: Admin and Registration (HTTPS)
- "443:443"
# PROXY: Shadowsocks service
- "1080:1080"
volumes:
- ./maddy.conf:/data/maddy.conf
- ./data:/data
- ./tls:/data/tls
environment:
- MADDY_HOSTNAME=SERVER_IP
- MADDY_DOMAIN=[SERVER_IP]
- MADDY_PUBLIC_IP=SERVER_IP
- POSTGRES_USER=madmail
- POSTGRES_PASSWORD=madmail_pass
- POSTGRES_DB=madmail
depends_on:
- db
db:
image: postgres:alpine
restart: always
environment:
- POSTGRES_USER=madmail
- POSTGRES_PASSWORD=madmail_pass
- POSTGRES_DB=madmail
volumes:
- ./pgdata:/var/lib/postgresql/data
maddy.conf
$(hostname) = {env:MADDY_HOSTNAME}
$(primary_domain) = {env:MADDY_DOMAIN}
$(local_domains) = $(primary_domain) $(hostname)
$(public_ip) = {env:MADDY_PUBLIC_IP}
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
state_dir /data
auth.pass_table local_authdb {
auto_create yes
table sql_table {
driver postgres
dsn "host=db dbname={env:POSTGRES_DB} user={env:POSTGRES_USER} password={env:POSTGRES_PASSWORD} sslmode=disable"
table_name passwords
}
}
storage.imapsql local_mailboxes {
auto_create yes
driver postgres
dsn "host=db dbname={env:POSTGRES_DB} user={env:POSTGRES_USER} password={env:POSTGRES_PASSWORD} sslmode=disable"
}
hostname $(hostname)
table.chain local_rewrites {
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
optional_step static {
entry postmaster postmaster@$(primary_domain)
}
}
msgpipeline local_routing {
destination postmaster $(local_domains) {
modify {
replace_rcpt &local_rewrites
}
deliver_to &local_mailboxes
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
smtp tcp://0.0.0.0:25 {
dmarc yes
check {
require_mx_record
dkim
spf
}
source $(local_domains) {
reject 501 5.1.8 "Use Submission for outgoing SMTP"
}
default_source {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
}
submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
auth &local_authdb
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
source $(local_domains) {
check {
authorize_sender {
prepare_email &local_rewrites
user_to_email identity
}
}
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
modify {
dkim $(primary_domain) $(local_domains) default
}
deliver_to &remote_queue
}
}
default_source {
reject 501 5.1.8 "Non-local sender domain"
}
}
target.remote outbound_delivery {
mx_auth {
dane
mtasts {
cache fs
fs_dir mtasts_cache/
}
local_policy {
min_tls_level encrypted
min_mx_level none
}
}
}
target.queue remote_queue {
target &outbound_delivery
autogenerated_msg_domain $(primary_domain)
bounce {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
}
}
}
imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
auth &local_authdb
storage &local_mailboxes
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
}
chatmail tls://0.0.0.0:443 tcp://0.0.0.0:80 {
mail_domain $(primary_domain)
mx_domain $(hostname)
web_domain $(hostname)
public_ip $(public_ip)
auth_db local_authdb
storage local_mailboxes
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
ss_addr 0.0.0.0:1080
}
روش ۳: دامنه با TLS خودکار (Let's Encrypt)
docker run -d --name madmail \
-p 25:25 \
-p 143:143 \
-p 465:465 \
-p 587:587 \
-p 993:993 \
-p 80:80 \
-p 443:443 \
-p 1080:1080 \
-v ./maddy.conf:/data/maddy.conf \
-v ./data:/data \
-e MADDY_HOSTNAME=chat.example.org \
-e MADDY_DOMAIN=chat.example.org \
-e MADDY_PUBLIC_IP=SERVER_IP \
-e MADDY_ACME_EMAIL=admin@example.org \
ghcr.io/themadorg/madmail:latest
⚠️ پیشنیازها:
- دامنه واقعی با رکورد DNS A به سمت سرور شما
- پورت 80 باز و از اینترنت قابل دسترس (برای چالش ACME)
- پورت 443 باز (برای HTTPS)
docker-compose.yml
services:
madmail:
image: ghcr.io/themadorg/madmail:latest
restart: always
ports:
# SMTP: Incoming/Outgoing email (Standard)
- "25:25"
# IMAP: Reading email (STARTTLS)
- "143:143"
# SUBMISSION: Outgoing email (TLS/SSL)
- "465:465"
# SUBMISSION: Outgoing email (STARTTLS)
- "587:587"
# IMAPS: Reading email (TLS/SSL)
- "993:993"
# HTTP: ACME HTTP-01 challenge handler + redirect to HTTPS
- "80:80"
# HTTPS: Admin API, Registration, ALPN-multiplexed SMTP+IMAP
- "443:443"
# PROXY: Shadowsocks service
- "1080:1080"
volumes:
- ./maddy.conf:/data/maddy.conf
- ./data:/data
environment:
- MADDY_HOSTNAME=chat.example.org
- MADDY_DOMAIN=chat.example.org
- MADDY_PUBLIC_IP=203.0.113.50
- MADDY_ACME_EMAIL=admin@example.org
maddy.conf
$(hostname) = {env:MADDY_HOSTNAME}
$(primary_domain) = {env:MADDY_DOMAIN}
$(local_domains) = $(primary_domain)
$(public_ip) = {env:MADDY_PUBLIC_IP}
tls {
loader autocert {
hostname {env:MADDY_HOSTNAME}
email {env:MADDY_ACME_EMAIL}
cache_dir /data/autocert
agreed
}
}
state_dir /data
auth.pass_table local_authdb {
auto_create yes
table sql_table {
driver sqlite3
dsn /data/credentials.db
table_name passwords
}
}
storage.imapsql local_mailboxes {
auto_create yes
driver sqlite3
dsn /data/imapsql.db
}
hostname $(hostname)
table.chain local_rewrites {
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
optional_step static {
entry postmaster postmaster@$(primary_domain)
}
}
msgpipeline local_routing {
destination postmaster $(local_domains) {
modify {
replace_rcpt &local_rewrites
}
deliver_to &local_mailboxes
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
smtp tcp://0.0.0.0:25 {
dmarc yes
check {
require_mx_record
dkim
spf
}
source $(local_domains) {
reject 501 5.1.8 "Use Submission for outgoing SMTP"
}
default_source {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
}
submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
auth &local_authdb
source $(local_domains) {
check {
authorize_sender {
prepare_email &local_rewrites
user_to_email identity
}
}
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
modify {
dkim $(primary_domain) $(local_domains) default
}
deliver_to &remote_queue
}
}
default_source {
reject 501 5.1.8 "Non-local sender domain"
}
}
target.remote outbound_delivery {
mx_auth {
dane
mtasts {
cache fs
fs_dir mtasts_cache/
}
local_policy {
min_tls_level encrypted
min_mx_level none
}
}
}
target.queue remote_queue {
target &outbound_delivery
autogenerated_msg_domain $(primary_domain)
bounce {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
}
}
}
imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
auth &local_authdb
storage &local_mailboxes
}
chatmail tls://0.0.0.0:443 {
mail_domain $(primary_domain)
mx_domain $(hostname)
web_domain $(hostname)
public_ip $(public_ip)
auth_db local_authdb
storage local_mailboxes
ss_addr 0.0.0.0:1080
}
طرز کار:
- Madmail یک سرور HTTP روی پورت ۸۰ برای چالش ACME راهاندازی میکند
- Let's Encrypt در اولین اتصال TLS، گواهینامه صادر میکند
- گواهینامهها در
/data/autocert/ذخیره و خودکار تمدید میشوند - درخواستهای HTTP غیر ACME به HTTPS ریدایرکت میشوند (302)
جایگزین: اگر گواهینامه خودتان را دارید (مثلاً از certbot):
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
و در docker-compose فایلهای TLS را mount کنید: ./tls:/data/tls
پورتها
| Port | Protocol | توضیحات |
|---|---|---|
| 25 | SMTP | ارسال و دریافت ایمیل |
| 143 | IMAP | خواندن ایمیل (STARTTLS) |
| 465 | Submission | ارسال ایمیل (TLS/SSL) |
| 587 | Submission | ارسال ایمیل (STARTTLS) |
| 993 | IMAPS | خواندن ایمیل (TLS/SSL) |
| 80 | HTTP | ثبتنام + چالش ACME |
| 443 | HTTPS | Admin API و ثبتنام |
| 1080 | TCP | پروکسی Shadowsocks |
حجمها (Volumes)
| Path | توضیحات |
|---|---|
/data |
دیتابیس، کلیدهای DKIM، صف پیامها |
/data/maddy.conf |
فایل تنظیمات اصلی |
/data/tls/ |
گواهینامههای TLS (در حالت file) |
/data/autocert/ |
کش گواهینامهها (در حالت autocert) |
ساخت از سورس
Build
docker build -t madmail:latest .
Compose Build
docker compose up -d --build
تصاویر داکر به صورت خودکار در هر push به شاخه main ساخته و به GHCR ارسال میشوند.