Docker Compose — WordPress + MySQL Project
Build a complete WordPress blog with MySQL database and phpMyAdmin administration panel — a classic introduction to multi-container Compose setups.
Architecture
┌─────────────────────────────────────────────────┐
│ Docker Network: wp-net │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │WordPress │ │ MySQL │ │ phpMyAdmin │ │
│ │ :8080 │→ │ :3306 │←─│ :8888 │ │
│ └──────────┘ └──────────┘ └───────────────┘ │
│ │ │ │
│ wp-content/ db-data │
│ (bind mount) (named vol) │
└─────────────────────────────────────────────────┘
Project Setup
Create a project directory:
mkdir wordpress-compose && cd wordpress-compose
Step 1 — The Compose File
Create compose.yml:
services:
db:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppassword
volumes:
- db-data:/var/lib/mysql
networks:
- wp-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
wordpress:
image: wordpress:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppassword
WORDPRESS_DB_NAME: wordpress
volumes:
- ./wp-content:/var/www/html/wp-content
networks:
- wp-net
phpmyadmin:
image: phpmyadmin:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "8888:80"
environment:
PMA_HOST: db
PMA_PORT: 3306
networks:
- wp-net
volumes:
db-data:
networks:
wp-net:
driver: bridge
Step 2 — Launch the Stack
docker compose up -d
Expected output:
[+] Running 4/4
✔ Network wordpress-compose_wp-net Created
✔ Volume "wordpress-compose_db-data" Created
✔ Container wordpress-compose-db-1 Healthy
✔ Container wordpress-compose-wordpress-1 Started
✔ Container wordpress-compose-phpmyadmin-1 Started
Step 3 — Verify Services
Check all containers are running:
docker compose ps
Expected output:
NAME STATUS PORTS
wordpress-compose-db-1 Up (healthy) 3306/tcp, 33060/tcp
wordpress-compose-phpmyadmin-1 Up 0.0.0.0:8888->80/tcp
wordpress-compose-wordpress-1 Up 0.0.0.0:8080->80/tcp
Visit the WordPress setup wizard:
open http://localhost:8080 # macOS
# or
xdg-open http://localhost:8080 # Linux
Visit phpMyAdmin at http://localhost:8888 (login: wpuser / wppassword).
Step 4 — Inspect the Database Volume
docker volume inspect wordpress-compose_db-data
Expected output:
[
{
"CreatedAt": "2024-01-15T10:30:00Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "wordpress-compose",
"com.docker.compose.volume": "db-data"
},
"Mountpoint": "/var/lib/docker/volumes/wordpress-compose_db-data/_data",
"Name": "wordpress-compose_db-data",
"Options": null,
"Scope": "local"
}
]
Step 5 — Test Data Persistence
Stop and restart the stack:
docker compose down
docker compose up -d
Visit WordPress again — your site should still be configured. The MySQL data survived because it is stored in the named volume db-data.
Caution:
docker compose down -vwould delete the volume and destroy your database. Only use-vwhen you intentionally want a clean slate.
Step 6 — Add Environment File
Create .env:
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=wordpress
MYSQL_USER=wpuser
MYSQL_PASSWORD=wppassword
WORDPRESS_PORT=8080
PHPMYADMIN_PORT=8888
Update compose.yml to use variable substitution:
services:
db:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- db-data:/var/lib/mysql
networks:
- wp-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
wordpress:
image: wordpress:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "${WORDPRESS_PORT}:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: ${MYSQL_USER}
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
volumes:
- ./wp-content:/var/www/html/wp-content
networks:
- wp-net
phpmyadmin:
image: phpmyadmin:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "${PHPMYADMIN_PORT}:80"
environment:
PMA_HOST: db
PMA_PORT: 3306
networks:
- wp-net
volumes:
db-data:
networks:
wp-net:
driver: bridge
Verify the configuration:
docker compose config
Enhancements to Try
- Add SSL with an Nginx reverse proxy in front of WordPress
- Add WP-CLI as a service for command-line WordPress management
- Add automated backups with a cron-based service that dumps the database
- Use Compose profiles — put phpMyAdmin behind a
debugprofile so it only starts in development
Quick Reference
| Item | Detail |
|---|---|
| WordPress image | wordpress:latest |
| MySQL image | mysql:8.0 |
| WordPress port | 8080:80 |
| phpMyAdmin port | 8888:80 |
| DB health check | mysqladmin ping -h localhost |
| Data volume | db-data:/var/lib/mysql |