Skip to content

antony@notes:~/container$ cat "Podman-Start-Docker-Mail-Server-with-TLS.md"

Podman Start Docker Mail Server with TLS

2026-03-31· container

Podman Start Docker Mail Server with TLS

環境資訊

項目
域名kubeantony.com
主機名mx1.kubeantony.com
容器引擎Podman(--network=host
網路環境內網,無 Public IP
DNS 管理Cloudflare
套用服務SMTP(25/465/587)、IMAP(993)、POP3(995)、Rspamd Web UI

方案比較

方案 A:mkcert 自簽憑證方案 B:Let’s Encrypt + Cloudflare DNS
費用免費免費
需要 Public IP(DNS-01 驗證不需要)
客戶端信任需手動匯入 CA 憑證自動信任(公開 CA)
有效期限10 年90 天(自動續期)
適用場景內部測試 / 個人使用正式環境 / 對外服務
維護難度低(幾乎不用管)低(cron 自動續期)

方案 A:使用 braveantony/mkcert 自簽憑證

A-1:產生自簽憑證

# 下載 mkcert 工具
cd /opt
sudo git clone https://github.com/braveantony/mkcert.git
sudo chmod +x /opt/mkcert/certctl

編輯設定檔:

sudo nano /opt/mkcert/config

修改為以下內容:

# CA 相關設定
CA_KEY="ca.key"
CA_CRT="ca.crt"
CA_DAYS=3650
CA_SUBJ="/C=TW/ST=Taipei/L=Taipei/O=kubeantony/OU=Mail/CN=kubeantony Root CA"

# Server 相關設定
DOMAIN_NAME="kubeantony.com"
SERVER_KEY="${DOMAIN_NAME}.key"
SERVER_CSR="${DOMAIN_NAME}.csr"
SERVER_CRT="${DOMAIN_NAME}.crt"
SERVER_DAYS=3650
SERVER_SUBJ="/C=TW/ST=Taipei/L=Taipei/O=kubeantony/OU=Mail/CN=mx1.${DOMAIN_NAME}"
V3_EXT="v3.ext"

ALT_NAMES=(
  "DNS.1=*.${DOMAIN_NAME}"
  "DNS.2=${DOMAIN_NAME}"
  "DNS.3=mx1.${DOMAIN_NAME}"
)
# 產生憑證
sudo /opt/mkcert/certctl

# 確認結果
ls -l /opt/mkcert/kubeantony.com/
# 預期:ca.crt  ca.key  kubeantony.com.crt  kubeantony.com.key

A-2:部署憑證

sudo mkdir -p /opt/zfs/dkmail/conf/ssl

sudo cp /opt/mkcert/kubeantony.com/kubeantony.com.key /opt/zfs/dkmail/conf/ssl/
sudo cp /opt/mkcert/kubeantony.com/kubeantony.com.crt /opt/zfs/dkmail/conf/ssl/

# 產生 fullchain(伺服器憑證 + CA 憑證)
sudo bash -c 'cat /opt/mkcert/kubeantony.com/kubeantony.com.crt \
  /opt/mkcert/kubeantony.com/ca.crt \
  > /opt/zfs/dkmail/conf/ssl/fullchain.pem'

sudo chmod 644 /opt/zfs/dkmail/conf/ssl/fullchain.pem
sudo chmod 600 /opt/zfs/dkmail/conf/ssl/kubeantony.com.key

A-3:客戶端匯入 CA 根憑證

自簽憑證需要在每台客戶端手動匯入 ca.crt

客戶端匯入方式
Thunderbird設定 → 隱私與安全 → 憑證 → 匯入 → 選 ca.crt → 勾選「信任此 CA 識別網站」
Windows雙擊 ca.crt → 安裝憑證 → 「受信任的根憑證授權單位」
macOS雙擊 ca.crt → 加入鑰匙圈 → 設定為「永遠信任」
Linuxsudo cp ca.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates

完成後,跳至「共通步驟:設定 Mail Server 並啟動容器」。


方案 B:Let’s Encrypt + Cloudflare DNS-01 驗證

核心原理:acme.sh 透過 Cloudflare API 在 DNS 自動建立 _acme-challenge TXT 記錄, Let’s Encrypt 驗證該記錄後簽發憑證。全程不需要 Public IP 或開放任何 Port。

B-1:在 Cloudflare 建立 API Token

  1. 登入 Cloudflare Dashboard
  2. 右上角點擊個人頭像 → My ProfileAPI Tokens
  3. 點擊 Create Token
  4. 選擇 Edit zone DNS 模板(Use template)
  5. 設定權限:
    • Permissions:Zone / DNS / Edit
    • Zone Resources:IncludeSpecific zonekubeantony.com
  6. 建立後複製 Token(只會顯示一次)
  7. kubeantony.com 的 Overview 頁面,記下右側的 Zone ID

B-2:安裝 acme.sh

# 安裝 acme.sh(不需要 root,但建議用 root 方便管理憑證路徑)
sudo -i
curl https://get.acme.sh | sh -s email=bigred@kubeantony.com

# 讓 acme.sh 指令在當前 shell 可用
source ~/.bashrc

# 設定預設 CA 為 Let's Encrypt
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt

B-3:簽發憑證

# 設定 Cloudflare API 認證
export CF_Token="你的_Cloudflare_API_Token"
export CF_Zone_ID="你的_Zone_ID"

# 簽發萬用字元憑證(含 kubeantony.com 和 *.kubeantony.com)
~/.acme.sh/acme.sh --issue \
  --dns dns_cf \
  -d kubeantony.com \
  -d '*.kubeantony.com'

acme.sh 會自動透過 Cloudflare API 建立 TXT 記錄進行驗證, 驗證通過後自動清除 TXT 記錄。整個過程完全不需要 Public IP。

B-4:安裝憑證到 Mail Server 目錄

sudo mkdir -p /opt/zfs/dkmail/conf/ssl

~/.acme.sh/acme.sh --install-cert \
  -d kubeantony.com \
  -d '*.kubeantony.com' \
  --key-file       /opt/zfs/dkmail/conf/ssl/kubeantony.com.key \
  --fullchain-file /opt/zfs/dkmail/conf/ssl/fullchain.pem \
  --reloadcmd      "podman restart dkmail"

sudo chmod 644 /opt/zfs/dkmail/conf/ssl/fullchain.pem
sudo chmod 600 /opt/zfs/dkmail/conf/ssl/kubeantony.com.key

說明:

  • --install-cert 會將憑證複製到你指定的路徑
  • --reloadcmd 會在每次續期後自動重啟容器套用新憑證
  • acme.sh 會自動建立 cron job,每天檢查是否需要續期(到期前 30 天自動續期)

B-5:確認自動續期 cron

crontab -l
# 應該會看到類似這行:
# 0 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null

完成後,跳至「共通步驟:設定 Mail Server 並啟動容器」。


共通步驟:設定 Mail Server 並啟動容器

以下步驟不論方案 A 或方案 B 都相同。此時你的 /opt/zfs/dkmail/conf/ssl/ 應該已有:

/opt/zfs/dkmail/conf/ssl/
├── fullchain.pem          # 完整憑證鏈
└── kubeantony.com.key     # 私鑰

C-1:設定 Postfix(SMTP TLS)

sudo tee /opt/zfs/dkmail/conf/postfix-main.cf << 'EOF'
# TLS 設定
smtpd_tls_cert_file = /etc/ssl/mail/fullchain.pem
smtpd_tls_key_file = /etc/ssl/mail/kubeantony.com.key
smtpd_tls_security_level = may
smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_mandatory_ciphers = medium

# 寄出郵件也啟用 TLS
smtp_tls_security_level = may
smtp_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
EOF

C-2:設定 Dovecot(IMAP/POP3 TLS)

sudo mkdir -p /opt/zfs/dkmail/conf/dovecot

sudo tee /opt/zfs/dkmail/conf/dovecot/10-ssl.conf << 'EOF'
ssl = required
ssl_cert = </etc/ssl/mail/fullchain.pem
ssl_key = </etc/ssl/mail/kubeantony.com.key
ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes
EOF

注意: Dovecot 設定中 < 是必要語法,代表「讀取此檔案內容」,不是打字錯誤。

C-3:停止舊容器並重新啟動

sudo podman stop dkmail
sudo podman rm dkmail
sudo podman run --name dkmail -itd \
  -h mx1.kubeantony.com --network=host \
  -e NETWORK_INTERFACE=ens18 \
  -e SSL_TYPE=manual \
  -e SSL_CERT_PATH=/etc/ssl/mail/fullchain.pem \
  -e SSL_KEY_PATH=/etc/ssl/mail/kubeantony.com.key \
  --dns 192.168.11.253 --dns-search kubeantony.com \
  -v /opt/zfs/dkmail/data:/var/mail \
  -v /opt/zfs/dkmail/conf/postfix-main.cf:/tmp/docker-mailserver/postfix-main.cf:ro \
  -v /opt/zfs/dkmail/conf/rspamd:/tmp/docker-mailserver/rspamd \
  -v /opt/zfs/dkmail/conf/ssl:/etc/ssl/mail:ro \
  -v /opt/zfs/dkmail/conf/dovecot/10-ssl.conf:/etc/dovecot/conf.d/10-ssl.conf:ro \
  dkmail.rspamd

與你原始指令相比新增的項目:

新增項目說明
-e SSL_TYPE=manual告訴 docker-mailserver 使用手動指定的憑證
-e SSL_CERT_PATH=...容器內憑證路徑
-e SSL_KEY_PATH=...容器內私鑰路徑
-v .../ssl:/etc/ssl/mail:ro掛載憑證目錄
-v .../10-ssl.conf:...覆蓋 Dovecot SSL 設定

C-4:Rspamd Web UI 的 TLS(選用,透過 Nginx 反向代理)

Rspamd Web UI 本身不支援 TLS,需要用 Nginx 做反向代理:

sudo apt install -y nginx
sudo mkdir -p /etc/nginx/ssl
sudo cp /opt/zfs/dkmail/conf/ssl/fullchain.pem /etc/nginx/ssl/
sudo cp /opt/zfs/dkmail/conf/ssl/kubeantony.com.key /etc/nginx/ssl/
sudo tee /etc/nginx/sites-available/rspamd << 'EOF'
server {
    listen 443 ssl;
    server_name mx1.kubeantony.com;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/kubeantony.com.key;
    ssl_protocols       TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://127.0.0.1:11334;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/rspamd /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl restart nginx

驗證 TLS 是否生效

# SMTP STARTTLS(Port 587)
openssl s_client -connect mx1.kubeantony.com:587 -starttls smtp

# SMTPS(Port 465)
openssl s_client -connect mx1.kubeantony.com:465

# IMAPS(Port 993)
openssl s_client -connect mx1.kubeantony.com:993

# POP3S(Port 995)
openssl s_client -connect mx1.kubeantony.com:995

正常輸出應包含:

  • 方案 A(自簽):issuer: CN = kubeantony Root CAVerify return code: 21 — 這是正常的
  • 方案 B(Let’s Encrypt):issuer: C = US, O = Let's EncryptVerify return code: 0 (ok)

完整目錄結構

/opt/zfs/dkmail/
├── data/                              # 郵件資料(/var/mail)
└── conf/
    ├── postfix-main.cf                # Postfix TLS 設定
    ├── ssl/
    │   ├── fullchain.pem              # 完整憑證鏈
    │   └── kubeantony.com.key         # 私鑰
    ├── dovecot/
    │   └── 10-ssl.conf                # Dovecot SSL 設定
    └── rspamd/
        └── override.d/
            ├── options.inc
            ├── worker-controller.inc
            └── hfilter.conf