WHA Docs

Architecture

Directory structure, system diagram, request flow, and how all the pieces connect.

System Diagram

Request Flow

  1. Cloudflare handles DNS and proxying, terminates the public edge
  2. Heroku Router terminates SSL and routes to the dyno
  3. Nginx serves static files directly (JS, CSS, images, fonts) and proxies PHP requests to PHP-FPM
  4. PHP-FPM runs WordPress — index.phpwp/wp-blog-header.php
  5. 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 by yax-version-sync.
  • Project code lives in src/, separate from Composer-managed code in wp/ and vendor/.
  • Deployment builds a self-contained artifact in dist/ via tools/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 separate wordpress container.

Both share the same routing rules:

  • Static files served directly with expires max
  • /wp-admin and /wp-login.php redirect 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/*.php denied)

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.

On this page