A minimal Postfix image based on Alpine with optional LDAP and LMDB support.
postfix package)postfix.sh applies configuration from environment then starts Postfix in the foregroundThis README shows how to run the container, persist data, and configure Postfix without baking custom files into the image.
| Tag | Postfix version | Alpine base |
|---|---|---|
latest, 3, 3.11, 3.11.6, 3.11.6-r0 | 3.11.6-r0 | edge |
3.10, 3.10.13, 3.10.13-r0 | 3.10.13-r0 | 3.23 |
3.9, 3.9.14, 3.9.14-r0 | 3.9.14-r0 | 3.21 |
The major (3) and latest tags always follow the highest supported Postfix version.
Images are built and published automatically via GitLab CI; see .gitlab-ci.yml for the build matrix.
# Run
mkdir -p ./volumes/postfix/data ./volumes/postfix/queue
chmod 0770 ./volumes/postfix/data ./volumes/postfix/queue
docker run -d --name postfix \
-p 25:25 -p 587:587 \
-e MAIN_myhostname=mail.example.com \
-e MAIN_myorigin=example.com \
-e MAIN_mynetworks="127.0.0.1/32 10.0.0.0/8" \
-v $(pwd)/volumes/postfix/data:/srv/data \
-v $(pwd)/volumes/postfix/queue:/srv/queue \
mydcs/postfix:latest
./volumes/postfix/{data,queue} on the host.main.cf and master.cf as described below.The entrypoint img_fs/postfix.sh applies settings from environment variables using a few simple conventions. This allows you to configure both main.cf and master.cf, and to build/update map databases with postmap, all without custom images.
MAIN_ variablesMAIN_<param>=<value>param=value in main.cf via postconf -e.<param> portion is lowercased; underscores are preserved. Example: MAIN_mynetworks -> mynetworks.Examples:
-e MAIN_myhostname=mail.example.com \
-e MAIN_myorigin=example.com \
-e MAIN_mydomain=example.com \
-e MAIN_mynetworks="127.0.0.1/32 10.0.0.0/8" \
-e MAIN_relayhost="[smtp.relay.example.com]:587" \
-e MAIN_smtp_use_tls=yes \
-e MAIN_smtp_sasl_auth_enable=yes \
-e MAIN_smtp_sasl_password_maps="lmdb:/srv/sasl_passwd" \
-e MAIN_smtp_sasl_security_options=noanonymous \
-e MAIN_smtpd_tls_cert_file=/etc/ssl/certs/yourcert.crt \
-e MAIN_smtpd_tls_key_file=/etc/ssl/private/yourcert.key
Tips:
MAIN_virtual_mailbox_maps=ldap:/etc/postfix/ldap/virtual_mailbox_maps.cf and mount that config file (see Volumes).MASTER_ variablesMASTER_<service>__<type>=<rhs>master.cf entry via postconf -Me "<service>/<type>=<rhs>".<service>__<type> portion is lowercased; __ becomes /. Example: MASTER_smtp__inet -> smtp/inet.Examples:
Enable and harden the Submission service (587):
-e MASTER_submission__inet="submission inet n - n - - smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_client_restrictions=permit_sasl_authenticated,reject" \
-e MASTER_smtps__inet="smtps inet n - n - - smtpd -o syslog_name=postfix/smtps -o smtpd_tls_wrappermode=yes"
postconf -Me supports.MAP_ variablesUse these to build/update map databases inside the container at startup.
Two ways to specify a map:
MAP_<maptype>__<pathEnc>=""<pathEnc>, use __ to represent /. The script converts it to an absolute path.lmdb:/srv/sasl_passwd:-e MAP_lmdb__/srv__sasl_passwd=""
MAP_):-e MAP_ANY=lmdb:/srv/sasl_passwd
Notes:
/srv/sasl_passwd) exists and is writable because postmap will create the database (e.g., /srv/sasl_passwd.lmdb). If you bind-mount the directory as read-only, postmap will fail and the container will exit.postfix package. Prefer LMDB maps (e.g., lmdb:/path). You can also specify other map types supported by the installed Postfix build.Example SASL password map for a relayhost:
/srv/sasl_passwd):[smtp.relay.example.com]:587 relayuser:relaypassword
-e MAIN_relayhost="[smtp.relay.example.com]:587" \
-e MAIN_smtp_sasl_auth_enable=yes \
-e MAIN_smtp_sasl_password_maps="lmdb:/srv/sasl_passwd" \
-e MAP_lmdb__/srv__sasl_passwd=""
This image ships with convenient mount points at /srv/queue and /srv/data, but the entrypoint does not change Postfix's queue_directory or data_directory by default. If you want Postfix to use these mount points, set them explicitly via environment variables, for example:
-e MAIN_queue_directory=/srv/queue \
-e MAIN_data_directory=/srv/data \
Without these overrides, Postfix will use its built-in defaults (check with postconf -d; the queue is typically under /var/spool/postfix).
Mount the directories to persist Postfix state and the mail queue across container restarts:
-v $(pwd)/volumes/postfix/data:/srv/data \
-v $(pwd)/volumes/postfix/queue:/srv/queue \
Recommendations:
postfix inside). Typical 0770 permissions work well./srv (e.g., /srv/sasl_passwd), mount the parent directory as read-write so postmap can create the database next to the source file./etc/postfix/ldap/ and reference them from main.cf:
-v $(pwd)/postfix-ldap:/etc/postfix/ldap:ro and -e MAIN_virtual_alias_maps=ldap:/etc/postfix/ldap/virtual_alias_maps.cfMAIN_smtpd_tls_*:
-v /etc/letsencrypt:/etc/letsencrypt:ro with MAIN_smtpd_tls_cert_file=/etc/letsencrypt/live/mail.example.com/fullchain.pem and MAIN_smtpd_tls_key_file=/etc/letsencrypt/live/mail.example.com/privkey.pem.services:
postfix:
image: mydcs/postfix:latest
container_name: postfix
ports:
- "25:25"
- "587:587"
environment:
# main.cf
MAIN_myhostname: mail.example.com
MAIN_myorigin: example.com
MAIN_mydomain: example.com
MAIN_mynetworks: "127.0.0.1/32 10.0.0.0/8"
MAIN_relayhost: "[smtp.relay.example.com]:587"
MAIN_smtp_use_tls: "yes"
MAIN_smtp_sasl_auth_enable: "yes"
MAIN_smtp_sasl_password_maps: "lmdb:/srv/sasl_passwd"
# master.cf (enable submission and smtps)
MASTER_submission__inet: >-
submission inet n - n - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
MASTER_smtps__inet: >-
smtps inet n - n - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
# postmap (build lmdb db from plain file)
MAP_lmdb__/srv__sasl_passwd: ""
volumes:
- ./volumes/postfix/data:/srv/data
- ./volumes/postfix/queue:/srv/queue
- ./volumes/postfix/maps:/srv
# Optional LDAP configs
# - ./postfix-ldap:/etc/postfix/ldap:ro
# Optional TLS certs/keys
# - /etc/letsencrypt:/etc/letsencrypt:ro
Place your sasl_passwd file at ./volumes/postfix/maps/sasl_passwd with contents like:
[smtp.relay.example.com]:587 relayuser:relaypassword
On container start, the entrypoint will run postmap lmdb:/srv/sasl_passwd and main.cf will reference it.
The image defines a healthcheck that waits for Postfix to come up and then checks the SMTP banner on port 25:
HEALTHCHECK --start-period=350s CMD echo QUIT|nc localhost 25|grep "220 .* ESMTP Postfix"
If you disable the healthcheck in Compose, add healthcheck: { disable: true } under the service.
docker logs -f postfix.docker exec -it postfix postconf -ndocker exec -it postfix postmap -q '[smtp.relay.example.com]:587' lmdb:/srv/sasl_passwdpostmap must create databases.MASTER_ entries, then re-add gradually./srv/data and /srv/queue exist and are owned by postfix. If you see permission errors, check the host volume permissions and SELinux/AppArmor context.mynetworks narrowly. For authenticated submission, use TLS and SASL.At startup, the entrypoint script will:
MAIN_ variables to main.cf using postconf -e.MASTER_ variables to master.cf using postconf -Me.MAP_ variables using postmap.That’s it — no custom image layers are required for typical tweaks.
When no environment overrides are provided, img_fs/postfix.sh applies the following defaults on startup (in addition to creating/locking down spool directories and permissions):
Access and networking
mynetworks = 127.0.0.1/32 <container_eth0_cidr> (discovers the container's eth0 CIDR automatically via ip route)relay_domains_reject_code = 564access_map_reject_code = 574maps_rbl_reject_code = 584Delivery and services
virtual_transport = dovecot:inet:dovecot:24postconf -M:
dovecot/inet = dovecot unix - - n - - lmtpsmtp/inet = smtp inet n - n - - smtpd -v (enables verbose smtpd logging)maillog_file = /dev/stdout (logs go to container stdout)HELO and recipient/data restrictions
smtpd_helo_required = yessmtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostnamesmtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_pipelining, reject_non_fqdn_recipient, reject_invalid_helo_hostname, reject_unknown_recipient_domain, reject_unauth_destinationsmtpd_data_restrictions = reject_unauth_pipelining, reject_multi_recipient_bounce, permitSASL (via Dovecot)
smtpd_sasl_auth_enable = yessmtpd_sasl_type = dovecotsmtpd_sasl_path = inet:dovecot:14322TLS settings (server side)
smtpd_tls_loglevel = 1smtpd_tls_received_header = yessmtpd_tls_security_level = maysmtpd_tls_auth_only = yessmtpd_tls_mandatory_protocols = !SSLv2, !SSLv3tls_disable_workarounds = 0xFFFFFFFFFFFFFFFFsmtpd_tls_mandatory_ciphers = highsmtpd_tls_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDB3-SHA, KRB5-DES, CBC3-SHAsmtpd_tls_eecdh_grade = ultraNotes:
queue_directory or data_directory. To use /srv/queue and /srv/data, set MAIN_queue_directory and MAIN_data_directory accordingly.MAIN_*, MASTER_*, and MAP_* environment variables are applied after these defaults, so your values override the above.Issues and merge requests are welcome on GitLab.
Content type
Image
Digest
sha256:6a69ba6d5…
Size
10.2 MB
Last updated
about 1 month ago
docker pull mydcs/postfix