# Azione!Lab WordPress A warm, editorial single-page website for the Azione!Lab contemporary theatre workshop. WordPress manages the content, a custom theme owns the visual system, and Docker Compose provides MariaDB, NGINX, optional Let's Encrypt, WP-CLI, and isolated browser tests. ## Architecture - `wordpress/`: pinned WordPress 7/PHP 8.3 image, custom theme, and structured-content must-use plugin. - `db`: MariaDB 11.8 LTS, available only on the internal data network. - `proxy`: the only published service; routes `azionelab.org` to WordPress and applies edge security controls. - `certbot`: optional HTTP-01 certificate issue/renewal service. - `wp-cli`: opt-in bootstrap and maintenance service. - `tests/functional`: Playwright tests running only through the public virtual host. - `tests/security`: containerized static checks for security-sensitive Compose and Apache assumptions. See [architecture](docs/architecture.md) and [ADR-0001](docs/adr/0001-wordpress-single-page.md). ## Prerequisites - Docker Engine with Docker Compose v2. - Local ports `8080` and `8443` available. - `127.0.0.1 azionelab.org` in the local hosts file. No host PHP, database, Node.js, or WordPress installation is required. ## Start locally ```bash cp .env.example .env ./scripts/prepare-host-volumes.sh docker compose up --build -d docker compose --profile tools run --rm wp-cli /scripts/bootstrap.sh docker compose ps ``` Open: - site: - admin: Use the development credentials copied into `.env` only locally. Change them before sharing the environment. ## First startup procedure Use this checklist for the first run of a new environment. 1. Create the environment file: ```bash cp .env.example .env ``` 2. Edit `.env` before starting containers: - set strong `MARIADB_PASSWORD`, `MARIADB_ROOT_PASSWORD`, and `WP_ADMIN_PASSWORD`; - set `WP_URL` to the public URL, for example `https://lab.younerd.org`; - set `LETSENCRYPT_DOMAIN` to the same hostname when direct Let's Encrypt is used; - keep `LETSENCRYPT_STAGING=1` for the first certificate test; - configure `DB_DATA_PATH`, `WORDPRESS_DATA_PATH`, `LETSENCRYPT_DATA_PATH`, and `CERTBOT_CHALLENGES_PATH` for the host directories that must persist. 3. Prepare host-based volumes and permissions: ```bash ./scripts/prepare-host-volumes.sh ``` On Linux hosts, rerun with `sudo` if the script warns that it cannot change ownership: ```bash sudo ./scripts/prepare-host-volumes.sh ``` 4. Start the runtime services: ```bash docker compose up --build -d docker compose ps ``` With `LETSENCRYPT_ENABLED=1` and no certificate yet, NGINX intentionally returns `503` for normal application traffic while still serving ACME challenge and health routes. Certbot should start after the proxy becomes healthy. 5. Install/configure WordPress on the real persistent volume: ```bash docker compose --profile tools run --rm wp-cli /scripts/bootstrap.sh ``` Do not add `-f docker-compose.test.yml` here. The test override uses disposable test volumes and is only for automated checks. 6. Follow certificate issuance: ```bash docker compose logs -f proxy certbot ``` After the staging certificate flow is working, switch to the production CA: ```dotenv LETSENCRYPT_STAGING=0 ``` Then recreate Certbot so it sees the new environment and restart the proxy: ```bash docker compose up -d --force-recreate certbot docker compose up -d proxy ``` If a staging certificate already exists, the Certbot entrypoint removes only that staging certificate before requesting the production one. During the replacement window, NGINX may briefly return `503` for application traffic and will recover as soon as the production certificate is issued. 7. Final sanity checks: ```bash docker compose ps docker compose logs --tail=100 proxy wordpress db certbot ``` Open the configured `WP_URL` and `/wp-admin/`. Persistent data uses host-based bind mounts by default: - `./runtime/db` for MariaDB; - `./runtime/wordpress` for WordPress core, uploads, themes, and mu-plugins copied at container startup; - `./runtime/letsencrypt` for certificates; - `./runtime/certbot/www` for ACME HTTP-01 challenges. Run `./scripts/prepare-host-volumes.sh` before the first start, especially on Linux hosts. The script reads `.env`, creates the directories, and assigns the expected container owners (`999:999` for MariaDB, `33:33` for WordPress, `0:0` for Certbot). Override the `*_DATA_PATH` and `*_VOLUME_UID/GID` variables in `.env` if your runtime uses different host paths or image user IDs. ## Edit content - **Appearance > Customize**: hero, manifesto, laboratory, teacher, lessons, contacts, social links, and footer. - **Shows**: poster, title, short description, year, place, and order. Shows use the classic WordPress editor so the **Dettagli spettacolo** box is always visible below the title/content area. The line above each title is built from **Anno** and **Luogo**; the text below the title is **Descrizione breve**, stored as the standard WordPress excerpt. - **Gallery**: image, category, caption/title, and order. Gallery items also use the classic editor for a predictable metadata form. - **Settings > General**: site name and tagline. Real uploaded images replace the built-in editorial SVG placeholders. The three participation cards remain intentional theme copy; making them reorderable is a TODO if the workshop later needs more than the current three fixed audiences. ## Production configuration Set `WP_ENVIRONMENT_TYPE=production`, unique database passwords of at least 16 characters, a strong admin password, the public HTTPS `WP_URL`, and `WORDPRESS_DEBUG=0`. Production startup rejects placeholder database credentials. WordPress file editing and web-based theme/plugin/core modification are disabled; deploy updates by rebuilding the pinned image. When a trusted load balancer owns TLS, keep `LETSENCRYPT_ENABLED=0` and set `TRUST_PROXY_HEADERS=1` only if NGINX is reachable exclusively from that load balancer and the load balancer overwrites forwarding headers. For direct exposure, point public DNS at the host, expose ports 80/443, and start with the Let's Encrypt staging CA: ```dotenv WP_ENVIRONMENT_TYPE=production WP_URL=https://azionelab.org LETSENCRYPT_ENABLED=1 LETSENCRYPT_DOMAIN=azionelab.org LETSENCRYPT_EMAIL=operator@example.org LETSENCRYPT_STAGING=1 NGINX_BIND_ADDRESS=0.0.0.0 NGINX_HTTP_PORT=80 NGINX_HTTPS_PORT=443 TRUST_PROXY_HEADERS=0 ``` Before the first certificate exists, NGINX serves ACME challenges and returns 503 for application traffic. After issuance, HTTP redirects to HTTPS automatically. After validating the staging certificate flow, set `LETSENCRYPT_STAGING=0` and recreate the Certbot container. Existing staging certificates are removed automatically before a production certificate is requested: ```bash docker compose up -d --force-recreate certbot docker compose up -d proxy ``` Run the real bootstrap without the test override: ```bash docker compose --profile tools run --rm wp-cli /scripts/bootstrap.sh ``` The `docker-compose.test.yml` override intentionally uses disposable test volumes; do not use it for production or staging bootstrap commands. ## Useful commands ```bash docker compose logs -f proxy wordpress db certbot docker compose --profile tools run --rm wp-cli -c 'wp plugin list' docker compose --profile tools run --rm wp-cli -c 'wp core version' docker compose run --rm --no-deps wordpress php -l /opt/azionelab/theme/functions.php docker compose run --rm --no-deps wordpress php -l /opt/azionelab/theme/front-page.php docker compose run --rm --no-deps wordpress php -l /opt/azionelab/azionelab-content.php docker compose run --rm --no-deps proxy nginx -t docker compose -f docker-compose.yml -f docker-compose.test.yml up --build -d db wordpress proxy docker compose -f docker-compose.yml -f docker-compose.test.yml --profile tools run --rm wp-cli /scripts/bootstrap.sh docker compose -f docker-compose.yml -f docker-compose.test.yml --profile test run --build --rm functional-tests docker compose -f docker-compose.yml -f docker-compose.test.yml --profile test run --rm security-tests docker compose config --quiet ``` ## Backup Back up the database and WordPress files at the same logical point in time: ```bash docker compose stop wordpress mkdir -p backups docker compose exec -T db sh -c 'mariadb-dump -u root -p"$MARIADB_ROOT_PASSWORD" "$MARIADB_DATABASE"' > backups/database.sql tar -C runtime -czf backups/wordpress-files.tgz wordpress docker compose start wordpress ``` Do not commit backups or `.env`. Encrypt and test real backups off-host. See [operations](docs/operations.md) before restoring or deleting volumes. ## Rollback Revert the application commit and rebuild containers. Preserve the host directories configured by `DB_DATA_PATH`, `WORDPRESS_DATA_PATH`, `LETSENCRYPT_DATA_PATH`, and `CERTBOT_CHALLENGES_PATH`. Database restoration is a separate, destructive operation and requires a verified backup.