Operations
Health monitoring, ephemeral storage, logging and practical guidance for running the site day to day.
Health monitoring
The /healthz endpoint is a JSON health check provided by the yax framework (yax-common). It answers in layers:
/healthz— liveness. Returns 200 once the database, Redis and PHP are responding. Fast and dependency-light; this is what an uptime monitor should hit. A 200 means the origin is actually handling requests./healthz?detail— adds a per-subsystem health summary for everything WHA actually runs: scheduled cron, Meilisearch, media offload and image conversion, error reporting, and the Doctor.com and Press Ganey syncs.?alladds verbose detail;?cache=falseforces a live read (deeper views are cached briefly).
A failing subsystem is reported without failing the overall check — a stale Doctor.com sync, a dead cron, or an unreachable search index shows up as a clear ok: false in the detail view while liveness stays 200. So uptime stays meaningful ("is the site up?") and the detail view is the developer's at-a-glance verification lens for the integrations ("is everything actually working?"). The framework contract for how each subsystem reports lives in the per-plugin documentation.
Uptime monitoring setup
Point your monitoring service (Uptime Robot, Uptime Kuma, etc.) at:
https://whallc.com/healthzThe endpoint bypasses both Cloudflare WAF rules and cache rules, so a 200 response means the origin server is actually handling requests, not just Cloudflare serving a cached page.
Do's and don'ts
| Do | Don't |
|---|---|
Monitor /healthz — it hits the origin directly | Monitor the homepage — Cloudflare may serve a cached 200 even if the origin is down |
| Set check intervals to 1–5 minutes | Check more frequently than every minute — it's unnecessary and adds load |
| Alert on consecutive failures (2–3) | Alert on a single failure — transient network blips cause false alarms |
| Monitor both production and staging | Assume staging is fine because production is up — they're separate deployments |
Ephemeral storage
Heroku dynos have an ephemeral filesystem. Any files written to disk during runtime are lost when the dyno restarts, which happens:
- On every deploy
- At least once every 24 hours (Heroku's daily dyno cycling)
- When scaling or restarting manually
What this means
- Media uploads must go to S3. Media offloading (the
yax-offload-mediaplugin) handles this automatically: uploads are written to S3 and URLs are rewritten. Files aren't stored on the dyno filesystem. - Don't rely on local file writes. Anything that writes to
/tmpor the filesystem (log files, generated reports, cached files) will disappear on restart. - WordPress core and plugins are part of the deploy artifact. They're baked into the
dist/build, not installed at runtime. That's why the build step exists: everything the application needs is in the artifact. composer installruns on container startup. The entrypoint script makes sure dependencies are present, but it's a safety net. The deploy artifact should already be complete.
Common pitfalls
- Poorly coded plugins — Any plugin that uses the WordPress upload API works fine, since media offloading handles S3 automatically. Problems only arise with plugins that bypass the upload API and write directly to the filesystem.
- WordPress debug log —
wp-content/debug.logis written to the ephemeral filesystem. It's useful for debugging during a session but don't rely on it persisting. Useheroku logsinstead. - Scheduled tasks run from an external 5-minute cron rather than page visits, so they fire reliably regardless of traffic. See Scheduled tasks below.
Logging
Heroku logs
Application logs are available via the Heroku CLI:
# Tail all logs
heroku logs --tail --app womens-healthcare-associates
# Filter to web process
heroku logs --tail --app womens-healthcare-associates --ps web
# Recent logs (last 1500 lines)
heroku logs -n 1500 --app womens-healthcare-associatesLocal development
# All container logs
npm run logs
# Specific service
npm run logs:wordpress
npm run logs:nginx
npm run logs:db
# Log viewer UI
# Dozzle at localhost:8888What gets logged
- Nginx — Access logs and error logs. Shows request URLs, response codes and upstream PHP-FPM errors.
- PHP-FPM — PHP errors, warnings and notices. Controlled by
WORDPRESS_DEBUGandWORDPRESS_DEBUG_LOGenv vars. - WordPress — When
WP_DEBUG_LOGis enabled, WordPress writes towp-content/debug.log(ephemeral on Heroku). - Audit log — The audit log (the
yax-audit-logmu-plugin) tracks admin actions (post edits, plugin changes, option updates) in the database, not the filesystem.
Deploys
Pre-deploy checklist
- Theme assets built? Run
npm run productionin the theme directory if CSS/JS changed. Commitassets/dist/. - Tests pass? Run
npm testif Playwright tests exist for the feature. - Staging verified? Merge to
stagingfirst, verify on staging.whallc.com. - Database changes? If the deploy requires database changes (new options, updated ACF field groups), plan for those separately. The deploy only ships code.
Post-deploy verification
- Check
/healthzreturns 200 - Spot-check key pages (homepage, provider listing, a provider detail page)
- Check the browser console for JS errors
- If Cloudflare cache is stale, purge it
Rollback
Heroku keeps previous releases. To roll back:
# See recent releases
heroku releases --app womens-healthcare-associates
# Roll back to previous release
heroku rollback --app womens-healthcare-associatesScheduled tasks
WordPress cron (wp-cron.php) drives the recurring jobs: the Doctor.com and Press Ganey syncs, cache and log cleanup, and so on. Instead of WordPress's default trigger — which only fires when someone loads a page, and stalls during quiet periods — an external scheduler runs cron every 5 minutes, so jobs fire on a fixed cadence regardless of traffic. (WORDPRESS_DISABLE_CRON controls WordPress's built-in page-visit trigger.)
- Reliable cadence: scheduled tasks run on time, including overnight and on weekends.
- Long-running tasks can still hit Heroku's 30-second request limit, so heavy work is batched (the sync plugins already do this).
- The
wp-crontrolplugin is installed for viewing and managing scheduled events in wp-admin.
Database
Backups
A daily offsite backup protects the database and media. See Backups & Restore for what's covered and how to restore. For a manual export:
# Export via WP-CLI (local dev)
npm run wp -- db export backup.sql
# Import
npm run wp -- db import backup.sqlSearch and replace
When migrating data between environments, URLs need to be rewritten. The better-search-replace and search-and-replace plugins handle this in wp-admin. For CLI:
npm run wp -- search-replace 'https://whallc.com' 'https://whallc.joeyyax.dev' --dry-runAlways run with --dry-run first.
Version control
All code changes go through Git. The branch workflow and what's tracked are documented in Deployment. Key points:
- Never edit files directly on the server. Ephemeral storage means changes are lost on the next deploy.
- Always commit compiled theme assets (
assets/dist/) after runningnpm run production - Always commit
composer.lock. It pins exact dependency versions.
Managed services
| Service | Status |
|---|---|
| Media optimization | Active — images optimized for web delivery |
| Uptime monitoring | Active — /healthz endpoint monitored, alerts on downtime |
| Error monitoring | Active — application errors captured via GlitchTip |
| Daily offsite backups | Active — database backups stored offsite |
| Social feed | Active — aggregated social media feed |
See Contacts for support and escalation information.