Publish Alpine musl distroless Postfix as the sole latest image
Build and Push Docker Image / build (push) Successful in 1m25s

This commit is contained in:
Ketan Patel committed 2026-10-03 00:17:09 -04:00
1 parent 2a9a7c6baa
commit 7ead00647b
12 files changed
+776 -1274

No files matched your search

+94 -427
View File
@@ -1,465 +1,132 @@
# Docker Postfix SMTP Relay
A lightweight Postfix SMTP relay container supporting multiple email service providers including Maileroo, Mailtrap, SendGrid, and any generic SMTP server.
Source: https://git.k2patel.in/k2patel/docker-postfix
The image uses Alpine 3.24 (musl libc) and is published as `k2patel/postfix:latest`.
Builds run on Gitea for pushes to `main`, monthly, or by manual dispatch.
`k2patel/postfix:latest` is the single maintained image: a distroless SMTP relay
built from Alpine 3.24/musl into a `scratch` runtime. It supports Maileroo,
Mailtrap, SendGrid, Gmail, Office 365, and generic upstream SMTP servers.
There is no shell, BusyBox, package manager, Supervisor, or compiler in the image.
## Features
A native launcher configures Postfix, creates queue directories on fresh volumes,
builds the SASL credential map, and starts Postfix in the foreground. Logs go to
container stdout. Native Postfix tools remain available for queue administration.
- 🚀 Multi-provider support (Maileroo, Mailtrap, SendGrid, Gmail, Office365, any SMTP)
- 🔒 TLS/SSL encryption
- 🌐 Auto-detects /16 subnet for relay
- 📝 All logs to stderr (`docker logs`)
- 🏥 Built-in health checks
- 🔧 Simple Makefile-based operations
## Quick start
## Quick Start
### 1. Clone and Configure
```bash
```sh
git clone https://git.k2patel.in/k2patel/docker-postfix.git
cd docker-postfix
cp env.sample .env
nano .env # Edit with your SMTP provider details
# Edit .env with your provider credentials and domain.
docker compose pull
docker compose up -d
```
### 2. Build and Start
To build locally, run `docker compose build` first. Builds include isolated
SMTP relay tests; no test messages are sent to external mailboxes.
```bash
cd test
make build
make up
```
### 3. Test
```bash
cd test
make test TO=your@email.com
```
Configure applications to connect to `postfix-relay:25` on the same Docker
network. Only trusted networks should be allowed to relay.
## Configuration
### Environment Variables
| Variable | Default | Purpose |
|----------|---------|---------|
| `SMTP_SERVER` | Required | Upstream SMTP hostname |
| `SMTP_PORT` | `587` | Upstream SMTP port |
| `SMTP_USERNAME` | Required | Upstream SMTP username |
| `SMTP_PASSWORD` | Required | Upstream SMTP password or token |
| `DOMAIN` | Hostname suffix, otherwise `localdomain` | Domain for outgoing mail; set explicitly |
| `SERVER_HOSTNAME` | Container hostname | Relay hostname; set to a valid FQDN |
| `TIMEZONE` | `America/New_York` | IANA timezone |
| `LOCAL_NETWORK` | Detected IPv4 interface subnet | Trusted local CIDR |
| `SMTP_NETWORKS` | Empty | Additional trusted IPv4 CIDRs, comma-separated |
| `SMTP_HEADER_TAG` | Empty | Optional `RelayTag` header value |
| `SMTP_LISTEN_PORT` | `25` | Host port in Compose |
| `DATA_FOLDER` | Current directory | Compose bind-mount base for logs/mail/spool |
| `DEBUG` | `no` | Enable Postfix debug level 2 |
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `SMTP_SERVER` | Yes | - | SMTP server hostname |
| `SMTP_PORT` | Yes | 587 | SMTP server port |
| `SMTP_USERNAME` | Yes | - | SMTP username |
| `SMTP_PASSWORD` | Yes | - | SMTP password |
| `DOMAIN` | Yes | - | Domain for outgoing mail |
| `SERVER_HOSTNAME` | No | Auto | Server FQDN |
| `TIMEZONE` | No | America/New_York | Timezone |
| `SMTP_NETWORKS` | No | Auto /16 | Additional networks (comma-separated CIDR) |
| `SMTP_LISTEN_PORT` | No | 25 | Port to expose on host |
| `LOCAL_NETWORK` | No | Auto-detected | Override local network CIDR (e.g., 192.168.0.0/16) |
| `DEBUG` | No | no | Enable debug logging |
Localhost and the local subnet are trusted. Invalid CIDRs and multiline
configuration values are rejected. Credentials are stored in root-only files;
the launcher does not print their contents.
### Provider Examples
Maileroo, Mailtrap, and SendGrid require STARTTLS. Generic providers retain
opportunistic STARTTLS (`smtp_tls_security_level=may`). Use the provider's
STARTTLS submission port, typically 587; implicit TLS on port 465 is not configured.
#### Maileroo
```bash
SMTP_SERVER=smtp.maileroo.com
SMTP_PORT=587
SMTP_USERNAME=noreply@example.com
SMTP_PASSWORD=your_password
DOMAIN=example.com
```
Provider examples are included in `env.sample`. For SendGrid, use the literal
username `apikey`. Gmail requires an app password. Keep credentials in `.env`,
which is excluded from Git and the Docker build context.
#### Mailtrap
```bash
SMTP_SERVER=live.smtp.mailtrap.io
SMTP_PORT=587
SMTP_USERNAME=your_username
SMTP_PASSWORD=your_password
DOMAIN=example.com
```
## Updating an existing deployment
#### SendGrid
```bash
SMTP_SERVER=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USERNAME=apikey
SMTP_PASSWORD=your_api_key
DOMAIN=example.com
```
**Note**: For SendGrid, username must be `apikey` (literal string).
#### Gmail
```bash
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your_email@gmail.com
SMTP_PASSWORD=your_app_password
DOMAIN=gmail.com
```
## Makefile Commands
All operations are handled through the Makefile in the `test/` directory.
### Setup Commands
```bash
cd test
make build # Build the Docker image
make up # Start the container
make down # Stop the container
make restart # Restart the container
```
### Monitoring Commands
```bash
make logs # View logs (follow mode)
make logs-tail # View last 100 lines
make status # Show container and Postfix status
make queue # View mail queue
make health # Check container health
```
### Maintenance Commands
```bash
make shell # Open shell in container
make config # Show Postfix configuration
make test # Send test email (auto-detects container)
make flush # Flush mail queue
make check # Validate Postfix configuration
```
### Cleanup Commands
```bash
make clean # Remove container and volumes
make clean-all # Remove everything including images
```
### Advanced Commands
```bash
make debug # Start with debug mode enabled
make validate # Validate .env file
make queue-delete # Delete all messages in queue
make stats # Show real-time container stats
```
### Full Command List
Run `make help` or just `make` to see all available commands:
```bash
cd test
make
```
## Network Configuration
The container automatically allows relay from:
- Localhost (127.0.0.0/8)
- Auto-detected /16 subnet
To add custom networks:
```bash
SMTP_NETWORKS=192.168.1.0/24,10.0.0.0/8
```
## Using with Applications
Configure your application to use the relay:
Use `k2patel/postfix:latest`, pull, and recreate the container. Existing environment
settings and `/var/spool/postfix` persistence are retained. The default Compose
file supplies the native health check. Replace an old custom `postfix status`
or shell-based health check with:
```yaml
services:
your-app:
environment:
MAIL_HOST: postfix-relay
MAIL_PORT: 25
depends_on:
- postfix
healthcheck:
test: ["CMD", "/usr/local/bin/postfix-entrypoint", "--healthcheck"]
```
Connection details:
- **Host**: `postfix-relay`
- **Port**: `25`
- **Authentication**: Not required for local network
The check connects to localhost SMTP and requires a `220` greeting. The old
`postfix status`, `postfix check`, shell access, and `mail`/`mailx` commands are
not part of this image. Use native commands below. Custom pipe/alias delivery
commands that need a shell or external utilities are unsupported.
## Testing
## Operations
### Quick Test (Recommended)
```sh
docker logs -f postfix-relay
docker exec postfix-relay /usr/local/bin/postfix-entrypoint --healthcheck
docker exec postfix-relay /usr/local/bin/postfix-entrypoint --check
docker exec postfix-relay postconf -n
docker exec postfix-relay postqueue -p
docker exec postfix-relay postqueue -f
docker compose restart postfix
```
The easiest way to test your Postfix relay:
`--check` parses and displays the active main and master configuration; startup
also checks queue structure using `postsuper`. It does not validate upstream
credentials or external delivery.
```bash
Host-side helpers work without a shell inside the container:
```sh
cd test
make test TO=recipient@example.com
```
**That's it!** No configuration needed.
### What Gets Auto-Detected?
The test script automatically finds and configures:
| Feature | What Happens | Override Option |
|---------|--------------|-----------------|
| **Container Name** | Finds containers using `docker-postfix-postfix` image | Specify as first arg |
| **FROM Address** | Uses `noreply@DOMAIN` from container env | `FROM=email@domain.com` |
| **SMTP Port** | Detects mapped port (e.g., `25` or custom) | N/A (always detected) |
| **Host IP** | Converts `0.0.0.0` → `127.0.0.1` for local testing | N/A (always detected) |
| **Subject** | Defaults to "Test Email from Postfix Relay" | `SUBJECT="Your Subject"` |
**No manual configuration required** - just provide the recipient email!
### Advanced Testing Options
**With custom FROM address:**
```bash
make test TO=user@example.com FROM=noreply@mydomain.com
```
**With custom subject:**
```bash
make test TO=user@example.com FROM=sender@domain.com SUBJECT="My Test Email"
```
**Using the test script directly:**
```bash
./test-email.sh user@example.com
./test-email.sh user@example.com sender@mydomain.com
./test-email.sh user@example.com sender@mydomain.com "Custom Subject"
```
**Manual container specification (if auto-detection fails):**
```bash
./test-email.sh my-container user@example.com sender@domain.com
```
### Test Script Features
The `test-email.sh` script automatically:
- Finds containers using `docker-postfix-postfix` image
- Detects port mapping and converts `0.0.0.0` to `127.0.0.1`
- Uses proper SMTP protocol via sendmail
- Falls back to mail command if needed
- Shows queue status after sending
- Provides colored output for easy reading
### Validation Tests
```bash
cd test
make check # Validate Postfix configuration
make validate # Validate .env file
make queue # View mail queue
make config # Show current Postfix config
```
## Troubleshooting
### Email Not Received
**1. Check container logs:**
```bash
cd test
make logs-tail # Last 100 lines
make logs # Follow in real-time
```
**2. Check mail queue:**
```bash
make queue # View queued messages
make flush # Force processing
```
**3. Verify configuration:**
```bash
make config # Show Postfix config
make check # Validate config
make validate # Validate .env file
```
### SMTP Protocol Errors
If you see "improper command pipelining" errors:
**Use the test script** (handles protocol correctly):
```bash
./test-email.sh user@example.com sender@domain.com
```
**Verify FROM domain matches DOMAIN setting:**
```bash
# In .env file
DOMAIN=mydomain.com
# Use matching FROM address
make test TO=user@example.com FROM=noreply@mydomain.com
```
**Check mynetworks configuration:**
```bash
docker exec postfix-relay postconf mynetworks
```
### Container Not Auto-Detected
**1. Verify container is running:**
```bash
docker ps | grep postfix
```
**2. Start the container:**
```bash
cd test
make up
```
**3. Manually specify container:**
```bash
./test-email.sh my-container-name user@example.com
```
### Enable Debug Mode
For detailed SMTP transaction logs:
```bash
cd test
make debug
```
### Emails Stuck in Queue
```bash
cd test
make queue # View queue
make flush # Force processing
make queue-delete # Delete all (with confirmation)
```
### Network Configuration Issues
**Override auto-detected network:**
```bash
# In .env file
LOCAL_NETWORK=192.168.1.0/24
```
**Add additional networks:**
```bash
SMTP_NETWORKS=10.0.0.0/8,172.16.0.0/12
```
### Container Health Issues
```bash
cd test
make status # Check status
make health # Check health status
make restart # Restart container
make down # Stop container
make up # Start fresh
```
## Examples
### Testing Examples
**Simple test with Gmail:**
```bash
cd test
make test TO=youraddress@gmail.com
# FROM will auto-detect from DOMAIN in .env
```
**Test with custom FROM and subject:**
```bash
make test TO=client@example.com FROM=support@mycompany.com SUBJECT="Production Test"
```
**Test from command line:**
```bash
./test-email.sh user@example.com noreply@mydomain.com "Hello World"
```
**Verify email was sent:**
```bash
make queue # Should show empty queue if sent
make logs-tail # Check for "status=sent"
```
### Basic Workflow
```bash
# Initial setup
cd docker-postfix
cp env.sample .env
nano .env
# Build and start
cd test
make build
make up
# Check status
make help
make status
make logs
# Test email delivery
make test TO=your@email.com
# Stop
make down
```
### Development Workflow
```bash
cd test
# Start with debug
make debug
# Check logs in another terminal
make logs
# Restart after changes
make restart
# Clean up
make clean
```
### Monitoring Workflow
```bash
cd test
# Check everything
make status
make health
make queue
make logs-tail
# Continuous monitoring
make logs
make check
# Sends a real email only when you explicitly run this command:
make test TO=recipient@example.com
# Optional sender, subject, or container:
make test TO=recipient@example.com FROM=sender@example.com SUBJECT='Relay test' CONTAINER=postfix-relay
```
## Security
Submission to the queue is not proof of delivery. Check logs for `status=sent`
and inspect deferred messages with `postqueue -p`.
- Store credentials in `.env` (never commit to git)
- Limit relay to trusted networks only
- TLS automatically enabled for known providers
- Use app-specific passwords for Gmail
## Build and CI
## License
Gitea Actions builds `linux/amd64` on the Kubernetes runner for pushes to `main`,
monthly on the first day, and manual dispatch. Configure repository secrets
`DOCKER_USER` and `DOCKER_TOKEN` for Docker Hub publishing. Each build uses a
temporary Buildx builder and removes its cache afterward.
MIT License - See [LICENSE](LICENSE) file for details.
The final filesystem contains Postfix and its musl/shared-library dependencies,
SASL and database plugins, ICU data, CA certificates, timezone data, and the
native launcher. Go, Python, and packaging tools are confined to the build stage.
## Support
Before publication, a separate stage tests the exact shell-free filesystem:
startup with an empty queue and stale PID file, native health checks, SMTP
submission, STARTTLS and authenticated relay to a local mock server, header
insertion, LMDB/PCRE plugins, queue draining, restart, and SIGTERM shutdown.
The final image is copied from the pristine runtime, excluding test credentials
and queue contents.
For issues and questions, please open an issue on [Gitea](https://git.k2patel.in/k2patel/docker-postfix/issues).
## License and support
MIT License; see [LICENSE](LICENSE).
Issues: https://git.k2patel.in/k2patel/docker-postfix/issues