# Docker Postfix SMTP Relay Source: https://git.k2patel.in/k2patel/docker-postfix `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. 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. ## Quick start ```sh git clone https://git.k2patel.in/k2patel/docker-postfix.git cd docker-postfix cp env.sample .env # Edit .env with your provider credentials and domain. docker compose pull docker compose up -d ``` To build locally, run `docker compose build` first. Builds include isolated SMTP relay tests; no test messages are sent to external mailboxes. Configure applications to connect to `postfix-relay:25` on the same Docker network. Only trusted networks should be allowed to relay. ## Configuration | 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 | 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. 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. 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. ## Updating an existing deployment 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 healthcheck: test: ["CMD", "/usr/local/bin/postfix-entrypoint", "--healthcheck"] ``` 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. ## Operations ```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 ``` `--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. Host-side helpers work without a shell inside the container: ```sh cd test make help make status make queue 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 ``` Submission to the queue is not proof of delivery. Check logs for `status=sent` and inspect deferred messages with `postqueue -p`. ## Build and CI 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. 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. 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. ## License and support MIT License; see [LICENSE](LICENSE). Issues: https://git.k2patel.in/k2patel/docker-postfix/issues