Setting up WordPress with Docker
Learning Objectives
- Create a complete docker-compose.yml for WordPress
- Configure WordPress, MySQL, and phpMyAdmin containers
- Implement persistent volumes for data storage
- Set up container networking and environment variables
Introduction
Docker Compose simplifies WordPress deployment by orchestrating multiple containers with a single configuration file. We'll build a complete WordPress development environment step by step.
What We're Building
Project Structure
First, let's create our project directory structure:
# Create project directory
mkdir wordpress-docker
cd wordpress-docker
# Create subdirectories
mkdir -p config/php
mkdir -p wp-content/themes
mkdir -p wp-content/plugins
mkdir -p wp-content/uploads
mkdir -p database/backup
# Create configuration files
touch docker-compose.yml
touch .env
touch .env.example
touch config/php/uploads.ini
graph TD
A[wordpress-docker/] --> B[docker-compose.yml]
A --> C[.env]
A --> D[config/]
D --> E[php/]
E --> F[uploads.ini]
A --> G[wp-content/]
G --> H[themes/]
G --> I[plugins/]
G --> J[uploads/]
A --> K[database/]
K --> L[backup/]
Creating docker-compose.yml
Let's build our docker-compose.yml file step by step:
Step 1: Basic Structure and Version
# docker-compose.yml
version: '3.8'
services:
# Our services will go here
volumes:
# Named volumes for data persistence
networks:
# Custom network for container communication
Step 2: WordPress Service Configuration
version: '3.8'
services:
wordpress:
image: wordpress:6.4-php8.2-apache
container_name: wp-site
restart: unless-stopped
ports:
- "${WP_PORT:-8080}:80"
environment:
WORDPRESS_DB_HOST: mysql:3306
WORDPRESS_DB_NAME: ${DB_NAME:-wordpress}
WORDPRESS_DB_USER: ${DB_USER:-wpuser}
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD:-wppass}
WORDPRESS_TABLE_PREFIX: ${WP_PREFIX:-wp_}
WORDPRESS_DEBUG: ${WP_DEBUG:-false}
WORDPRESS_DEBUG_LOG: ${WP_DEBUG_LOG:-false}
WORDPRESS_DEBUG_DISPLAY: ${WP_DEBUG_DISPLAY:-false}
volumes:
- wordpress_data:/var/www/html
- ./wp-content:/var/www/html/wp-content
- ./config/php/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
depends_on:
- mysql
networks:
- wp-network
Step 3: MySQL Database Configuration
mysql:
image: mysql:8.0
container_name: wp-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-rootpass}
MYSQL_DATABASE: ${DB_NAME:-wordpress}
MYSQL_USER: ${DB_USER:-wpuser}
MYSQL_PASSWORD: ${DB_PASSWORD:-wppass}
volumes:
- mysql_data:/var/lib/mysql
- ./database/backup:/backup
ports:
- "${DB_PORT:-3306}:3306"
command: [
'--default-authentication-plugin=mysql_native_password',
'--character-set-server=utf8mb4',
'--collation-server=utf8mb4_unicode_ci'
]
networks:
- wp-network
Step 4: phpMyAdmin Configuration
phpmyadmin:
image: phpmyadmin:latest
container_name: wp-phpmyadmin
restart: unless-stopped
ports:
- "${PMA_PORT:-8081}:80"
environment:
PMA_HOST: mysql
PMA_PORT: 3306
PMA_USER: ${DB_USER:-wpuser}
PMA_PASSWORD: ${DB_PASSWORD:-wppass}
UPLOAD_LIMIT: 300M
depends_on:
- mysql
networks:
- wp-network
Step 5: Volumes and Networks
volumes:
wordpress_data:
driver: local
mysql_data:
driver: local
networks:
wp-network:
driver: bridge
Complete docker-compose.yml
Here's the complete configuration file:
# docker-compose.yml
version: '3.8'
services:
wordpress:
image: wordpress:6.4-php8.2-apache
container_name: wp-site
restart: unless-stopped
ports:
- "${WP_PORT:-8080}:80"
environment:
WORDPRESS_DB_HOST: mysql:3306
WORDPRESS_DB_NAME: ${DB_NAME:-wordpress}
WORDPRESS_DB_USER: ${DB_USER:-wpuser}
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD:-wppass}
WORDPRESS_TABLE_PREFIX: ${WP_PREFIX:-wp_}
WORDPRESS_DEBUG: ${WP_DEBUG:-false}
WORDPRESS_DEBUG_LOG: ${WP_DEBUG_LOG:-false}
WORDPRESS_DEBUG_DISPLAY: ${WP_DEBUG_DISPLAY:-false}
volumes:
- wordpress_data:/var/www/html
- ./wp-content:/var/www/html/wp-content
- ./config/php/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
depends_on:
- mysql
networks:
- wp-network
mysql:
image: mysql:8.0
container_name: wp-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-rootpass}
MYSQL_DATABASE: ${DB_NAME:-wordpress}
MYSQL_USER: ${DB_USER:-wpuser}
MYSQL_PASSWORD: ${DB_PASSWORD:-wppass}
volumes:
- mysql_data:/var/lib/mysql
- ./database/backup:/backup
ports:
- "${DB_PORT:-3306}:3306"
command: [
'--default-authentication-plugin=mysql_native_password',
'--character-set-server=utf8mb4',
'--collation-server=utf8mb4_unicode_ci'
]
networks:
- wp-network
phpmyadmin:
image: phpmyadmin:latest
container_name: wp-phpmyadmin
restart: unless-stopped
ports:
- "${PMA_PORT:-8081}:80"
environment:
PMA_HOST: mysql
PMA_PORT: 3306
PMA_USER: ${DB_USER:-wpuser}
PMA_PASSWORD: ${DB_PASSWORD:-wppass}
UPLOAD_LIMIT: 300M
depends_on:
- mysql
networks:
- wp-network
volumes:
wordpress_data:
driver: local
mysql_data:
driver: local
networks:
wp-network:
driver: bridge
Environment Variables (.env)
Create a.envfile to store sensitive configuration:
# .env
# WordPress Configuration
WP_PORT=8080
WP_PREFIX=wp_
WP_DEBUG=true
WP_DEBUG_LOG=true
WP_DEBUG_DISPLAY=false
# Database Configuration
DB_NAME=wordpress
DB_USER=wpuser
DB_PASSWORD=SecurePassword123!
DB_ROOT_PASSWORD=RootPassword456!
DB_PORT=3306
# phpMyAdmin Configuration
PMA_PORT=8081
Security Notice
# .env.example
# WordPress Configuration
WP_PORT=8080
WP_PREFIX=wp_
WP_DEBUG=false
WP_DEBUG_LOG=false
WP_DEBUG_DISPLAY=false
# Database Configuration
DB_NAME=wordpress
DB_USER=wpuser
DB_PASSWORD=ChangeThisPassword
DB_ROOT_PASSWORD=ChangeThisRootPassword
DB_PORT=3306
# phpMyAdmin Configuration
PMA_PORT=8081
PHP Configuration
Create custom PHP settings for WordPress:
# config/php/uploads.ini
; Maximum allowed size for uploaded files
upload_max_filesize = 64M
; Maximum size of POST data
post_max_size = 64M
; Maximum execution time
max_execution_time = 300
; Maximum input time
max_input_time = 300
; Memory limit
memory_limit = 256M
; Maximum input variables
max_input_vars = 3000
; File uploads
file_uploads = On
; Session configuration
session.gc_maxlifetime = 1440
Managing Persistent Volumes
Understanding volume management is crucial for data persistence:
graph LR
subgraph "Named Volumes"
V1[wordpress_data]
V2[mysql_data]
end
subgraph "Bind Mounts"
B1[./wp-content]
B2[./database/backup]
B3[./config/php]
end
subgraph "Containers"
C1[WordPress]
C2[MySQL]
end
V1 --> C1
V2 --> C2
B1 --> C1
B2 --> C2
B3 --> C1
style V1 fill:#e3f2fd
style V2 fill:#e3f2fd
style B1 fill:#e8f5e9
style B2 fill:#e8f5e9
style B3 fill:#e8f5e9
Volume Commands
# List all volumes
docker volume ls
# Inspect a volume
docker volume inspect wordpress-docker_wordpress_data
# Backup WordPress files
docker run --rm \
-v wordpress-docker_wordpress_data:/data \
-v $(pwd)/backup:/backup \
alpine tar czf /backup/wordpress-backup.tar.gz -C /data .
# Backup MySQL database
docker exec wp-mysql mysqldump \
-u root -pRootPassword456! \
wordpress > ./database/backup/wordpress-backup.sql
# Remove unused volumes
docker volume prune
Best Practices for Docker WordPress Setup
-
Use specific image versions:Never use
latesttags in production - Separate data volumes:Keep WordPress files and database data in separate volumes
- Network isolation:Use custom networks instead of the default bridge
-
Environment variables:Store all configuration in
.envfiles - Regular backups:Implement automated backup strategies for volumes
- Resource limits:Set memory and CPU limits in production
Real World Example: Multi-Site Setup
Setting up WordPress Multisite with Docker:
# docker-compose-multisite.yml
version: '3.8'
services:
wordpress-multisite:
image: wordpress:6.4-php8.2-apache
container_name: wp-multisite
restart: unless-stopped
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: mysql:3306
WORDPRESS_DB_NAME: multisite
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppass
WORDPRESS_CONFIG_EXTRA: |
/* Multisite configuration */
define('WP_ALLOW_MULTISITE', true);
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false);
define('DOMAIN_CURRENT_SITE', 'localhost');
define('PATH_CURRENT_SITE', '/');
define('SITE_ID_CURRENT_SITE', 1);
define('BLOG_ID_CURRENT_SITE', 1);
volumes:
- multisite_data:/var/www/html
- ./multisite-content:/var/www/html/wp-content
depends_on:
- mysql
networks:
- wp-multisite-network
mysql:
image: mysql:8.0
container_name: wp-multisite-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: multisite
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppass
volumes:
- multisite_mysql:/var/lib/mysql
networks:
- wp-multisite-network
volumes:
multisite_data:
multisite_mysql:
networks:
wp-multisite-network:
driver: bridge
Practice Exercise
Let's get your WordPress Docker environment running:
Try It Now
Troubleshooting Common Issues
Port Already in Use
# Check what's using port 8080
sudo lsof -i :8080
# Change port in .env file
WP_PORT=8082
Permission Issues
# Fix ownership for wp-content
docker exec wp-site chown -R www-data:www-data /var/www/html/wp-content
# Set correct permissions
docker exec wp-site chmod -R 755 /var/www/html/wp-content
Database Connection Failed
# Check if MySQL is running
docker-compose ps
# View MySQL logs
docker-compose logs mysql
# Test connection
docker exec -it wp-mysql mysql -u wpuser -p