# Hermetic e2e stand override for ../../deploy/docker-compose.yml (plan C.4). # # Applied as a second -f on top of the SHIPPED compose file, not a separate # test compose — the whole point is to exercise cap_drop/cap_add/ # no-new-privileges exactly as documented, not a laxer stand-in. Run only via # `make e2e`, which also sets --project-directory to a scratch stage # directory so the base file's relative ./data and ./certs bind mounts land # there instead of next to a real deployment, and -p selfpost-e2e so the # containers/network never collide with a prod stack on the same host. # # `ports:` and `networks:` use the compose-spec `!override` merge tag to fully # replace the base file's list rather than concatenate with it (the default # merge behaviour for sequences) — otherwise the real 465/587/8080 would stay # published here too and collide with a prod container on the same host. # (docs.docker.com/reference/compose-file/merge/, "!override": "fully replace # an attribute, bypassing the standard merge rules". The page names no # minimum Compose version; the stand's own Compose is what each run uses.) services: selfpost: # Crash-looping with unless-stopped makes `compose exec` fail with # "Container is restarting" and hides the entrypoint/supervisord exit # reason; keep the stand exited so logs stay attached to one attempt. restart: "no" build: # Resolved relative to --project-directory (.stage), NOT this file's own # directory: with --project-directory, build.context and bind-mount # volume paths alike resolve against it - which is why ./data, ./certs # and the ./dns-stage and ./mail-stage below land in .stage # (docs.docker.com/compose/how-tos/multiple-compose-files/merge/, "Paths # are evaluated relative to the base file"; assumptions C-K8). context: ../../.. dockerfile: build/Dockerfile args: # release.yml sets SELFPOST_E2E_VERSION to the release's X.Y.Z, so the # image this build tags selfpost:e2e - the one release.yml then pushes - # carries the release's version, not "e2e". A local `make e2e` leaves it # unset. The suite checks the stamp (image_stamped_with_version). VERSION: ${SELFPOST_E2E_VERSION:-e2e} image: selfpost:e2e volumes: # Where certs/ links lead when it is laid out as the guide has it with # certbot (certbot_layout_followed): certbot's directory, mounted at the # same path as on a host. Empty until that test fills it. Added to the # base file's volumes (a new target). - ./letsencrypt:/etc/letsencrypt:ro environment: SELFPOST_HOSTNAME: mail.e2e.test # Plain HTTP panel for the test client — no reverse proxy in this stand. PANEL_COOKIE_SECURE: "false" # The panel's deliverability checks query resolvers directly rather than # the container's own (see internal/dnscheck), so the fake zone has to be # named here too — `dns:` below only redirects the system resolver. SELFPOST_DNS_RESOLVERS: 10.77.0.10:53 # High enough that the whole suite's other ~10 sends (positive path, # level-2 test, the various rejected attempts) never trip it by # accident — only the dedicated level-1 test (negative check 4, which # loops well past this count) is meant to hit it. RATE_LIMIT_MESSAGES_PER_IP: "50" RATE_LIMIT_WINDOW_SECONDS: "60" # cert-reload compares the mounted certificate with Postfix's copy this # often, so renewed_certificate_picked_up does not wait a day. It # reloads Postfix only when the certificate changed. TLS_RELOAD_INTERVAL_SECONDS: "2" # The wrapper gives up on the milter sockets after this long, and # program postfix's startsecs is this plus ten (build/entrypoint.sh): # supervision_stops_postfix_and_gives_up waits for one give-up and # every start for startsecs, so the stand keeps both short. MILTER_WAIT_TIMEOUT: "10" ports: !override - "20465:465" - "20587:587" - "20025:25" - "127.0.0.1:20080:8080" dns: - 10.77.0.10 networks: !override e2e: depends_on: - coredns - sink # Fake authoritative DNS for e2e.test (plan C.4): serves the DKIM/host # records the test harness publishes, so Postfix's outbound MX lookup for # the sink and the panel's own dnscheck resolve inside the isolated network # instead of hitting the real internet. # # Pinned to a release tag rather than `latest`, so a CoreDNS upload cannot # change what the release gate runs against between two runs of the same # commit. Not a digest: the tag is a multi-arch manifest and the stand has to # come up on arm64 developer machines as well as amd64 CI. coredns: image: coredns/coredns:1.14.6 command: ["-conf", "/dns/Corefile"] volumes: - ./dns-stage:/dns networks: e2e: ipv4_address: 10.77.0.10 ports: - "20053:53/udp" # Sink-MX: accepts any inbound mail and dumps each transaction to its own # file under ./mail-stage, which the harness reads directly to check the # DKIM-Signature header the panel's signer produced. sink: build: context: ../sink volumes: - ./mail-stage:/mail # dkimverify looks the DKIM keys up in the fake zone. dns: - 10.77.0.10 networks: e2e: ipv4_address: 10.77.0.11 networks: e2e: ipam: config: - subnet: 10.77.0.0/24