CoursesDocker in depthPractical recipes

Recipe: WordPress + MySQL

A two-service stack wired with Compose.

Intermediate12 min · lesson 28 of 30

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.

The two-service stack
1browser
hits :8080
2wordpress
PHP + Apache
3db (mysql)
private network only
4volumes
uploads + data persist
WordPress is published; MySQL is not. They talk over the Compose network by service name; each has its own volume.
compose.yaml
services:
wordpress:
image: wordpress:6-php8.3-apache
ports: ["8080:80"]
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wp
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/wp_db_pw
secrets: [wp_db_pw]
volumes: ["wp_html:/var/www/html"] # themes, plugins, uploads persist
depends_on:
db: { condition: service_healthy }
db:
image: mysql:8.4
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wp
MYSQL_PASSWORD_FILE: /run/secrets/wp_db_pw
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/wp_root_pw
secrets: [wp_db_pw, wp_root_pw]
volumes: ["db_data:/var/lib/mysql"]
healthcheck:
test: ["CMD","mysqladmin","ping","-h","127.0.0.1"]
interval: 10s
volumes: { 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.

terminal
$ docker compose up -d
$ docker compose ps
NAME IMAGE STATUS
wordpress wordpress:6-php8.3-apache Up
db mysql:8.4 Up (healthy)
$ curl -sI localhost:8080 | head -1
HTTP/1.1 302 Found # WordPress redirecting to its installer — stack is live
One password source, two consumers
WordPress and MySQL must agree on the database password — reference the same secret file from both services rather than typing the value twice, so they cannot drift. Keep MYSQL_ROOT_PASSWORD separate from the app password, publish only WordPress, and put both volumes under backup: losing wp_html loses your uploads, losing db_data loses the site.