A production-minded WordPress boilerplate based on Roots Bedrock, designed for:
- Local development with Docker Compose (profiles for optional services)
- Hardened, reproducible container builds (multi-stage)
- Kubernetes-first deployment via Helm
- Supply-chain and security gates in GitHub Actions
This repo is intentionally generic and safe to publish: no internal domains, no secrets, and no private themes/plugins/submodules.
- Docker + Docker Compose v2
make
cp .env.example .env
make doctor
make bootstrap
make smoke
# Full pre-push QA bundle (runs composer checks; runs smoke-full when Docker is available)
make qaThen open:
make bootstrap also runs a WP-CLI install if the site isn't installed yet.
Defaults (override in .env):
WP_SITE_TITLE(default:Boilerplate)WP_ADMIN_USER(default:admin)WP_ADMIN_PASSWORD(default:admin)WP_ADMIN_EMAIL(default:admin@example.com)
You can re-run:
make wp-installTo stop:
make downThis repo includes optional Caddy reverse-proxy profiles for local HTTPS:
Option A: quick internal CA (browser warns)
# Update .env so WP_HOME/WP_SITEURL use https://wp.localhost:8443
make bootstrap-tlsOption B: trusted certs with mkcert (recommended for local UX)
# Requires mkcert installed on your machine
make bootstrap-tls-trustedURL:
Notes:
- For trusted TLS,
make certs-mkcertgenerates local cert files under.certs/(gitignored). - Set in
.env:WP_HOME=https://wp.localhost:8443WP_SITEURL=https://wp.localhost:8443/wp
- Detailed guide:
docs/local-dev/tls-mkcert.md
# Adds MailHog (SMTP capture UI at http://localhost:8025)
make up-mail
# Adds phpMyAdmin (http://localhost:8081)
make up-dbadmin
# Adds metrics exporters (Prometheus scrape targets)
make up-observabilityThis boilerplate provides:
- A PHP-FPM image (Bedrock + WordPress via Composer)
- A web image (Nginx, non-root)
- Secure-by-default runtime posture (drop caps, no privilege escalation, read-only root filesystem where possible)
Images are intended to be built in CI and deployed by digest (build once, deploy everywhere).
The included GitHub Actions workflow builds and publishes images for each branch:
ghcr.io/<owner>/<repo>-php:<branch>ghcr.io/<owner>/<repo>-nginx:<branch>
For production, prefer pinning by digest instead of tags.
- Local dev uses a
.envfile (see.env.example). - Production should set env vars via Kubernetes Secrets/ConfigMaps (do not bake secrets into images).
Common toggles:
DISABLE_WP_CRON=true(production)DISALLOW_FILE_MODS=true(production hardening)WP_CACHE=true+WP_REDIS_HOST=...(optional Redis object cache; plugin required)
Helm chart: helm/wp-boilerplate
High-level steps:
- Push images to a registry (GHCR workflow included).
- Create Kubernetes Secrets for database credentials and WordPress salts/keys.
- Install the chart and pin images by digest.
Example:
helm upgrade --install wp helm/wp-boilerplate \
--namespace wp --create-namespace \
--set image.php.repository=ghcr.io/OWNER/REPO-php \
--set image.web.repository=ghcr.io/OWNER/REPO-nginx \
--set image.php.digest=sha256:... \
--set image.web.digest=sha256:...Preferred (cloud-native): use an S3-compatible uploads plugin (no shared PVC required).
Alternative (simple clusters): enable the chart's uploads.persistence option to mount a PVC.
Details: docs/kubernetes/uploads-s3.md
In production, you should disable the built-in pseudo-cron and run a real scheduler:
- Set
DISABLE_WP_CRON=true - Use a Kubernetes
CronJob(template included) or an external scheduler to trigger cron processing
Recommended approaches:
- External Secrets Operator (ESO)
- SealedSecrets
See docs/kubernetes/ for templates and required keys.
CI signs published images (keyless Cosign). See docs/supply-chain/cosign.md.
MIT. See LICENSE.
See SECURITY.md.