Postfix, when run in Docker, can feel like a black box, but it’s just a mail server with a few networking quirks.
Let’s see Postfix in action. Imagine we have a simple web application that needs to send out emails for password resets. We’ll put Postfix in a Docker container and have our app connect to it.
First, the Dockerfile for our Postfix image:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y postfix mailutils
RUN newaliases
# Configure Postfix to allow relaying from anywhere (for this example)
RUN postconf -e 'inet_interfaces = all'
RUN postconf -e 'mydestination = localhost, your.domain.com'
RUN postconf -e 'mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128'
RUN postconf -e 'relayhost ='
RUN postconf -e 'home_mailbox = Maildir/'
# Expose the SMTP port
EXPOSE 25
CMD ["postfix", "start-fg"]
Now, we’ll run this container and, for demonstration, send a test email from another container on the same Docker network.
Let’s set up a docker-compose.yml:
version: '3.8'
services:
postfix:
build: .
container_name: my-postfix-server
ports:
- "25:25" # Expose Postfix's SMTP port externally
networks:
- app-network
sender:
image: ubuntu:22.04
container_name: mail-sender
depends_on:
- postfix
networks:
- app-network
command: >
bash -c "
apt-get update && apt-get install -y mailutils &&
echo 'This is a test email body.' | mail -s 'Test Subject' -r sender@your.domain.com recipient@example.com &&
echo 'Email sent (or attempted).' &&
sleep infinity
"
We’ll build and run this: docker-compose up --build.
Once the sender container has run its command, you’d check the my-postfix-server logs to see if the email was processed. If you were to inspect the my-postfix-server container’s filesystem, you’d find mail in /var/mail/recipient or /var/mail/recipient/new if home_mailbox = Maildir/ is set and the user exists.
The core problem Postfix solves is reliably queuing and delivering email. Internally, it uses several daemons: master (which manages other daemons), smtpd (for receiving mail), pickup (for picking up mail from the local queue), and qmgr (which queues mail for delivery). smtp is the daemon that actually sends mail out.
The key levers you control are in main.cf:
inet_interfaces: Which network interfaces Postfix listens on.allmeans all available.mydestination: Domains that this Postfix instance considers "local" and will accept mail for.mynetworks: Trusted IP addresses that are allowed to relay mail through this server without authentication. This is critical for internal app communication.relayhost: If set, all outgoing mail is sent to this host instead of attempting direct MX lookups. Useful for using a smart host.home_mailbox: Where mail is delivered locally.Maildir/is common for compatibility with many mail clients.
When running in Docker, the mynetworks setting is crucial. If your sending application is in a different container but on the same Docker network, Postfix needs to trust that container’s IP range. By default, mynetworks is often just 127.0.0.0/8, which won’t allow other containers to send mail.
The ports mapping in docker-compose.yml ("25:25") is also a common point of confusion. It maps port 25 on the host to port 25 inside the container. If another service on your host is already using port 25, the Docker mapping will fail or bind to the wrong interface, preventing Postfix from listening.
The sender container in our example is configured to send mail to recipient@example.com. If example.com is not a domain Postfix is configured to accept mail for (i.e., not in mydestination), Postfix will attempt to deliver it to the internet. This requires Postfix to be able to perform DNS lookups and connect to the recipient’s mail server on port 25. If relayhost is set, it will send to that instead.
A common misconception is that mynetworks is about authentication. It’s not; it’s about source IP trust for relaying. If you need to authenticate users, you’d typically set up smtpd_sasl_auth_enable = yes and related configurations, often with a separate authentication daemon.
If your application container cannot reach the Postfix container on port 25, it’s almost always a Docker networking issue, not a Postfix configuration problem. Ensure both containers are on the same user-defined Docker network. localhost inside a container refers only to that container, not other containers on the network. You must use the service name (e.g., postfix in docker-compose.yml) or the container’s IP address on the Docker network.