Files

Postfix PostgreSQL Relay Image

A Postfix SMTP relay/MTA whose relay and transport decisions are driven by PostgreSQL lookup maps instead of static config. It receives inbound mail and forwards it to an internal SMTP target after checking the recipient's domain against a database — it has no knowledge of, and makes no assumptions about, what consumes the mail downstream.

This mirrors the Postfix setup described in the SimpleLogin self-hosting documentation (postfix + postfix-pgsql, a relay_domains PostgreSQL map, and a transport_maps PostgreSQL map): https://github.com/simple-login/app

No domain, hostname, or database credential is hardcoded in the image. Everything is templated from environment variables by docker-entrypoint.sh at container startup, then Postfix is started in the foreground (postfix start-fg) so it runs correctly under Docker's process supervision.

How it works

On startup, the entrypoint renders three files from templates under /etc/postfix/templates/:

  • /etc/postfix/main.cf — core Postfix configuration.
  • /etc/postfix/pgsql-relay-domains.cf — PostgreSQL lookup that tells Postfix which domains it should relay mail for.
  • /etc/postfix/pgsql-transport-maps.cf — PostgreSQL lookup that tells Postfix where to forward accepted mail (the internal SMTP relay target).

Chroot is disabled for all Postfix services via postconf -F '*/*/chroot = n' on every startup. Postfix's chrooted child processes (smtpd, trivial-rewrite, etc.) lack access to /etc/resolv.conf inside the chroot jail, so they cannot resolve Docker-internal hostnames — causing every PostgreSQL lookup to fail with 451 4.3.0 Temporary lookup failure. Disabling chroot is the standard remedy in containerised deployments.

Environment variables

Required

Variable Description Example
MAIL_DOMAIN Mail domain this relay handles (mydomain/myorigin in main.cf). example.com
MYHOSTNAME Hostname of this relay, used as Postfix's myhostname and as the MX target for MAIL_DOMAIN. mx1.example.com
POSTGRES_HOST Hostname of the PostgreSQL server holding the lookup tables. postgres
POSTGRES_PORT PostgreSQL port. 5432
POSTGRES_USER PostgreSQL user used for the lookup queries. postfix
POSTGRES_PASSWORD PostgreSQL password for POSTGRES_USER. change-me
POSTGRES_DB PostgreSQL database name. relaydb
SMTP_RELAY_HOST Internal SMTP host that accepted mail is forwarded to after the relay-domains lookup matches. app-internal
SMTP_RELAY_PORT Port of the internal SMTP relay target. 2525

Optional

Variable Description
TLS_CERT_FILE Absolute path (inside the container) to a TLS certificate to use for smtpd_tls_cert_file. If unset, a self-signed certificate is generated on first start.
TLS_KEY_FILE Absolute path (inside the container) to the private key matching TLS_CERT_FILE. Must be set together with TLS_CERT_FILE.

The PostgreSQL lookup tables are expected to expose at least a relay_domain(domain) table; both lookups query it by recipient domain.

TLS certificates

By default, if TLS_CERT_FILE/TLS_KEY_FILE are not provided, the entrypoint generates a self-signed certificate at /etc/postfix/ssl/snakeoil.{pem,key} on first start (matching the self-signed fallback used in the upstream docs) and reuses it on subsequent restarts if that path is kept on a persistent volume.

To use your own certificate instead, mount it into the container and point TLS_CERT_FILE/TLS_KEY_FILE at the mounted paths, e.g.:

docker run \
  -v /path/to/fullchain.pem:/certs/fullchain.pem:ro \
  -v /path/to/privkey.pem:/certs/privkey.pem:ro \
  -e TLS_CERT_FILE=/certs/fullchain.pem \
  -e TLS_KEY_FILE=/certs/privkey.pem \
  ...

Usage

docker run

docker run -d \
  -p 25:25 \
  -e MAIL_DOMAIN=example.com \
  -e MYHOSTNAME=mx1.example.com \
  -e POSTGRES_HOST=postgres \
  -e POSTGRES_PORT=5432 \
  -e POSTGRES_USER=postfix \
  -e POSTGRES_PASSWORD=change-me \
  -e POSTGRES_DB=relaydb \
  -e SMTP_RELAY_HOST=app-internal \
  -e SMTP_RELAY_PORT=2525 \
  gitea.arnodo.fr/damien/postfix-pgsql:latest

Docker Compose

services:
  postfix:
    image: gitea.arnodo.fr/damien/postfix-pgsql:latest
    ports:
      - "25:25"
    environment:
      MAIL_DOMAIN: example.com
      MYHOSTNAME: mx1.example.com
      POSTGRES_HOST: postgres
      POSTGRES_PORT: "5432"
      POSTGRES_USER: postfix
      POSTGRES_PASSWORD: change-me
      POSTGRES_DB: relaydb
      SMTP_RELAY_HOST: app-internal
      SMTP_RELAY_PORT: "2525"

Version management

The Debian base image tag is set in POSTFIX_VERSION (e.g. 12-slim), which also pins the postfix/postfix-pgsql package versions available via apt for that Debian release. To change it:

  1. Edit POSTFIX_VERSION with the desired Debian tag.
  2. Commit and push.
  3. The CI builds the image with that base and tags it with both latest and the value of POSTFIX_VERSION.

Reference