--- project: staging-environment-setup type: reference status: active path: Tech/Reference tags: - pbs - infrastructure - staging - traefik - cloudflare - docker created: 2026-04-15 updated: 2026-04-15 --- # Staging Environment — Architecture Reference Reference doc for how the PBS staging environment is structured. Captured retroactively from the original Feb 24 build session, before the n8n email pipeline existed. > **Note on path:** Filed under a proposed new `Tech/Reference` folder for evergreen architecture docs (vs. `Tech/Sessions` for point-in-time notes or `Tech/Projects` for active work). Flag if you'd rather move it. ## Domain & DNS Strategy Staging lives at `staging.plantbasedsoutherner.com`, with all supporting tools nested as further subdomains under it. ``` staging.plantbasedsoutherner.com ← WordPress n8n.staging.plantbasedsoutherner.com ← n8n portainer.staging.plantbasedsoutherner.com ← Portainer status.staging.plantbasedsoutherner.com ← Uptime Kuma db.staging.plantbasedsoutherner.com ← phpMyAdmin gitea.staging.plantbasedsoutherner.com ← Gitea ``` **Cloudflare DNS uses a wildcard A record** so new subdomains require zero DNS work: ``` Type: A Name: *.staging Value: Proxy: DNS only (grey cloud) ``` The wildcard must remain **DNS only** (grey cloud) for Let's Encrypt HTTP challenges to succeed. With Cloudflare proxy on, ACME hits Cloudflare's IP instead of the origin and TLS handshake fails. ## SSL / Traefik Traefik handles all routing and SSL via Let's Encrypt **HTTP challenge** (not TLS challenge — TLS challenge is incompatible with Cloudflare in front). Each container declares its hostname via Traefik labels; Traefik handles cert issuance and renewal automatically per hostname. ## Migration Pattern (Prod → Staging Clone) The original staging build used a full prod clone. Key gotchas: - **Database URL replacement:** Use WP-CLI `search-replace` (not raw SQL) to handle serialized Elementor data correctly. Run from inside the wordpress container. - **Avoid "staging.staging" loops:** Replace `plantbasedsoutherner.com` → ` staging.plantbasedsoutherner.com` in one direction only; double-check existing staging references before running. - **Redis password:** Must be present in the container env for both prod and staging — easy to forget when moving env vars between `.env` files. - **DB user recreation:** Fresh MySQL inits won't carry over user grants from the prod dump. ## Resource Sizing Staging started on a 1GB Linode Nanode and was upgraded to 2GB after hitting memory ceilings during full-stack runs (WordPress + MySQL + Redis + n8n + Traefik + monitoring). 2GB is the practical minimum for the full stack. ## Healthchecks All Docker containers have healthchecks defined. A few patterns learned: - **Traefik** requires an IPv6 loopback target in some configurations. - **Containers without `curl`** (slim images, Gitea, Python slim) need alternative healthcheck commands — wget, a Python one-liner, or the application's own CLI. - **Gitea healthcheck on `/api/v1/version`** returns 404 before the setup wizard completes, blocking initial Traefik routing. Remove the healthcheck for first-time setup, then add it back. ## Inventory & Ansible (added later) The Ansible inventory eventually evolved to keep staging and production strictly separated — see the GitHub deploy key project for the final structure with `bootstrap_staging`, `bootstrap_production`, `staging`, and `production` groups (no meta-groups that could hit both environments). ## Open Items - [ ] Confirm `Tech/Reference` is the right folder for this kind of doc, or move to `Tech/Sessions` / `Tech/Projects` - [ ] Cross-link this from the GitHub deploy key project plan once both are in the vault ## Related - GitHub private repo + Ansible inventory restructure (April 2026) - PBS Security Hardening (Crowdsec + Authelia, staged on staging first) - WP-Cron / Supercronic deploy (March 2026) ...sent from Jenny & Travis