Mail Server Series — Part 4
In the previous chapters of this series, we completed:
- Part 1 – Overall architecture overview
- Part 2 – Network, DNS, and SSL (Let’s Encrypt)
- Part 3 – MariaDB & PostfixAdmin (virtual domains & users)
Now we move on to the heart of the mail system: Postfix.
This chapter focuses on how we build a fully-featured Postfix 3.10.4 SMTP service from source code, package it into a Docker image, integrate it with MySQL, enable multi-domain SNI, connect it with Dovecot for delivery, and route messages through Amavis and Piler.
🚀 1. Why Build Postfix Yourself?
Postfix does not provide an official Docker image. Installing from apt usually gives you:
- Outdated versions (Ubuntu 24.04 ships Postfix 3.7.x)
- Missing features (MySQL, LMDB, SNI maps)
- Limited flexibility inside containers
- Missing PCRE2 support (latest regex engine)
- Difficulties enabling advanced modules
Therefore, this project uses:
👉 Postfix 3.10.4 built from source
👉 Fully packaged into a custom Docker image
With full support for:
✔ MySQL maps
✔ LMDB (for TLS SNI)
✔ PCRE2 regex
✔ SASL authentication
✔ TLS & multi-domain SNI
✔ PostfixAdmin schema
✔ Amavis (Anti-Spam / Anti-Virus)
✔ Piler milter (X-Envelope-To extraction)
🧱 2. Building the Postfix Docker Image (Multi-Stage)
Directory:
/opt/docker/mail/postfix/
2.1 Builder Stage
FROM ubuntu:24.04 AS builder
RUN apt-get update && apt-get install -y \
build-essential libssl-dev libsasl2-dev libmariadb-dev \
liblmdb-dev libpcre2-dev ...
WORKDIR /usr/src
RUN curl -LO https://archive.postfix.org/official/postfix-3.10.4.tar.gz
RUN tar xzf postfix-3.10.4.tar.gz
WORKDIR /usr/src/postfix-3.10.4
RUN make -f Makefile.init makefiles \
CCARGS="-DUSE_TLS -DUSE_SASL_AUTH -DHAS_MYSQL -DHAS_LMDB -DHAS_PCRE2" \
AUXLIBS="-lssl -lcrypto -lsasl2 -lpcre2-8"
2.2 Runtime Stage
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
libssl3 libsasl2-2 libmariadb3 liblmdb0 libpcre2-8-0 ...
COPY --from=builder /tmp/postfix-stage/ /
ENTRYPOINT ["/usr/local/sbin/docker-entrypoint.sh"]
🎯 This produces a clean, stable, and fully extensible Postfix 3.10 Docker image.
🐳 3. Starting the Postfix Container
docker run -dit --name postfix \
--restart=always \
--network intranet-net \
-p 25:25 -p 587:587 \
-e TZ=Asia/Taipei \
-v $PWD/config:/etc/postfix \
-v $PWD/log:/var/log/postfix \
-v /opt/docker/wwwapp/data/etc-letsencrypt:/etc/letsencrypt \
nuface/postfix-3.10.4:1.0
Ports:
- 25 – incoming messages from the Internet
- 587 – submission for authenticated users
📑 4. Postfix main.cf – Detailed Breakdown
Below is the logical overview.
4.1 Basic Settings
myhostname = it.demo.tw
mydestination = $myhostname, localhost
inet_interfaces = all
Important:
Never add your virtual domains to mydestination
otherwise all user mail becomes “local delivery”.
4.2 TLS / SNI Configuration
smtpd_tls_chain_files =
/etc/letsencrypt/live/it.demo.tw/privkey.pem,
/etc/letsencrypt/live/it.demo.tw/fullchain.pem
Enable SNI:
smtpd_tls_server_sni_maps = lmdb:/etc/postfix/tls_sni
Example:
it.demo.tw /etc/letsencrypt/live/it.demo.tw/privkey.pem /fullchain.pem
nuface.tw /etc/letsencrypt/live/nuface.tw/privkey.pem /fullchain.pem
4.3 SASL (Dovecot Authentication)
smtpd_sasl_type = dovecot
smtpd_sasl_path = inet:dovecot:12345
smtpd_sasl_auth_enable = yes
Provides secure login for submission (Thunderbird/Outlook/Roundcube).
4.4 MySQL Virtual Mail System (PostfixAdmin compatible)
virtual_mailbox_domains = mysql:/etc/postfix/sql/virtual_domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/sql/virtual_mailboxes.cf
virtual_alias_maps = mysql:/etc/postfix/sql/virtual_aliases.cf ...
relay_domains = mysql:/etc/postfix/sql/relay_domains.cf
These SQL maps directly match the PostfixAdmin database schema.
4.5 Delivering Emails using Dovecot LMTP
virtual_transport = lmtp:inet:dovecot:24
Benefits:
- Full quota support
- Error reporting to the sender
- More reliable than local delivery
4.6 Milter – Sending Envelope RCPT to Piler
smtpd_milters = inet:pilermilter:33333
non_smtpd_milters = inet:pilermilter:33333
The milter adds:
X-Envelope-From:
X-Envelope-To:
This enables per-user access control in Piler.
🔧 5. master.cf – SMTP Processing Pipeline
This is where the actual message flow is defined.
5.1 Inbound (Port 25 → Amavis)
smtp inet ... content_filter=smtp-amavis:[amavis]:10024
Postfix → Amavis → Scan → Back to Postfix.
5.2 Amavis Return Path (10025)
10025 runs a reduced security smtpd instance:
- No extra checks
- No double filtering
- No duplicate DKIM signing
- Straight to delivery
5.3 Outbound Sign & Relay (10027)
Used for outgoing mail from Submission (587):
Postfix → Amavis → DKIM Sign → 10027 → Internet
5.4 Submission (587)
smtpd_tls_security_level=encrypt
smtpd_sasl_auth_enable=yes
milter_macro_daemon_name=ORIGINATING
Strict security for end-user mail clients.
✉ 6. Transport Maps (Sending to Piler Archive)
archive.local smtp:[172.18.0.1]:2525
Used together with:
always_bcc = piler@archive.local
All emails → archived automatically.
🗄 7. SQL Map Files
You created:
- virtual_domains.cf
- virtual_mailboxes.cf
- virtual_aliases*.cf
- sender_login_maps.cf
- relay_domains.cf
All pointing to:
maildb → postfix
PostfixAdmin writes → Postfix reads → Dovecot delivers
A complete, unified virtual mail system.
🧪 8. Testing the System
8.1 Check Postfix version
postconf mail_version
8.2 Test SNI
openssl s_client -connect mail.it.demo.tw:25 -starttls smtp -servername it.demo.tw
8.3 Test MySQL maps
postmap -q [email protected] mysql:/etc/postfix/sql/virtual_mailboxes.cf
8.4 Test LMTP delivery
sendmail [email protected]
8.5 Test port 587 submission
openssl s_client -connect it.demo.tw:587 -starttls smtp
8.6 Check Piler milter headers
X-Envelope-To:
X-Envelope-From:
🏁 9. Conclusion: Your SMTP Core Is Now Complete
At this stage, your mail system supports:
✔ Multi-domain hosting
✔ Virtual users
✔ Secure TLS + SNI
✔ SPF/DKIM/DMARC ready
✔ Amavis spam & virus filtering
✔ LMTP delivery
✔ Full mail archiving via Piler
✔ SQL-based configuration
Postfix is now fully operational with all enterprise-grade features.