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>
110 lines
4.2 KiB
Plaintext
110 lines
4.2 KiB
Plaintext
# Every app service builds the same dev stage, so they share one image — one
|
|
# build, whichever of them starts it.
|
|
#
|
|
# The `image` line is the load-bearing part. compose.yaml names these services
|
|
# `ghcr.io/karatektus/pl_mail:latest`, and compose tags what it builds with the
|
|
# service's image name — so without this, every `docker compose up --build`
|
|
# overwrites the published tag locally with a dev image. That image carries no
|
|
# application code (the source arrives via the bind mount below), which is
|
|
# correct for dev and fatal anywhere else: if this override ever goes missing,
|
|
# compose finds that local tag, does not pull, and boots an app-less container
|
|
# that falls into the entrypoint's symfony/skeleton branch and crash-loops.
|
|
# Building under our own name leaves the published tag alone.
|
|
x-dev-build: &dev-build
|
|
image: pl_mail-dev:latest
|
|
build:
|
|
context: .
|
|
target: frankenphp_dev
|
|
|
|
services:
|
|
# Builds locally like every other app service. Without this it is the one
|
|
# service still pulling ghcr.io/karatektus/pl_mail, which would run emulated
|
|
# on an arm64 host — and since everything else waits on it completing
|
|
# successfully, that one container gates the whole stack.
|
|
secrets-init:
|
|
<<: *dev-build
|
|
|
|
php:
|
|
<<: *dev-build
|
|
volumes:
|
|
- ./:/app
|
|
- app_var:/app/var
|
|
# Durable mail data must be visible to BOTH the web container (which
|
|
# serves downloads) and the workers (which write during sync). php
|
|
# overlays /app/var with the private app_var volume to keep cache/logs
|
|
# off the bind mount, which otherwise hides these two directories from
|
|
# everything else.
|
|
- app_attachments:/app/var/attachments
|
|
- app_raw:/app/var/raw
|
|
- app_uploads:/app/var/uploads
|
|
- ./frankenphp/Caddyfile:/etc/frankenphp/Caddyfile:ro
|
|
- ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/conf.d/20-app.dev.ini:ro
|
|
environment:
|
|
APP_ENV: ${APP_ENV:-dev}
|
|
XDEBUG_MODE: "${XDEBUG_MODE:-off}"
|
|
extra_hosts:
|
|
- host.docker.internal:host-gateway
|
|
tty: true
|
|
|
|
# Everything that is not the web server (app:work), from the dev image with
|
|
# the source mounted. A service the base file defines and this one does not
|
|
# still starts, from the published image and without the source: not a
|
|
# missing container but one running yesterday's code. There used to be one
|
|
# block per worker here, and worker-bulk's had been forgotten; one block has
|
|
# nothing left to forget.
|
|
worker:
|
|
<<: *dev-build
|
|
volumes:
|
|
- ./:/app
|
|
# Durable mail data must be visible to BOTH the web container (which
|
|
# serves downloads) and the workers (which write during sync). php
|
|
# overlays /app/var with the private app_var volume to keep cache/logs
|
|
# off the bind mount, which otherwise hides these directories from
|
|
# everything else.
|
|
- app_attachments:/app/var/attachments
|
|
- app_raw:/app/var/raw
|
|
- app_uploads:/app/var/uploads
|
|
environment:
|
|
APP_ENV: ${APP_ENV:-dev}
|
|
|
|
database:
|
|
ports:
|
|
- "5432:5432"
|
|
|
|
adminer:
|
|
image: adminer
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8080:8080"
|
|
environment:
|
|
ADMINER_DEFAULT_SERVER: database
|
|
|
|
tailwind:
|
|
<<: *dev-build
|
|
volumes:
|
|
- .:/app
|
|
# Runs bin/console, so it goes through the app entrypoint and needs the
|
|
# same secrets every other service uses. Without this it would generate
|
|
# its own and refuse to start.
|
|
- app_secrets:/app/var/secrets
|
|
- app_var:/app/var
|
|
command: php bin/console tailwind:build --watch
|
|
# The image healthcheck probes the FrankenPHP admin endpoint on :2019, but
|
|
# this service replaces the server with the Tailwind watcher, so nothing
|
|
# ever listens there and the container is reported unhealthy while working
|
|
# fine. Same reason the worker disables it in compose.yaml.
|
|
healthcheck:
|
|
disable: true
|
|
depends_on:
|
|
- php
|
|
|
|
# No `mercure` block: the hub is a process in the worker now, reachable
|
|
# through php at /.well-known/mercure like everything else. The Mercure
|
|
# bundle's recipe put one here to publish the hub's port; brought back, it
|
|
# would define a service with no image and compose would refuse the file.
|
|
volumes:
|
|
app_var:
|
|
app_attachments:
|
|
app_raw:
|
|
app_uploads:
|