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; routesazionelab.orgto 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.
See architecture and ADR-0001.
Prerequisites
- Docker Engine with Docker Compose v2.
- Local ports
8080and8443available. 127.0.0.1 azionelab.orgin the local hosts file.
No host PHP, database, Node.js, or WordPress installation is required.
Start locally
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:
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.
-
Create the environment file:
cp .env.example .env -
Edit
.envbefore starting containers:- set strong
MARIADB_PASSWORD,MARIADB_ROOT_PASSWORD, andWP_ADMIN_PASSWORD; - set
WP_URLto the public URL, for examplehttps://lab.younerd.org; - set
LETSENCRYPT_DOMAINto the same hostname when direct Let's Encrypt is used; - keep
LETSENCRYPT_STAGING=1for the first certificate test; - configure
DB_DATA_PATH,WORDPRESS_DATA_PATH,LETSENCRYPT_DATA_PATH, andCERTBOT_CHALLENGES_PATHfor the host directories that must persist.
- set strong
-
Prepare host-based volumes and permissions:
./scripts/prepare-host-volumes.shOn Linux hosts, rerun with
sudoif the script warns that it cannot change ownership:sudo ./scripts/prepare-host-volumes.sh -
Start the runtime services:
docker compose up --build -d docker compose psWith
LETSENCRYPT_ENABLED=1and no certificate yet, NGINX intentionally returns503for normal application traffic while still serving ACME challenge and health routes. Certbot should start after the proxy becomes healthy. -
Install/configure WordPress on the real persistent volume:
docker compose --profile tools run --rm wp-cli /scripts/bootstrap.shDo not add
-f docker-compose.test.ymlhere. The test override uses disposable test volumes and is only for automated checks. -
Follow certificate issuance:
docker compose logs -f proxy certbotAfter the staging certificate flow is working, switch to the production CA:
LETSENCRYPT_STAGING=0Then recreate Certbot so it sees the new environment and restart the proxy:
docker compose up -d --force-recreate certbot docker compose up -d proxyIf 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
503for application traffic and will recover as soon as the production certificate is issued. -
Final sanity checks:
docker compose ps docker compose logs --tail=100 proxy wordpress db certbotOpen the configured
WP_URLand/wp-admin/.
Persistent data uses host-based bind mounts by default:
./runtime/dbfor MariaDB;./runtime/wordpressfor WordPress core, uploads, themes, and mu-plugins copied at container startup;./runtime/letsencryptfor certificates;./runtime/certbot/wwwfor 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, excerpt, year, place, and order.
- Gallery: image, category, caption/title, and order.
- 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:
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:
docker compose up -d --force-recreate certbot
docker compose up -d proxy
Run the real bootstrap without the test override:
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
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 config --quiet
Backup
Back up the database and WordPress files at the same logical point in time:
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 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.