close
Skip to content

Add staging environment #39

@retlehs

Description

@retlehs

Context

We need a staging environment to safely test changes before production. Staging will be gated with Caddy basic auth and use dedicated R2 buckets.


Step 1: Caddyfile — Add Conditional Basic Auth

File: deploy/ansible/roles/caddy/templates/Caddyfile.j2

Add a conditional basicauth * block. Production never defines caddy_basic_auth_enabled, so it's unaffected.

{% if caddy_basic_auth_enabled | default(false) %}
	basicauth * {
		{{ caddy_basic_auth_user }} {{ caddy_basic_auth_hash }}
	}
{% endif %}

Caddy uses bcrypt hashes natively — generate with caddy hash-password --plaintext '<password>'.

For Composer client testing, configure auth.json:

{"http-basic": {"staging.wp-packages.org": {"username": "staging", "password": "<pass>"}}}

Step 2: Ansible Multi-Environment Setup

2a. Update ansible.cfg

File: deploy/ansible/ansible.cfg

Remove inventory = inventory/hosts/production.yml. Inventory will be passed explicitly via -i flag to prevent accidental production deploys.

2b. Create staging inventory example

New file: deploy/ansible/inventory/hosts/staging.example.yml

all:
  hosts:
    wp-packages-staging:
      ansible_host: your.staging.server.ip
      ansible_user: deploy
      ansible_python_interpreter: /usr/bin/python3

2c. Create staging group_vars

New file: deploy/ansible/group_vars/staging/main.yml

Copy from production with these overrides:

app_domain: staging.wp-packages.org
app_env: staging
app_debug: "true"
go_log_level: debug
r2_cdn_public_url: "https://cdn-staging.wp-packages.org"
caddy_basic_auth_enabled: true
caddy_basic_auth_user: "staging"
caddy_basic_auth_hash: "{{ vault_caddy_basic_auth_hash }}"

Everything else (timers, users, litestream version, etc.) stays the same as production.

New file: deploy/ansible/group_vars/staging/vault.example.yml

Same as production vault.example.yml plus vault_caddy_basic_auth_hash: "REPLACE_ME".


Step 3: GitHub Actions Workflow

File: .github/workflows/deploy.yml

Add an environment input (choice: staging/production, default: staging):

  • concurrency: ${{ inputs.environment }}-deploy — independent locks per env
  • Conditionally use PROD_* or STAGING_* secrets based on the selected environment
  • Pass -i inventory/hosts/${{ inputs.environment }}.yml explicitly to ansible-playbook

GitHub repo setup (manual, one-time):
Add repo-level secrets: STAGING_SSH_PRIVATE_KEY, STAGING_ANSIBLE_VAULT_PASSWORD, STAGING_INVENTORY_YML_B64, STAGING_VAULT_YML_B64


Step 4: R2 Buckets (Manual in Cloudflare)

Create three staging buckets (using existing R2 API token):

  1. wp-packages-staging — Composer package metadata
  2. wp-packages-staging-cdn — CDN assets, with custom domain cdn-staging.wp-packages.org
  3. wp-packages-staging-backups — Litestream SQLite replicas

Step 5: DNS & Cloudflare (Manual)

  1. staging.wp-packages.org — A record to staging server IP (proxied through Cloudflare)
  2. cdn-staging.wp-packages.org — set up via R2 custom domain settings
  3. Generate a separate Cloudflare origin certificate for staging.wp-packages.org
  4. Store staging cert + key in staging vault (vault_ssl_certificate, vault_ssl_private_key)

Step 6: Provision & Verify

  1. Spin up staging VPS
  2. Add all STAGING_* secrets to the GitHub repo
  3. Run workflow: environment: staging, action: provision, ref: main
  4. Verify:
    • curl https://staging.wp-packages.org → 401 (basic auth required)
    • curl -u staging:<pass> https://staging.wp-packages.org → 200
    • R2 uploads land in staging buckets
    • composer require wp-plugin/akismet works with auth.json configured

Files to Create/Modify

File Action
deploy/ansible/roles/caddy/templates/Caddyfile.j2 Add conditional basicauth block
deploy/ansible/ansible.cfg Remove hardcoded inventory line
deploy/ansible/group_vars/staging/main.yml Create — staging config
deploy/ansible/group_vars/staging/vault.example.yml Create — vault key reference
deploy/ansible/inventory/hosts/staging.example.yml Create — inventory example
.github/workflows/deploy.yml Add environment input, conditional secrets, per-env concurrency
docs/operations.md Update for multi-environment support (inventory -i flag, staging secrets, etc.)

Follow up after #28.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions