Recipe: WordPress + MySQL
A two-service stack wired with Compose.
WordPress is the classic multi-service stack: the PHP application in one container, its MySQL database in another, wired together with Compose. It is the recipe that ties the whole course together — two services, a private network, a named volume each, secrets for the database credentials, and healthcheck-gated startup so WordPress does not launch before the database is ready.
services:wordpress:image: wordpress:6-php8.3-apacheports: ["8080:80"]environment:WORDPRESS_DB_HOST: dbWORDPRESS_DB_NAME: wordpressWORDPRESS_DB_USER: wpWORDPRESS_DB_PASSWORD_FILE: /run/secrets/wp_db_pwsecrets: [wp_db_pw]volumes: ["wp_html:/var/www/html"] # themes, plugins, uploads persistdepends_on:db: { condition: service_healthy }db:image: mysql:8.4environment:MYSQL_DATABASE: wordpressMYSQL_USER: wpMYSQL_PASSWORD_FILE: /run/secrets/wp_db_pwMYSQL_ROOT_PASSWORD_FILE: /run/secrets/wp_root_pwsecrets: [wp_db_pw, wp_root_pw]volumes: ["db_data:/var/lib/mysql"]healthcheck:test: ["CMD","mysqladmin","ping","-h","127.0.0.1"]interval: 10svolumes: { wp_html: {}, db_data: {} }secrets:wp_db_pw: { file: ./secrets/wp_db_pw.txt }wp_root_pw: { file: ./secrets/wp_root_pw.txt }
Why it holds together
WordPress reaches the database at host db — the service name, resolved by Compose’s embedded DNS. Only WordPress publishes a port; the database is reachable solely on the private Compose network. Each service has its own named volume, so themes/uploads and database files both persist across recreates. And depends_on with service_healthy means WordPress waits for MySQL’s healthcheck, not merely for the container to exist — no more install-page database errors on first boot.
$ docker compose up -d$ docker compose psNAME IMAGE STATUSwordpress wordpress:6-php8.3-apache Updb mysql:8.4 Up (healthy)$ curl -sI localhost:8080 | head -1HTTP/1.1 302 Found # WordPress redirecting to its installer — stack is live