mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 10:04:18 +00:00
The stack was nine long-running containers: the web server, the database, the Mercure hub, and six of plMail's own image, one per background process. It is three now: php, worker and database, with secrets-init still running once and exiting. The worker runs `app:work`, a small supervisor keeping seven processes alive the way Docker's restart policy kept seven containers alive: the IMAP supervisor, one consumer per queue, the scheduler and the hub. A clean exit (the hourly recycle, the restart signal) comes straight back; a crash backs off from one second to thirty. Output passes through a whole line at a time, so two processes' JSON records never interleave, and SIGTERM reaches every child at once with 25s to finish. Each queue keeps a process of its own, which is the part the split was for: a consumer inside a long handler cannot take a send. And each process keeps its container's old name as APP_CONTAINER_NAME, so heartbeats, /healthz and the log filters are unchanged. The hub is the official Mercure 1.x binary, copied into the image and run as one of those processes. Not FrankenPHP's built-in hub, which is 0.24 and refuses the RFC 9068 tokens plMail has minted since v0.2.31. The worker answers to `mercure` on the network, so the Caddyfile proxy and MERCURE_URL reach it where they reached the hub container, and older compose files keep working with this image unchanged. It is configured from the application's own settings, including MERCURE_COOKIE_NAME, so a plain-HTTP install names the cookie once for both halves. A 5s shutdown grace stops it waiting on open subscriptions, which made every stop take the whole 25s. The dev override loses its per-worker blocks, one of which (worker-bulk) had been missing, so that queue ran the published image's code in development. The demo overlay runs app:work --without=imap-supervisor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
4.1 KiB
YAML
82 lines
4.1 KiB
YAML
# A public demo, on top of the published image.
|
|
#
|
|
# SERVER_NAME=demo.example.com \
|
|
# docker compose -f compose.yaml -f compose.demo.yaml up -d --wait
|
|
#
|
|
# Layers over compose.yaml, not over compose.prod.yaml: that one builds the
|
|
# image from source, and a demo has no reason to. Everything here is either the
|
|
# demo switch or a consequence of it.
|
|
#
|
|
# APP_DEMO_MODE is hardcoded rather than interpolated, and that is the whole
|
|
# reason this file exists instead of a line in a README. `docker compose` reads
|
|
# the `.env` beside it — Symfony's, committed — and that file says
|
|
# APP_DEMO_MODE=0. A `${APP_DEMO_MODE:-1}` here would therefore resolve to 0 for
|
|
# every operator who cloned the repo, and the stack would come up looking
|
|
# perfectly healthy and not be a demo at all: no /demo route, no button, and
|
|
# real IMAP sync attempts against accounts pointing at example.com. That is the
|
|
# same trap APP_ENV fell into once — see ComposeEnvironmentTest, which holds
|
|
# this file to the rule too.
|
|
#
|
|
# Before putting one on the internet: give it its own APP_SECRET and
|
|
# APP_ENCRYPTION_KEY and never reuse them from an install holding real mail.
|
|
# The encryption key is what reads mailbox passwords, and a box where anyone can
|
|
# log in is the last place a real one should exist. Left unset, secrets-init
|
|
# mints fresh ones into the demo's own volume, which is what you want.
|
|
#
|
|
# See docs/install/demo-mode.md.
|
|
|
|
# APP_ENV is spelled out beside the demo switch rather than inherited, for the
|
|
# reason ComposeEnvironmentTest gives: a file that configures the app services
|
|
# should say which environment it is configuring them for, so that it cannot be
|
|
# read as "whatever the base happened to say".
|
|
x-demo-env: &demo-env
|
|
APP_ENV: prod
|
|
APP_DEMO_MODE: "1"
|
|
# Safe to interpolate: `.env` sets exactly this value, so an operator who
|
|
# never heard of the variable gets the documented default either way.
|
|
APP_DEMO_TTL: ${APP_DEMO_TTL:-PT2H}
|
|
|
|
# The legal notice. Passed through explicitly, because compose hands a
|
|
# container only what a service declares — putting these in an .env file
|
|
# makes them available for ${…} interpolation and nothing else. Without these
|
|
# three lines the operator sets them, restarts, and the page still says they
|
|
# are unset, with nothing anywhere explaining why.
|
|
#
|
|
# Empty defaults, matching the shipped .env, so the page keeps saying loudly
|
|
# that it has no operator until somebody gives it one.
|
|
APP_DEMO_IMPRESSUM_NAME: ${APP_DEMO_IMPRESSUM_NAME:-}
|
|
APP_DEMO_IMPRESSUM_ADDRESS: ${APP_DEMO_IMPRESSUM_ADDRESS:-}
|
|
APP_DEMO_IMPRESSUM_EMAIL: ${APP_DEMO_IMPRESSUM_EMAIL:-}
|
|
APP_DEMO_PRIVACY_HOST: ${APP_DEMO_PRIVACY_HOST:-}
|
|
|
|
services:
|
|
# Serves /demo, provisions visitors and renders the demo bar.
|
|
php:
|
|
environment:
|
|
<<: *demo-env
|
|
|
|
# Every background process but one, and each is needed:
|
|
#
|
|
# - worker-ingest delivers the scripted mail the Receive button asks for,
|
|
# and the auto-reply to anything a visitor sends. Without it the button
|
|
# appears to do nothing: the job is queued and never handled.
|
|
# - worker-export: nothing outbound leaves a demo (DemoMailSender swallows
|
|
# it), but a send still travels through this queue, so without it Send
|
|
# sits pending.
|
|
# - worker-bulk: a demo's mailboxes are small, so a bulk action is quick,
|
|
# but without the consumer its indicator would sit at nothing for ever.
|
|
# - the scheduler runs app:demo:reap every ten minutes. Without it expired
|
|
# visitors are never deleted, and the database grows by a user, an
|
|
# account, ten threads and ten contacts for everybody who followed the
|
|
# link, with nothing anywhere saying so.
|
|
# - the Mercure hub is what refreshes a visitor's inbox when mail arrives.
|
|
#
|
|
# The IMAP supervisor is the one left out. It holds an IDLE connection per
|
|
# mailbox, and a demo has no mailbox to hold one to: its accounts point at
|
|
# documentation domains that resolve nowhere, so it would spend the whole
|
|
# time failing to connect and backing off.
|
|
worker:
|
|
command: php bin/console app:work --without=imap-supervisor
|
|
environment:
|
|
<<: *demo-env
|