guide.md gains Notifications: the four things, when each is said and said again, who is told, the two switches, and the two DNS records of the server's own name that make the mail arrive. architecture.md: the notifier as the fifth role, its readings, state, recipients and sending; the server's own key in OpenDKIM's tables; non_smtpd_milters. security.md: the panel submits mail of its own - how, to whom, and what text from outside can reach a body. That text - a queued message's sender, a DNS finding - is reduced to printable ASCII and cut before it is printed. Refused mail is looked for every minute instead of every five: it is one indexed query, and a refusal is mail not being sent now. The image's note on the panel's postdrop membership no longer calls it read-side only. go test ./internal/notify passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 lines
5.8 KiB
Docker
128 lines
5.8 KiB
Docker
# syntax=docker/dockerfile:1
|
|
#
|
|
# SelfPost — single Debian-slim image running postfix + opendkim + panel under
|
|
# supervisord. Build from the repository root:
|
|
#
|
|
# docker build -f build/Dockerfile -t selfpost:dev --build-arg VERSION=dev .
|
|
|
|
# ---- build stage -------------------------------------------------------------
|
|
FROM golang:1.26-bookworm AS build
|
|
|
|
WORKDIR /src
|
|
|
|
# Version stamped into both binaries; MUST match the image tag.
|
|
ARG VERSION=dev
|
|
|
|
# Module metadata first for layer caching.
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
COPY cmd ./cmd
|
|
COPY internal ./internal
|
|
|
|
ENV CGO_ENABLED=0
|
|
RUN go vet ./... \
|
|
&& go build -trimpath \
|
|
-ldflags "-X github.com/mixeme/selfpost/internal/buildinfo.Version=${VERSION}" \
|
|
-o /out/panel ./cmd/panel \
|
|
&& go build -trimpath \
|
|
-ldflags "-X github.com/mixeme/selfpost/internal/buildinfo.Version=${VERSION}" \
|
|
-o /out/selfpost-backup ./cmd/selfpost-backup
|
|
|
|
# ---- runtime stage -----------------------------------------------------------
|
|
FROM debian:bookworm-slim AS runtime
|
|
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
|
|
# Preseed Postfix so its install is non-interactive and yields a working
|
|
# main.cf. The real relay configuration is generated by the panel.
|
|
RUN echo "postfix postfix/mailname string localhost" | debconf-set-selections \
|
|
&& echo "postfix postfix/main_mailer_type string Internet Site" | debconf-set-selections \
|
|
&& apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
postfix \
|
|
opendkim \
|
|
opendkim-tools \
|
|
sasl2-bin \
|
|
libsasl2-modules \
|
|
db-util \
|
|
supervisor \
|
|
logrotate \
|
|
ca-certificates \
|
|
curl \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Unprivileged user for the panel process.
|
|
RUN useradd --system --no-create-home --shell /usr/sbin/nologin panel
|
|
|
|
# Shared group bridging the unprivileged services: the panel generates
|
|
# per-domain DKIM keys, application SASL accounts (sasldb2) and the Postfix
|
|
# sender map, while OpenDKIM and Postfix (different users) must read them.
|
|
# Membership in this group — plus setgid dirs under /data (set up in
|
|
# entrypoint.sh) — lets OpenDKIM read the panel-owned keys and lets Postfix read
|
|
# the sasldb2/sender map, and lets the panel reach the supervisor control socket
|
|
# to signal OpenDKIM/Postfix reloads without any process running as root.
|
|
# The id is fixed, and it is the one the group has always had: an operator
|
|
# gives the TLS private key on the host to this group by number, so that the
|
|
# panel can put it into a full backup (guide § Taking a full backup).
|
|
RUN groupadd --system --gid 998 selfpost \
|
|
&& usermod -aG selfpost panel \
|
|
&& usermod -aG selfpost opendkim \
|
|
&& usermod -aG selfpost postfix
|
|
|
|
# The panel reads the mail queue with `postqueue -p` (and the status page's
|
|
# queue card). postqueue is setgid postdrop, which normally gives it the group
|
|
# needed to reach Postfix's showq socket — but the documented deployment runs
|
|
# with `no-new-privileges`, which disables setgid transitions, so the panel
|
|
# would always see "Permission denied". Making `panel` a real member of postdrop
|
|
# grants the same access without relying on a setgid escalation the hardening
|
|
# deliberately forbids. The same membership is what lets the panel submit its
|
|
# own notifications with sendmail(1) - postdrop, too, is setgid and would fail
|
|
# to write the maildrop directory (internal/notify/mailer.go). It does not let
|
|
# the panel bypass any Postfix restriction that a local user does not already
|
|
# have through the world-executable sendmail.
|
|
RUN usermod -aG postdrop panel
|
|
|
|
# Runtime directories: milter sockets and the consolidated persistent root.
|
|
RUN mkdir -p /run/opendkim /run/selfpost /data \
|
|
&& chown opendkim:opendkim /run/opendkim \
|
|
&& chown panel:panel /run/selfpost /data
|
|
|
|
COPY --from=build /out/panel /usr/local/bin/panel
|
|
COPY --from=build /out/selfpost-backup /usr/local/bin/selfpost-backup
|
|
|
|
# Licence text shipped with the image (AGPL-3.0 conveyance). The panel also
|
|
# serves the same text at /license from an embedded copy. OFL.txt is the SIL
|
|
# Open Font License 1.1 that must travel with the IBM Plex WOFF2 files
|
|
# embedded in the panel binary (NOTICE).
|
|
COPY --chmod=0644 LICENSE NOTICE /usr/share/doc/selfpost/
|
|
COPY --chmod=0644 internal/web/view/static/OFL.txt /usr/share/doc/selfpost/OFL.txt
|
|
|
|
# File modes are pinned here so a build context copied from a checkout without
|
|
# POSIX permissions (e.g. Windows tar sync) cannot land group-writable config
|
|
# that logrotate would silently ignore — see docs/development.md § Building
|
|
# binaries and the image.
|
|
COPY --chmod=0644 build/opendkim.conf /etc/opendkim.conf
|
|
COPY --chmod=0644 build/logrotate-mail.conf /etc/logrotate.d/mail
|
|
COPY --chmod=0755 build/postfix-wrapper.sh /usr/local/bin/postfix-wrapper.sh
|
|
COPY --chmod=0755 build/postfix-config.sh /usr/local/bin/postfix-config.sh
|
|
COPY --chmod=0755 build/postfix-cert-reload.sh /usr/local/bin/postfix-cert-reload.sh
|
|
COPY --chmod=0755 build/logrotate-loop.sh /usr/local/bin/logrotate-loop.sh
|
|
COPY --chmod=0755 build/crashexit.py /usr/local/bin/crashexit.py
|
|
COPY --chmod=0755 build/entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
COPY --chmod=0644 build/supervisord.conf /etc/supervisor/supervisord.conf
|
|
|
|
# Published submission ports: 465 (smtps, primary) and 587 (submission, optional),
|
|
# inbound SMTP on 25 when INBOUND_RELAY_ENABLE=true, plus the panel on 8080.
|
|
# Outbound delivery dials remote MXs on 25 as a client even when inbound is off.
|
|
EXPOSE 8080 465 587 25
|
|
|
|
# Liveness probe: panel HTTP plus mail-path processes (opendkim, panel, postfix).
|
|
# Does not verify TLS, DNS, or end-to-end delivery — see docs/guide.md Operations.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \
|
|
CMD curl -fsS http://127.0.0.1:8080/healthz || exit 1
|
|
|
|
# The entrypoint fixes /data ownership (bind mount) as root, then execs
|
|
# supervisord, which becomes PID 1 and owns process supervision.
|
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|