Architecture
Directory structure, system diagram, request flow, and how all the pieces connect.
System Diagram
Request Flow
- Cloudflare handles DNS and proxying, terminates the public edge
- Heroku Router terminates SSL and routes to the dyno
- Nginx serves static files directly (JS, CSS, images, fonts) and proxies PHP requests to PHP-FPM
- PHP-FPM runs WordPress —
index.php→wp/wp-blog-header.php - Amazon RDS (MySQL) stores all WordPress data
wp-admin lives at /wp/wp-admin/ — Nginx rewrites bare /wp-admin and /wp-login.php automatically.
How This Differs From Standard WordPress
The wp-admin experience is standard WordPress — updates, plugin installs, and content editing all work normally. The differences are under the hood:
- Composer manages WordPress core and plugins as dependencies (pinned in
composer.lock). Updates through wp-admin are synced back to the manifest automatically byyax-version-sync. - Project code lives in
src/, separate from Composer-managed code inwp/andvendor/. - Deployment builds a self-contained artifact in
dist/viatools/build-dist.sh. See Deployment. - Configuration is entirely environment-driven via
wp-config.custom.php. No hardcoded values. - Custom
yax-*plugins fill gaps WordPress doesn't cover natively. See Plugin System.
WordPress Configuration
All settings come from environment variables — src/wp-config.php loads wp-config.custom.php, which reads everything via getenv_docker(). URLs are detected dynamically from HTTP_HOST, so the same codebase runs on any domain without config changes. HTTPS is detected through multiple proxy headers (X-Forwarded-Proto, CF-Visitor) to handle the Cloudflare → Heroku → Nginx → PHP-FPM chain.
See Hosting > Environment Variables for the full variable reference.
Custom Plugin Ecosystem
The site uses two layers of custom mu-plugins alongside standard third-party plugins:
-
yax-*plugins — A shared framework maintained by the site developer, used across multiple projects. These fill gaps in core WordPress: media offloading, Composer version sync, component rendering, fluent query building, security hardening, and more. Maintained as Composer packages via a private registry (composer.joeyyax.app). -
wha-*plugins — Built specifically for WHA. These register the custom post types (providers, locations, services, life stages, etc.) and site-specific business logic like the Doctor.com rating sync. Committed directly to the repo.
See Plugin System for the full list and Custom Post Types for the data model.
Media Offloading
The yax-offload-media mu-plugin rewrites upload URLs to point at the S3 bucket. Media uploaded through wp-admin gets stored both locally and in S3. Public URLs serve from S3 via Cloudflare.
Nginx
Two Nginx configs exist:
src/nginx.conf— Production. Used inside the single-container deployment (PHP-FPM + Nginx together).src/nginx.dev.conf— Development. Proxies PHP to the separatewordpresscontainer.
Both share the same routing rules:
- Static files served directly with
expires max /wp-adminand/wp-login.phpredirect to/wp/wp-admin/and/wp/wp-login.php- PHP requests proxied to PHP-FPM with proper
X-Forwarded-*headers - Sensitive files blocked (
.,wp-config.php,readme.html,xmlrpc.php) - Upload PHP execution blocked (
/uploads/*.phpdenied)
Privacy & Consent
The yax-cookie-consent mu-plugin manages cookie consent and controls when analytics scripts load. Freshpaint's tracking is gated behind consent — the yax_cookie_consent_block_analytics filter prevents analytics from firing until the user accepts.
Cloudflare
Cloudflare sits in front of the site handling DNS, CDN, and security. The domain is on the Free plan. See Caching for cache rules and Security for WAF rules, geo-blocking, and Turnstile configuration.