Minimal, secure Alpine-based Postfix SMTP relay. Zero CVEs, weekly rebuilt.
1.3K
Minimal, secure Alpine-based Postfix SMTP relay for Docker. Designed as a drop-in replacement for the almost perfect boky/postfix with zero known HIGH or CRITICAL vulnerabilities and automated weekly rebuilds.
Most Postfix relay images are built on Debian or stale Alpine bases, accumulating CVEs over time while waiting for maintainer rebuilds. This image:
postfix start-fg as PID 1 — proper Docker signal handlingdocker logs out of the boxservices:
smtp-relay:
image: arcclaro/postfix-relay:latest
container_name: smtp-relay
restart: unless-stopped
environment:
ALLOWED_SENDER_DOMAINS: "example.com"
RELAYHOST: "smtp.example.com:587"
RELAYHOST_USERNAME: "[email protected]"
RELAYHOST_PASSWORD: "your-smtp-password"
ports:
- "127.0.0.1:2525:587"
Send a test email:
swaks --to [email protected] \
--from [email protected] \
--server 127.0.0.1 \
--port 2525 \
--body "Test email"
| Variable | Required | Default | Description |
|---|---|---|---|
ALLOWED_SENDER_DOMAINS | ✅* | — | Space-separated list of permitted sender domains |
ALLOW_EMPTY_SENDER_DOMAINS | — | — | Set to any value to bypass sender domain restriction |
RELAYHOST | — | — | Upstream relay as host:port. If unset, delivers directly |
RELAYHOST_USERNAME | — | — | SASL username for relay authentication |
RELAYHOST_PASSWORD | — | — | SASL password for relay authentication |
RELAYHOST_TLS_LEVEL | — | encrypt | TLS level for relay connection (encrypt, may, none) |
MASQUERADE_DOMAINS | — | — | Rewrite sender domain (e.g. hide internal hostnames) |
MASQUERADE_EXCEPTIONS | — | root | Accounts excluded from masquerading |
MESSAGE_SIZE_LIMIT | — | 26214400 (25MB) | Maximum message size in bytes |
MYNETWORKS | — | RFC1918 ranges | Networks allowed to relay without authentication |
HOSTNAME | — | reverse DNS or postfix-relay | Postfix myhostname |
INET_PROTOCOLS | — | ipv4 | IP protocols to use (ipv4, ipv6, all) |
SMTP_HEADER_CHECKS | — | — | Path to header checks file, or 1 for default location |
INBOUND_DEBUGGING | — | — | Set to any value to enable verbose debug logging |
*Required unless ALLOW_EMPTY_SENDER_DOMAINS is set.
| Port | Description |
|---|---|
587 | Submission (primary) |
25 | SMTP (also available inside container) |
services:
smtp-relay:
image: arcclaro/postfix-relay:latest
container_name: smtp-relay
restart: unless-stopped
hostname: smtp.example.com
environment:
ALLOWED_SENDER_DOMAINS: "example.com example.org"
RELAYHOST: "mail.example.com:587"
RELAYHOST_USERNAME: "[email protected]"
RELAYHOST_PASSWORD: "your-mailbox-password"
MASQUERADE_DOMAINS: "example.com"
MESSAGE_SIZE_LIMIT: "52428800"
ports:
- "127.0.0.1:2525:587"
networks:
- smtp-network
networks:
smtp-network:
driver: bridge
services:
smtp-relay:
image: arcclaro/postfix-relay:latest
container_name: smtp-relay
restart: unless-stopped
environment:
ALLOWED_SENDER_DOMAINS: "example.com"
MESSAGE_SIZE_LIMIT: "26214400"
ports:
- "127.0.0.1:2525:587"
Rewrite the From display name for all outbound mail regardless of the sending service:
services:
smtp-relay:
image: arcclaro/postfix-relay:latest
container_name: smtp-relay
restart: unless-stopped
environment:
ALLOWED_SENDER_DOMAINS: "example.com"
RELAYHOST: "mail.example.com:587"
RELAYHOST_USERNAME: "[email protected]"
RELAYHOST_PASSWORD: "your-password"
SMTP_HEADER_CHECKS: "regexp:/etc/postfix/smtp_header_checks"
volumes:
- ./smtp_header_checks:/etc/postfix/smtp_header_checks:ro
ports:
- "127.0.0.1:2525:587"
smtp_header_checks:
# Rewrite all senders to a single display name
/^From:.*@example\.com/ REPLACE From: My App <[email protected]>
/^From:.*@example\.org/ REPLACE From: My App <[email protected]>
environment:
INET_PROTOCOLS: "all" # ipv4, ipv6, or all
Internal services connect to the relay on the shared network without authentication:
services:
smtp-relay:
image: arcclaro/postfix-relay:latest
container_name: smtp-relay
restart: unless-stopped
environment:
ALLOWED_SENDER_DOMAINS: "example.com"
RELAYHOST: "mail.example.com:587"
RELAYHOST_USERNAME: "[email protected]"
RELAYHOST_PASSWORD: "your-password"
networks:
- smtp-network
my-app:
image: my-app:latest
environment:
SMTP_HOST: smtp-relay
SMTP_PORT: 587
networks:
- smtp-network
networks:
smtp-network:
driver: bridge
Mount scripts into /docker-init.d/ — they run before Postfix starts:
volumes:
- ./custom-postfix.sh:/docker-init.d/custom-postfix.sh:ro
custom-postfix.sh:
#!/bin/sh
postconf -e "address_verify_negative_cache=yes"
Services on the same Docker network connect without authentication:
| Setting | Value |
|---|---|
| SMTP host | smtp-relay (container name) |
| SMTP port | 587 |
| Username | (empty) |
| Password | (empty) |
| TLS | (disabled — internal network) |
Every build runs Trivy against the published image. The workflow fails if any CRITICAL or HIGH CVE is found, preventing vulnerable images from reaching Docker Hub.
# Scan manually
trivy image arcclaro/postfix-relay:latest --severity HIGH,CRITICAL
| Tag | Description |
|---|---|
latest | Latest build from main branch |
1, 1.0, 1.0.0 | Versioned releases |
MIT
Content type
Image
Digest
sha256:4fdd3aecd…
Size
12 MB
Last updated
2 days ago
docker pull arcclaro/postfix-relay