Skip to content

TaiKamilla/Wordpress

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JTI WordPress

WordPress + Terraform infrastructure for the Journalism Trust Initiative — a project of Reporters Without Borders (RSF), built and operated by Relief Applications.

Repos in this project:

  • ReliefApplications/jti-wordpress (this repo) — WordPress + default plugins + themes + Docker image + IaC
  • ReliefApplications/jti-custom — the JTI custom WordPress plugin. Deployed independently of this repo's image: its own GH Actions workflow (azcopy sync) uploads the plugin to the staging/prod Azure Files share on push to main. The WP container's polling overlay then picks it up within ~2-5 min. No WP image rebuild needed for jti-custom changes. See infra/RUNBOOK.md §4 "Update jti-custom".

What's in this repo

├── docker/
│   ├── Dockerfile                  WP-on-Apache base + perf tuning + overlay scripts
│   ├── apache.conf                 vhost + HTTP Basic Auth (staging gating)
│   ├── php-opcache.ini             OPcache prod tuning (validate_timestamps=0)
│   └── entrypoint-persist.sh       uploads/ symlink + 30s bidirectional rsync poller
├── wordpress/                      The WordPress install (core + plugins + themes)
│   ├── wp-config.php               Env-aware config (Azure App Service settings)
│   ├── wp-content/
│   │   ├── plugins/                Default + third-party plugins (no jti-custom)
│   │   ├── themes/                 Astra, hello-elementor, twentyeleven
│   │   ├── mu-plugins/             Must-use plugins (perf-trace, Elementor inline CSS)
│   │   └── languages/              Translations
│   └── ...                         WP core
├── infra/                          Terraform IaC (App Service, MySQL, ACR, etc.)
│   ├── RUNBOOK.md                  ⭐ DETAILED OPS GUIDE — read this for deploys
│   ├── modules/                    Reusable Terraform modules
│   ├── environments/               staging/ and prod/
│   └── bootstrap/                  One-time state-storage bootstrap
├── docker-compose.yml              Local development
├── .env.example                    Local-dev env template
└── .dockerignore / .gitignore      What stays out of the image / repo

Architecture (staging)

        Cloudflare (DNS + proxy)
                  │
                  ▼
       Azure App Service (Linux Container, B2, France Central)
                  │
        ┌─────────┴───────────────────────┐
        │                                 │
        ▼                                 ▼
   Image: ACR (wp-jti:latest)        Azure Files share "wp-content"
   (WP core + default plugins        (mounted at /persist)
    + themes + mu-plugins,            ├─ uploads/   → symlinked into wp-content
    OPcache pre-tuned;                │              (instant, no rsync)
    baseline files mtime=1970         └─ everything else → rsync poll every 30s,
    for sync correctness)                              bidirectional, no --delete
                  │                                    (admin updates + Storage
                  ▼                                     Explorer drops both work)
      Azure DB for MySQL Flexible           ┌── Azure Cache for Redis (Basic C0)
        (8.4, B_Standard_B2s)               └── ↑ used as WP object cache

Key design choices:

  • Plugins/themes are baked into the Docker image for fast PHP includes (Azure Files SMB latency on require_once would be catastrophic — measured 20+ s vs ~2 s baked). See infra/RUNBOOK.md §2 for the performance journey.
  • Admin-installed plugin updates persist via the polling rsync overlay (docker/entrypoint-persist.shrsync -au both directions every 30 s, no --delete). Updates from wp-admin AND direct Storage Explorer drops into the share both propagate. Lag is ~2–5 min for non-uploads paths (limited by SMB walk time over thousands of files), instant for uploads/. inotify was tried first; Azure Files CIFS doesn't reliably propagate cross-client change notifications to local inotify, so we poll instead — see infra/RUNBOOK.md §5.1 for the test data.
  • User uploads are direct-mounted (symlink) so writes go straight to AzFiles with zero data-loss window.
  • Image baseline mtimes are backdated to 1970 in the Dockerfile so the poller's -u flag (skip if dest is newer) reliably keeps share-side edits alive across image rebuilds. Without this, a fresh image rebuild's "newer" mtimes silently clobber older admin updates.
  • Cryptographic salts are generated by Terraform (random_password), injected as App Service env vars — never in git, never in the image.
  • Staging is password-protected: HTTP Basic Auth at the Apache level. Credentials are stored as a GitHub Actions environment secret PUBLIC_ACCESS_PWD under the staging environment.

Deploy workflow

For day-to-day plugin/theme/code updates:

# 1. Edit files in wordpress/ or docker/
# 2. Commit (optional but recommended)
git add -A && git commit -m "Update foo plugin to X.Y.Z"

# 3. Build + push image (also restarts staging)
cd /path/to/jti-wordpress
az acr build \
  --registry acrjtistaginghecl \
  --image wp-jti:latest \
  --file docker/Dockerfile \
  --build-arg HTPASSWD_PASSWORD="<from PUBLIC_ACCESS_PWD secret>" \
  .

# 4. Force fresh image pull (regular restart sometimes serves cached image)
az webapp stop --name app-jti-staging-fuml --resource-group rg-jti-staging
az webapp start --name app-jti-staging-fuml --resource-group rg-jti-staging

For a quick hot-fix without rebuilding the image (e.g. iterating on a mu-plugin), upload the file directly to the wp-content share via Azure Storage Explorer or the REST API — the polling overlay picks it up within 2–5 min and serves it via the live site, with no container restart needed. Remember to also commit the change to git + bake it into the next image, or the next CI rebuild won't include it.

See infra/RUNBOOK.md for the full deploy procedure, including the database import gotchas (Azure MySQL GIPK trap, etc.) that took a day of debugging to find.

Local development

cp .env.example .env  # tweak DB creds if needed
docker compose up
# → WordPress at http://localhost:8080

The image expects:

  • WordPress DB env vars (WORDPRESS_DB_*)
  • Cryptographic salts (WORDPRESS_AUTH_KEY etc.) — leave blank locally for dev or supply your own
  • Optional: WP_REDIS_HOST if you want object cache
  • Optional: WORDPRESS_DOMAIN_CURRENT_SITE for multisite

Documentation

Doc Purpose
infra/RUNBOOK.md Full deploy procedure, gotchas, performance journey, troubleshooting — read before any DB or infra operation
infra/CLAUDE.md Project context for AI coding agents (Claude Code etc.)
infra/README.md Terraform-specific guide
infra/INFRACOST.md Monthly Azure cost breakdown

Status

  • Staging: ✅ live at https://staging.journalismtrustinitiative.org/ (password-protected — see PUBLIC_ACCESS_PWD GH secret). CI auto-deploy on push to main (paths-filtered to docker/** + wordpress/**).
  • Prod: ✅ provisioned (2026-05-26); reachable internally via Front Door endpoint fde-jti-prod-dyfrdfhudtcsgyct.z03.azurefd.net + the preview.journalismtrustinitiative.org validation domain. Live apex DNS still points at the legacy hosting — flipping it is the final cutover step. See infra/RUNBOOK.md §10 for the cutover checklist.
  • CI/CD: GitHub Actions on both repos; see infra/RUNBOOK.md §4 "Continuous deployment".

License

Internal Relief Applications / RSF work — not for redistribution.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors