Postfix on Alpine with dynamic maps, env config, custom overlays, and TLSRPT.
10K+
This project builds a complete Postfix image on Alpine Linux, compiles a pinned Postfix version directly from source, enables SMTPUTF8/EAI, enables dynamic lookup tables and optional databases, and links Postfix against libtlsrpt so that TLSRPT works end-to-end.
The image uses a clean multi-stage build, pinned upstream sources, predictable runtime defaults, GitHub Actions for publishing, and a runtime model that is environment-driven first without blocking fully custom main.cf, master.cf, and map files when you need exact control.
libtlsrpt build and link integrationPOSTFIX_* and POSTFIXMASTER_*_FILE secret variants for every environment variablemain.cf and master.cfstdout using postlogd.
├── Dockerfile
├── docker-entrypoint.sh
├── docker-healthcheck.sh
├── README.md
├── .env.example
├── .gitignore
├── Makefile
├── defaults
│ ├── main.cf
│ └── master.cf
├── examples
│ ├── docker-compose.yml
│ ├── custom-config
│ │ ├── main.cf.d
│ │ └── master.cf.d
│ ├── init
│ └── maps
└── .github
└── workflows
├── docker-publish.yml
└── postfix-upstream-check.yml
The image is built in two stages:
libtlsrpt, and builds tinycdbCurrent pinned defaults in this repository:
3.11.7libtlsrpt 0.5.03.24The running container exposes the usual built-in Postfix table types plus dynamic lookups such as:
cdbldaplmdbmemcachemongodbmysqlnispcrepgsqlsqlitehash, btree, cidr, regexp, socketmap, tcp, texthash, unionmap, unixSDBM is not included on Alpine because the runtime does not provide the sdbm.h system interface required by Postfix's optional SDBM plugin.
The default master.cf also includes:
smtpsubmissionsubmissionspostlog / postlogd for container-friendly loggingThe Postfix build also includes SMTPUTF8/EAI support. The runtime default is smtputf8_enable = yes.
docker build -t postfix .
To pin an explicit upstream release:
docker build \
--build-arg POSTFIX_VERSION=3.11.7 \
--build-arg POSTFIX_SHA256=a2f3242345753448072177fae83c322a403c9263696996406201145dab8e8625 \
-t postfix:3.11.7 .
Multi-arch build with buildx:
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg POSTFIX_VERSION=3.11.7 \
--build-arg POSTFIX_SHA256=a2f3242345753448072177fae83c322a403c9263696996406201145dab8e8625 \
-t postfix:3.11.7 \
.
cp .env.example .env
make compose-up
docker exec postfix postconf -n
docker exec postfix postconf -m
docker exec postfix postconf smtputf8_enable smtp_tlsrpt_enable smtp_tlsrpt_socket_name maillog_file
This repository includes a GitHub Actions workflow at .github/workflows/docker-publish.yml that publishes the maintainer image to Docker Hub as chrroessner/postfix.
It also includes .github/workflows/postfix-upstream-check.yml, which runs daily, checks the official Postfix release directory for a newer upstream tarball, refreshes the pinned SHA256, and opens or updates a pull request automatically when the pinned version in this repository is behind upstream.
The workflow runs:
mainmasterv*scheduleworkflow_dispatchRequired GitHub repository secrets:
DOCKERHUB_USERNAMEDOCKERHUB_TOKENRecommended Docker Hub setup:
<your-namespace>/postfixlatest for the default branchv<postfix-version>-r<revision>, for example v3.11.7-r1The original container scripts and build tooling are licensed under the MIT License. See LICENSE. Postfix source and the Postfix patch series are distributed under IPL-1.0, with existing per-file notices preserved; they are not covered by the repository's blanket MIT license. See NOTICE.md and Postfix license.
This is an RNS-maintained customized Postfix image, not an official upstream Postfix image. Upstream submission or acceptance is not required to distribute our modifications and must not be inferred from their inclusion here.
Every image contains the exact patched Postfix sources, libtlsrpt and tinycdb
sources, their original license files, the Dockerfile, patches and source
metadata in /usr/share/doc/postfix-custom/sources/build-sources.tar.gz.
Extract them without starting a mail server:
docker run --rm --entrypoint cat chrroessner/postfix:3.11.7-r1 \
/usr/share/doc/postfix-custom/sources/build-sources.tar.gz > build-sources.tar.gz
Use an image digest instead of a mutable tag when retrieving sources for a
specific deployment. SHA256SUMS is stored beside the archive. Sources remain
available with that image even if upstream download locations change. Alpine
packages remain under their individual licenses; the archive covers the three
components compiled by this Dockerfile, not all Alpine package sources.
The table describes the current build. Patch filenames identify the qualified upstream version; image revisions distinguish our releases from upstream.
| Patch | Current image | Upstream status | Purpose |
|---|---|---|---|
| SASL EXTERNAL / client certificates | 3.11.7-r1 | Downstream; unchanged from previous 3.11.7 build | Pass verified certificate SAN identity and fingerprint to Dovecot SASL/PfxHTTP; optional full-chain CRLs with TLS resumption disabled. |
| Internal origin | 3.11.7-r1 | Backport of Wietse Venema's final implementation in postfix-3.12-20260915 | Expose {postfix_internal_origin} as bounce, notify, verify, or absent/empty, using upstream provenance semantics. |
The original two downstream DSN-origin patches have been removed. Older
3.11.7 images (before revision r1) exported {postfix_dsn_origin} with
internal/external. That interface is no longer provided or aliased.
Consumers must migrate together with this image. Pin the revision and digest;
the floating 3.11.7 tag alone does not identify the macro contract.
Upstream Postfix 3.11.7 itself does not include this feature. The final implementation was published in the official 3.12 development snapshot on 2026-09-15. This image keeps the stable 3.11.7 base and backports only that feature, preserving its values, behavior and author attribution. See backport provenance for source identity, mechanical adaptations and migration checks.
The published container image additionally includes Postfix, which is distributed under IPL-1.0, plus bundled runtime dependencies such as libtlsrpt and tinycdb. Because of that, the OCI image metadata declares a combined license expression.
On startup, the following happens:
/var/spool/postfix and /var/lib/postfix.main.cf, master.cf, and dynamicmaps.cf./etc/postfix/custom-config/main.cf/etc/postfix/custom-config/master.cf/etc/postfix/custom-config/dynamicmaps.cf/etc/postfix/custom-config/main.cf.d/*.cf/etc/postfix/custom-config/master.cf.d/*.cf/etc/postfix/custom-config/dynamicmaps.cf.d/*.cfPOSTFIX_* for main.cfPOSTFIXMASTER_* for master.cf.sh files from /docker-entrypoint-init.dPOSTFIX_RUNTIME_POSTMAPSpostfix check and finally starts postfix start-fg| Path in Container | Purpose |
|---|---|
/etc/postfix/custom-config | Full replacement files or config snippets |
/etc/postfix/maps | Custom map files |
/docker-entrypoint-init.d | Init hooks (.sh) |
/etc/postfix/certs | TLS certificates and keys |
/var/spool/postfix | Queue data if you want persistence |
/var/lib/postfix | Runtime-owned Postfix data directory |
| Variable | Default | Meaning |
|---|---|---|
POSTFIX_RUNTIME_LOG_TO_STDOUT | true | Enable maillog_file = /dev/stdout |
POSTFIX_RUNTIME_HOSTNAME | derived | Sets myhostname |
POSTFIX_RUNTIME_DOMAIN | derived | Sets mydomain |
POSTFIX_RUNTIME_DESTINATIONS | derived | Sets mydestination |
POSTFIX_RUNTIME_MYNETWORKS | 127.0.0.0/8 [::1]/128 | Sets mynetworks |
POSTFIX_RUNTIME_AUTO_POSTMAP_STANDARD | true | Compiles standard text maps |
POSTFIX_RUNTIME_POSTMAPS | empty | Comma-separated extra maps, for example lmdb:/etc/postfix/maps/transport,lmdb:/etc/postfix/maps/routes |
POSTFIX_RUNTIME_RUN_SCRIPTS | true | Runs /docker-entrypoint-init.d/*.sh |
POSTFIX_RUNTIME_TLSRPT_SOCKET_NAME | run/tlsrpt/tlsrpt.sock | Sets smtp_tlsrpt_socket_name |
main.cf overridesEvery variable named POSTFIX_<parameter> becomes:
<parameter> = <value>
The suffix is used verbatim as the Postfix parameter name. That matters for parameters with embedded uppercase segments such as CAfile.
Examples:
POSTFIX_relayhost=[smtp.example.net]:587POSTFIX_smtpd_tls_cert_file=/etc/postfix/certs/tls.crtPOSTFIX_smtpd_tls_CAfile=/etc/postfix/certs/ca.crtPOSTFIX_smtp_tlsrpt_enable=yesPOSTFIX_smtputf8_enable=yesPOSTFIX_transport_maps=lmdb:/etc/postfix/maps/transportEvery variable also supports a _FILE variant:
POSTFIX_relayhost_FILE=/run/secrets/postfix_relayhostPOSTFIX_sasl_passwd_FILE=/run/secrets/postfix_sasl_passwdmaster.cf overridesEvery variable named POSTFIXMASTER_<selector> becomes a postconf -P update.
Encoding rules:
__ becomes /___ becomes -The remaining characters are preserved verbatim, so use the exact Postfix parameter spelling after the service selector.
Examples:
POSTFIXMASTER_submission__inet__syslog_name=postfix/submissionPOSTFIXMASTER_submission__inet__smtpd_tls_security_level=encryptPOSTFIXMASTER_smtps___inet__smtpd_upstream_proxy_protocol=haproxyYou have three supported customization levels.
Use POSTFIX_* and POSTFIXMASTER_* for most installations. This is the intended fast path.
Mount custom files into /etc/postfix/custom-config:
main.cfmaster.cfdynamicmaps.cfOr mount snippets into:
main.cf.dmaster.cf.ddynamicmaps.cf.dMount map files under /etc/postfix/maps and either:
POSTFIX_*POSTFIX_RUNTIME_POSTMAPSExample:
docker run --rm \
-e POSTFIX_transport_maps=lmdb:/etc/postfix/maps/transport \
-e POSTFIX_RUNTIME_POSTMAPS=lmdb:/etc/postfix/maps/transport \
-v $(pwd)/examples/maps:/etc/postfix/maps \
postfix
Important for generated map types such as hash, cdb or lmdb:
postmap writes the compiled database next to the source fileImportant note when replacing master.cf completely:
POSTFIX_RUNTIME_LOG_TO_STDOUT=true, your custom master.cf should keep the postlog / postlogd servicesPOSTFIX_RUNTIME_LOG_TO_STDOUT=falseSMTPUTF8 support is compiled in through ICU and defaults to enabled:
smtputf8_enable = yesYou can still disable it for a specific deployment:
docker run --rm \
-e POSTFIX_smtputf8_enable=no \
postfix
SMTPUTF8 also has to be supported by the rest of the mail path, including content filters, LMTP servers, and downstream SMTP servers.
TLSRPT support is a hard requirement in this image:
libtlsrpt is built from source-DUSE_TLSRPT-ltlsrptThe container default is:
smtp_tlsrpt_enable = nosmtp_tlsrpt_socket_name = run/tlsrpt/tlsrpt.sockThat socket name is relative to the Postfix queue directory, so the effective default path inside the container is:
/var/spool/postfix/run/tlsrpt/tlsrpt.sock
Typical integration pattern:
POSTFIX_smtp_tlsrpt_enable=yes.POSTFIX_RUNTIME_TLSRPT_SOCKET_NAME.Since image revision 3.11.7-r1, the build applies Wietse Venema's final
upstream internal-origin implementation from postfix-3.12-20260915, backported
to the pinned stable source with a version guard and SHA-256 verification.
There is no old-macro fallback.
{postfix_internal_origin} | Upstream meaning |
|---|---|
bounce | Locally generated delivery notification, including double bounces and postmaster copies. |
notify | Locally generated SMTP session transcript. |
verify | Address-verification probe; Milters must leave it unchanged. |
| absent or empty | Other messages, including SMTP/QMQP/sendmail submissions and internal alias/forward processing. |
The macro is included in the default milter_connect_macros. Explicit operator
macro lists must include it if CONNECT delivery is needed. A Milter can also
request it through normal macro negotiation, including at end of headers.
This macro proves provenance, not permission to sign any message. A DKIM2 DSN
adapter must retain its null-envelope-sender, recipient, report-structure and
embedded-message validation. notify, verify, and non-null-sender
postmaster/double-bounce messages must not be mistaken for normal DSNs.
Deploy the matching adapter and image in a coordinated change. The old adapter will not recognize the new macro and may pass bounces without signing them; the new adapter cannot use the removed downstream macro. Rollback must restore the old Postfix image, adapter and configuration together.
The pinned Postfix 3.11.7 source is patched at build time with
patches/postfix-3.11.7-sasl-external-client-cert.patch. The build verifies
the patch checksum and refuses to apply this version-specific patch to another
Postfix release.
The patch bridges a verified TLS client identity to the Dovecot authentication
protocol used by Postfix. EXTERNAL is advertised and accepted for an SMTP
session only when all of these conditions are true:
rfc822Name SAN valuerfc822Name values deduplicate to that one identityThere is no Common Name fallback. A missing SAN, multiple different mail SANs,
an unsafe SAN, an untrusted chain, or a missing fingerprint removes EXTERNAL
from that session while leaving other configured SASL mechanisms available.
If the backend offers only EXTERNAL and the current session has no eligible
certificate identity, Postfix continues the SMTP session without announcing
or accepting AUTH; this condition does not terminate the smtpd process.
The same applies when the certificate-aware mechanism list still contains
other mechanisms but smtpd_sasl_mechanism_filter removes all of them for the
session, for example an external-only static filter after EXTERNAL was
removed because no verified client identity is available.
For an eligible AUTH EXTERNAL request, Postfix adds exactly these fields to
the tab-delimited Dovecot auth request:
ssl_client_verify=SUCCESS
ssl_client_san_email=<rfc822Name>
ssl_client_fingerprint=<hex[:hex...]>
The verify and fingerprint values are generated internally. The certificate identity is validated before it reaches this wire format. The receiving auth service must still reject duplicate security fields and must perform its normal identity lookup and authorization-ID policy.
Client certificates should remain optional when password or OAuth clients are also supported:
smtpd_tls_ask_ccert = yes
smtpd_tls_req_ccert = no
Configure smtpd_tls_CAfile with only the issuing CAs that are trusted for
client authentication. Do not add personal client certificates to
relay_clientcerts; SASL EXTERNAL must follow the regular authenticated-user
and account-policy path.
Upstream OpenSSL chain validation does not enable revocation checks merely because a certificate contains CRL distribution points. This patch therefore adds the opt-in Postfix parameters:
smtpd_tls_crl_file (default: empty)tlsproxy_tls_crl_file (default: $smtpd_tls_crl_file)When smtpd_tls_crl_file is non-empty, Postfix loads PEM CRLs from that file
and enables both X509_V_FLAG_CRL_CHECK and X509_V_FLAG_CRL_CHECK_ALL.
The file must contain current CRLs for every issuer in the verified chain. A
revoked certificate and a missing or expired required CRL make the client
certificate untrusted, so EXTERNAL is not offered. Because the client
certificate remains optional, the TLS connection can still use another SASL
mechanism.
Failure to load the explicitly configured CRL file disables TLS support rather
than silently continuing without revocation checks. Leaving the parameter
empty preserves upstream behavior and does not enable CRL checking. A combined
CA-and-CRL PEM bundle may be used for both smtpd_tls_CAfile and
smtpd_tls_crl_file. Reload Postfix after replacing the CRL bundle so that
new SMTP server and TLS proxy processes load the updated revocation state.
While CRL checking is enabled, server-side TLS session caching and all session tickets are disabled, including TLS 1.3 tickets. Every new SMTP connection therefore performs a full certificate verification against the currently loaded CRLs; a session verified before a revocation cannot bypass that check through TLS resumption after a reload.
This image follows the Postfix container logging model documented upstream:
postfix start-fgmaillog_file = /dev/stdoutpostlogd wired in through master.cfThat gives you standard container log collection without a syslog daemon in the image.
If you prefer a different logging setup:
POSTFIX_RUNTIME_LOG_TO_STDOUT=falsemain.cf / master.cfThe image includes a simple health check based on:
postfix status
Build locally:
make build
Run a local smoke check:
make test-smoke
Create a local SBOM export:
make sbom-local
Inspect registry SBOM data after push:
make sbom-registry IMAGE_NAME=<your-namespace>/postfix TAG=latest
Content type
Image
Digest
sha256:2b3cbc331…
Size
24.2 MB
Last updated
about 10 hours ago
docker pull chrroessner/postfix