Sign inSign up

mydcs/postfix

By mydcs

Updated about 1 month ago

https://gitlab.com/mydcs/postfix

Image
1

1.9K

mydcs/postfix repository overview

Postfix Docker Image

Docker Pulls Docker Image Size Pipeline Status Source on GitLab

A minimal Postfix image based on Alpine with optional LDAP and LMDB support.

  • Base: Alpine Linux (version configurable at build time)
  • Packages: postfix, postfix-ldap, postfix-pcre, openldap-clients, openssl (LMDB map support is built into the postfix package)
  • Exposed ports: 25 (smtp), 587 (submission)
  • Healthcheck: simple banner check on port 25
  • Entrypoint: postfix.sh applies configuration from environment then starts Postfix in the foreground

This README shows how to run the container, persist data, and configure Postfix without baking custom files into the image.

Supported tags

TagPostfix versionAlpine base
latest, 3, 3.11, 3.11.6, 3.11.6-r03.11.6-r0edge
3.10, 3.10.13, 3.10.13-r03.10.13-r03.23
3.9, 3.9.14, 3.9.14-r03.9.14-r03.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.

Quick start

# 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
  • Data and queue are persisted to ./volumes/postfix/{data,queue} on the host.
  • You can add more env vars to configure main.cf and master.cf as described below.

Configuration via environment variables

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.cf: MAIN_ variables
  • Pattern: MAIN_<param>=<value>
  • Behavior: Sets param=value in main.cf via postconf -e.
  • Mapping: The <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:

  • When values contain spaces or special characters, quote them (Compose YAML is easier for complex values).
  • To use LDAP maps: e.g., MAIN_virtual_mailbox_maps=ldap:/etc/postfix/ldap/virtual_mailbox_maps.cf and mount that config file (see Volumes).
master.cf: MASTER_ variables
  • Pattern: MASTER_<service>__<type>=<rhs>
  • Behavior: Sets/overrides a master.cf entry via postconf -Me "<service>/<type>=<rhs>".
  • Mapping: The <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"
  • In Compose, prefer YAML folded style for readability.
  • You can override any service line that postconf -Me supports.
postmap: MAP_ variables

Use these to build/update map databases inside the container at startup.

Two ways to specify a map:

  1. Encode type and path in the variable name (no meaningful value needed):
  • Pattern: MAP_<maptype>__<pathEnc>=""
  • Mapping: In <pathEnc>, use __ to represent /. The script converts it to an absolute path.
  • Example builds lmdb:/srv/sasl_passwd:
-e MAP_lmdb__/srv__sasl_passwd=""
  1. Provide the full map spec in the value (name can be anything starting with MAP_):
-e MAP_ANY=lmdb:/srv/sasl_passwd

Notes:

  • Ensure the plain text source file (e.g., /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.
  • LMDB map support is built into the Alpine 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:

  • File contents (/srv/sasl_passwd):
[smtp.relay.example.com]:587 relayuser:relaypassword
  • Env/Compose:
-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=""

Volumes and persistence

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:

  • Create the directories first and ensure they are writable by the container (they will be owned by postfix inside). Typical 0770 permissions work well.
  • If you use map files under /srv (e.g., /srv/sasl_passwd), mount the parent directory as read-write so postmap can create the database next to the source file.
  • To mount custom LDAP definitions, place them under /etc/postfix/ldap/ and reference them from main.cf:
    • Example: -v $(pwd)/postfix-ldap:/etc/postfix/ldap:ro and -e MAIN_virtual_alias_maps=ldap:/etc/postfix/ldap/virtual_alias_maps.cf
  • TLS keys/certs can be mounted under standard locations and referenced via MAIN_smtpd_tls_*:
    • Example: -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.

docker-compose example

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.

Healthcheck

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.

Troubleshooting

  • Logs: Postfix runs in the foreground; logs go to container stdout/stderr.
    • Inspect with docker logs -f postfix.
  • Check current config inside the container:
    • docker exec -it postfix postconf -n
    • docker exec -it postfix postmap -q '[smtp.relay.example.com]:587' lmdb:/srv/sasl_passwd
  • If the container exits at startup:
    • Ensure all referenced files exist and are writable if postmap must create databases.
    • Verify complex environment values are correctly quoted (prefer Compose for readability).
    • Validate your master.cf overrides: try removing MASTER_ entries, then re-add gradually.
  • Permissions: The entrypoint ensures /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.

Security notes

  • Exposing SMTP to the internet has security and reputation implications. Restrict relay and authentication appropriately.
  • Set mynetworks narrowly. For authenticated submission, use TLS and SASL.
  • Keep secrets (e.g., relay passwords) in Docker secrets or env vars managed by your orchestrator.

What gets configured automatically

At startup, the entrypoint script will:

  1. Apply all MAIN_ variables to main.cf using postconf -e.
  2. Apply all MASTER_ variables to master.cf using postconf -Me.
  3. Build maps for all MAP_ variables using postmap.
  4. Start Postfix in the foreground.

That’s it — no custom image layers are required for typical tweaks.

Defaults applied by the entrypoint

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)
    • Custom reject codes:
      • relay_domains_reject_code = 564
      • access_map_reject_code = 574
      • maps_rbl_reject_code = 584
  • Delivery and services

    • virtual_transport = dovecot:inet:dovecot:24
    • master.cf entries via postconf -M:
      • dovecot/inet = dovecot unix - - n - - lmtp
      • smtp/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 = yes
    • smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname
    • smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_pipelining, reject_non_fqdn_recipient, reject_invalid_helo_hostname, reject_unknown_recipient_domain, reject_unauth_destination
    • smtpd_data_restrictions = reject_unauth_pipelining, reject_multi_recipient_bounce, permit
  • SASL (via Dovecot)

    • smtpd_sasl_auth_enable = yes
    • smtpd_sasl_type = dovecot
    • smtpd_sasl_path = inet:dovecot:14322
  • TLS settings (server side)

    • smtpd_tls_loglevel = 1
    • smtpd_tls_received_header = yes
    • smtpd_tls_security_level = may
    • smtpd_tls_auth_only = yes
    • smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3
    • tls_disable_workarounds = 0xFFFFFFFFFFFFFFFF
    • smtpd_tls_mandatory_ciphers = high
    • smtpd_tls_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDB3-SHA, KRB5-DES, CBC3-SHA
    • smtpd_tls_eecdh_grade = ultra

Notes:

  • The entrypoint does not change queue_directory or data_directory. To use /srv/queue and /srv/data, set MAIN_queue_directory and MAIN_data_directory accordingly.
  • All MAIN_*, MASTER_*, and MAP_* environment variables are applied after these defaults, so your values override the above.

Contributing

Issues and merge requests are welcome on GitLab.

Tag summary

Content type

Image

Digest

sha256:6a69ba6d5

Size

10.2 MB

Last updated

about 1 month ago

docker pull mydcs/postfix