Sign inSign up

mydcs/postfix

By mydcs

•Updated about 2 months ago

https://gitlab.com/mydcs/postfix

Image
1

2.0K

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 2 months ago

docker pull mydcs/postfix