137 lines
5.1 KiB
Markdown
137 lines
5.1 KiB
Markdown
# 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.:
|
|
|
|
```bash
|
|
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`
|
|
|
|
```bash
|
|
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
|
|
|
|
```yaml
|
|
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
|
|
|
|
- Upstream Postfix + PostgreSQL setup this image is based on:
|
|
https://github.com/simple-login/app
|