Tableau de bord pour Postfix
  • Go 98.9%
  • Dockerfile 1.1%
Find a file
2026-10-26 12:12:20 +01:00
.forgejo/workflows Update build-image.yaml 2026-10-26 12:12:20 +01:00
cmd/dashboard 1.5.0 2026-10-26 12:08:02 +01:00
internal 1.5.0 2026-10-26 12:08:02 +01:00
vendor 1.2.0 2026-06-04 11:48:15 +02:00
.env.example 1.5.0 2026-10-26 12:08:02 +01:00
.gitignore 1.0.0 2026-06-04 11:31:53 +02:00
CHANGELOG.md 1.5.0 2026-10-26 12:08:02 +01:00
docker-compose.prod.yml 1.5.0 2026-10-26 12:08:02 +01:00
docker-compose.yml 1.5.0 2026-10-26 12:08:02 +01:00
Dockerfile 1.5.0 2026-10-26 12:08:02 +01:00
go.mod 1.2.0 2026-06-04 11:48:15 +02:00
go.sum 1.2.0 2026-06-04 11:48:15 +02:00
README.md 1.5.0 2026-10-26 12:08:02 +01:00
VERSION 1.5.0 2026-10-26 12:08:02 +01:00

Postfix Dashboard

A self-contained web dashboard to supervise a Postfix server running in Docker. Single Go binary (Chart.js embedded, no CDN for scripts), SQLite storage, no external runtime.

Version Go

Features

Area Details
Authentication Admin login, bcrypt, hashed server-side sessions (12 h), login rate limiting
Overview KPI cards with trend arrows (vs yesterday / previous week), hourly and weekly charts
Analytics 7/30/45/90 day trends, volume, delivery rate, hour x weekday heatmap, bounce rate by domain, top senders / recipients / relays / domains
Log History 30/45+ days of deliveries: date, sender, recipient, subject, recipients count, relay, size, status, SMTP code. Search (sender, recipient, queue ID, subject) over the whole database, status filter, pagination, refresh
Live Logs Real-time WebSocket stream of the container output, colouring, filter, auto-scroll
Queue List, hold, release, delete, flush, delete all (executed in the Postfix container through the Docker API)
Settings Database statistics (records, size, oldest/newest), retention, manual purge, automatic daily purge at 02:00

Logs are read from the container output (Docker API over the socket) - no log file is needed.

Quick start

cp .env.example .env        # edit it (password, DOCKER_GID, TZ, ...)
docker compose -f docker-compose.prod.yml up -d --build
# open http://localhost:8080
  • DOCKER_GID must match the host docker group: getent group docker | cut -d: -f3.
  • TZ must be the timezone of your Postfix container (Postfix log lines carry no year or timezone).
  • If DASHBOARD_PASS is empty on the first start, a random password is generated and printed once in the container logs.

Pipeline build

A Forgejo workflow (.forgejo/workflows/docker-build.yml) builds and pushes the image on v* tags (secret REPO_TOKEN).

Other modes

# Pipe logs from stdin
docker logs -f postfix-1 2>&1 | ./postfix-dashboard -log-source stdin -db ./dash.db

# Seed from a historical log file
./postfix-dashboard -db ./dash.db -seed /var/log/mail.log

Configuration

Flag Env Default Description
-addr - :8080 Listen address
-db - /data/postfix-dashboard.db SQLite path
-log-source LOG_SOURCE stdin stdin or docker://<container>
-docker-socket - /var/run/docker.sock Docker socket path
-admin-user ADMIN_USER admin Admin username
-admin-pass ADMIN_PASS random on first run Admin password. When provided it is applied at every start and revokes existing sessions
-retention - 45 Retention in days. Only applied when passed explicitly or when nothing is stored yet; otherwise the value chosen in Settings is kept
-seed - - Log file to import at startup
-healthcheck - - Probe the local server and exit (used by the Docker HEALTHCHECK)
- TZ UTC Timezone used to read Postfix timestamps and group statistics
- TRUST_PROXY false true behind a reverse proxy you control: use X-Forwarded-For / X-Forwarded-Proto (client IP, Secure cookie)

Subject column

Postfix does not log message subjects by default. To fill the Subject column, enable header logging in Postfix:

# main.cf
header_checks = regexp:/etc/postfix/header_checks
# header_checks
/^subject:/   INFO

Without it the column shows a dash.

Security

  • Passwords: bcrypt. Constant-time username check and dummy hash to avoid user enumeration.
  • Sessions: 32 random bytes, only the SHA-256 is stored in SQLite. Cookie HttpOnly, SameSite=Strict, Secure when served over HTTPS. The token is never exposed to JavaScript.
  • Login: 5 failures per IP / 15 minutes, then HTTP 429. Failures are logged as [auth] login failed ip=... (usable by fail2ban).
  • WebSocket: single-use ticket valid 30 s (no token in URLs), same-origin check, connection cap, ping/pong timeouts.
  • CSRF: state-changing endpoints reject cross-origin Origin, JSON bodies capped at 16 KiB.
  • Headers: Content-Security-Policy, X-Frame-Options: DENY, nosniff, Referrer-Policy: no-referrer, Cache-Control: no-store on the API.
  • Queue actions: IDs validated (hex only), commands run without a shell, 30 s timeout, every action audit-logged with user and IP.
  • SQL: parameterised queries, LIKE wildcards in search are escaped.
  • Container: non-root user, cap_drop: ALL, no-new-privileges, read-only root filesystem, database file mode 0600.

Docker socket warning. Access to docker.sock is equivalent to root on the host, even when mounted :ro (the flag does not restrict the API). The dashboard only uses log streaming and exec in the Postfix container. For stricter isolation put a filtering proxy such as tecnativa/docker-socket-proxy in front (allow CONTAINERS and EXEC, POST=1) and point -docker-socket at it.

Always serve the dashboard behind HTTPS (Traefik, nginx...) and set TRUST_PROXY=true.

Layout

cmd/dashboard/      main.go (bootstrap, middleware), html.go / css.go / js.go (embedded UI), assets/chart.umd.js
internal/auth/      bcrypt, sessions, login limiter, WS tickets
internal/api/       REST + WebSocket routes
internal/parser/    Postfix log parser
internal/store/     SQLite data layer
internal/ws/        WebSocket hub
internal/queue/     postqueue / postsuper (local or Docker exec)
internal/dockerlogs Docker socket log streaming

Build and test

Requirements: Go 1.22+, gcc, libsqlite3-dev. Dependencies are vendored.

go test -mod=vendor ./...
go build -mod=vendor -o postfix-dashboard ./cmd/dashboard

Known limitations

  • Containers started with a TTY do not use Docker's multiplexed log framing and are not supported as log source.
  • The brute-force limiter and WebSocket tickets are in memory (reset on restart); run a single replica.
  • Statistics are based on delivery attempts (one row per recipient and outcome), so a message deferred then sent produces two rows.

License

MIT