Skip to content
antalos

Mautic infrastructure

Mautic is owned by apps/mautic/. Shared versions, hostnames, NFS settings and volume sizes live in apps/variables.yaml. This page describes repository design, not evidence of a deployed or failure-tested service.

Traefik terminates TLS on websecure. Mautic trusts forwarded headers from the configured pod CIDR through the JSON-valued MAUTIC_TRUSTED_PROXIES input, so it recognizes the external HTTPS request while Apache listens internally on 8080. The install hook also writes that input into config/local.php, which Mautic’s early proxy middleware reads before Symfony resolves environment parameters. Web startup/readiness checks execute a local HTTP request to the pod address with the public Host and forwarded HTTPS headers, checking for a 200 login response. This follows the same trusted pod-network path as Traefik and avoids following an HTTPS redirect to a pod that has no TLS listener.

Components and placement

Component Placement Data and client path
Apache web Two pods, required anti-affinity on left/right Service and Traefik at mautic.antblu.net; processes run as UID/GID 1010:1010 on port 8080
Queue workers Two pods, required anti-affinity on left/right Consume Mautic email and hit Messenger transports from MariaDB
Scheduled commands Three staggered CronJobs Segments at :00/:15/:30/:45, campaigns five minutes later, triggers ten minutes later; each job forbids overlap with itself
MariaDB Two Galera data members on left/right MariaDB operator, OpenEBS local data volumes, and operator-managed primary Service
Galera arbitrator One garbd on tainted RTX Third quorum vote; contains no database copy
Redis Two data pods with Sentinel on left/right Persistent local volumes, AOF every second, authenticated replication and election
Sentinel voter One Sentinel-only pod on tainted RTX Third Sentinel, quorum two; contains no Redis data copy
Redis routing Two HAProxy pods on left/right Authenticated role checks route PHP sessions to the current primary and close connections when a backend becomes unhealthy
Application files RWX NFS at 10.30.0.5:/mnt/nvme/mautic Config, uploaded files/images and logs shared by all application processes

The MariaDB layout copies Invoice Ninja: two operator-managed Galera members, a separate garbd voter, local data PVCs and small Galera recovery PVCs. Mautic connects to mautic-db-primary, rather than spreading writes over both members. The operator controls primary selection and Service endpoints. Galera recovery allows a minimum cluster size of one; loss of quorum or ambiguous data position can still require operator recovery and must not be treated as permission to bootstrap an arbitrary stale member.

The image guards and patches Mautic’s installer lookup of the administrator role: it selects the first administrator role rather than assuming primary key 1. Galera’s automatic ID spacing remains enabled, including its adjustment for the arbitrator’s membership. Image upgrades fail to build if that upstream lookup changes and requires review.

Redis is used for shared PHP sessions with locking in database 0 and Mautic’s application cache in database 1 with the mautic_cache namespace. SAML request correlation uses this shared application cache; PHP sessions alone do not share outstanding authentication requests between web replicas. The install hook persists the Redis cache adapter settings in shared NFS config/local.php. Mautic’s bundled Doctrine Messenger transport stores queues in MariaDB; no Redis Messenger dependency is added. Failed messages remain in the failed Doctrine transport for operator review. The Redis image and database use their own supported Unix identities on local volumes. UID/GID 1010:1010 applies to Mautic application processes and NFS files.

Storage and installation

The Retain PV binds only to mautic-data; it does not create an export. NFS must already allow UID/GID 1010 to create directories. A non-root init container creates config, files, images and logs and seeds bundled media without replacing existing files. It never changes ownership of the export. Per-pod compiled Symfony cache and temporary files are ephemeral; Apache logs go to container output. Protect NFS configuration files because Mautic writes credentials and integration settings there.

Mautic pods deliberately omit fsGroup: the NFS CSI driver’s recursive ownership pass runs through kubelet’s root identity, which cannot traverse private 1010-owned directories on this export. A small root init container with only CHOWN prepares the two fresh local emptyDir mount roots. It has no NFS mount. The media/config init container and every Mautic process run as 1010:1010; no recursive ownership or permission changes are made to shared NFS data.

The service image extends the official Apache image, adds pinned phpredis and remaps www-data to 1010 at build time. Its entrypoint starts Apache or CLI directly, avoiding root-only startup helpers. The install/migration Sync hook runs after Secrets, storage, database grants and Redis routing, before web/worker resources. Repeated installation preserves an installed account; subsequent schema migrations run in that one hook. Schema upgrades can still need a maintenance window because old serving processes may remain present while a new migration runs.

Availability limits

Web, worker, database and Redis placement separates the two ordinary workers. Remaining Galera/Sentinel voters support a one-data-worker failover design; the resulting degraded service has only one data copy until recovery. Redis replication is asynchronous, so acknowledged session writes can be lost on promotion. In-flight requests can fail, and a queue worker interruption can cause message redelivery; application workflows must tolerate retries. Sentinel/HAProxy role checks are not partition fencing and do not establish arbitrary-network-partition safety.

NFS, public routing, DNS, the Kubernetes API endpoint and operator actions remain shared dependencies. CronJobs use scheduling/retry recovery rather than hot standby. No failover timing, whole-Proxmox-host resilience or backup/restore guarantee is established by these manifests. Replication is not an independent backup.

Identity

Native SAML uses Authentik application slug mautic, with Mautic’s public HTTPS origin as the SP entity ID and /s/saml/login_check as the assertion consumer. Authentik signs both responses and assertions using its existing signing key. Metadata and attribute settings persist in the shared NFS config/local.php; provider, mappings and access bindings persist in Authentik’s database. These are post-deployment application settings, configured by the scripts in apps/mautic/saml/, rather than resources reconciled by Argo CD.

Access is bound to Mautic Users or authentik Admins. JIT accounts use a non-admin SSO Users role with no permissions initially. Existing accounts match by username and keep their roles; review matching usernames before granting identity access. Authentik availability is required for new SAML logins. Local recovery login at /s/login remains available. See the SAML runbook.

See Mautic operations, the official Docker image, Mautic configuration and cron guidance.

Arbitrator recovery after pod replacement

The existing RTX arbitrator supervises its process and re-resolves both data-member hostnames. If both members resolve to changed addresses and both data members report a connected, synchronized Primary component with matching state UUIDs, it restarts the arbitrator to discard stale seed IPs. It does not recycle the tie-breaker merely because a data member disappears, force-bootstrap a component, or change quorum weights. Readiness requires the latest logged membership view to be Primary; a running process alone is insufficient. Confirm three communicating members before a failure drill and verify two-member Primary membership and SQL writes after losing one data worker.

Background consumers retry an exited process after five seconds under a signal-forwarding supervisor. The wrapper waits for its child during termination and does not intentionally run competing consumer copies. This removes the long crash-backoff following a transient database outage; application queue delivery and idempotency semantics are unchanged.