Docker-based Development Workflow
Learning Objectives
- Implement hot-reloading for theme and plugin development
- Manage environment variables for different stages
- Set up multi-site development environments
- Optimize Docker workflow for productivity
Introduction
A well-configured Docker workflow accelerates WordPress development by providing instant feedback, easy environment switching, and team collaboration features.
Key Workflow Benefits
Theme Development Workflow
Setting up an efficient theme development environment with live reloading:
Directory Structure for Theme Development
wordpress-docker/
├── docker-compose.yml
├── .env
├── themes/
│ └── my-custom-theme/
│ ├── style.css
│ ├── index.php
│ ├── functions.php
│ ├── assets/
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
│ ├── template-parts/
│ └── package.json
└── plugins/
└── my-custom-plugin/
Docker Compose Configuration for Development
# docker-compose.yml
version: '3.8'
services:
wordpress:
image: wordpress:6.4-php8.2-apache
container_name: wp-dev
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: mysql:3306
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppass
# Development settings
WORDPRESS_DEBUG: 'true'
WORDPRESS_DEBUG_LOG: 'true'
WORDPRESS_DEBUG_DISPLAY: 'false'
WP_ENVIRONMENT_TYPE: 'local'
volumes:
# Mount theme for live development
- ./themes/my-custom-theme:/var/www/html/wp-content/themes/my-custom-theme
# Mount plugin for live development
- ./plugins/my-custom-plugin:/var/www/html/wp-content/plugins/my-custom-plugin
# Persist uploads
- wp-uploads:/var/www/html/wp-content/uploads
# Custom PHP configuration
- ./config/php/dev.ini:/usr/local/etc/php/conf.d/dev.ini
depends_on:
- mysql
networks:
- wp-dev
mysql:
image: mysql:8.0
container_name: wp-mysql
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppass
volumes:
- mysql-data:/var/lib/mysql
networks:
- wp-dev
# Node.js container for build tools
node:
image: node:18-alpine
container_name: wp-node
working_dir: /app
volumes:
- ./themes/my-custom-theme:/app
command: npm run watch
networks:
- wp-dev
# Mailhog for email testing
mailhog:
image: mailhog/mailhog
container_name: wp-mail
ports:
- "1025:1025"
- "8025:8025"
networks:
- wp-dev
volumes:
mysql-data:
wp-uploads:
networks:
wp-dev:
driver: bridge
Plugin Development Workflow
Efficient plugin development with Docker:
Plugin File Structure
<?php
/**
* Plugin Name: My Custom Plugin
* Plugin URI: https://example.com/
* Description: A custom WordPress plugin
* Version: 1.0.0
* Author: Your Name
* Text Domain: my-custom-plugin
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Define plugin constants
define('MCP_VERSION', '1.0.0');
define('MCP_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('MCP_PLUGIN_URL', plugin_dir_url(__FILE__));
// Include required files
require_once MCP_PLUGIN_DIR . 'includes/class-plugin.php';
// Initialize plugin
function mcp_init() {
$plugin = new My_Custom_Plugin();
$plugin->run();
}
add_action('plugins_loaded', 'mcp_init');
Development Tools Integration
{
"name": "my-custom-theme",
"version": "1.0.0",
"scripts": {
"dev": "webpack --mode development --watch",
"build": "webpack --mode production",
"watch": "browser-sync start --proxy 'localhost:8080' --files '**/*.php, **/*.css, **/*.js'",
"lint": "eslint assets/js/**/*.js",
"format": "prettier --write '**/*.{js,css,scss}'"
},
"devDependencies": {
"webpack": "^5.88.0",
"webpack-cli": "^5.1.4",
"browser-sync": "^2.29.3",
"sass": "^1.63.6",
"autoprefixer": "^10.4.14"
}
}
Managing Environment Variables
Using different configurations for development, staging, and production:
graph LR
A[.env.development] --> D[docker-compose.yml]
B[.env.staging] --> D
C[.env.production] --> D
D --> E[WordPress Container]
style A fill:#4caf50
style B fill:#ff9800
style C fill:#f44336
Environment-Specific Files
# .env.development
WP_ENVIRONMENT_TYPE=local
WP_DEBUG=true
WP_DEBUG_LOG=true
WP_DEBUG_DISPLAY=false
WP_CACHE=false
SCRIPT_DEBUG=true
DB_HOST=mysql
DB_NAME=wordpress_dev
DB_USER=wp_dev
DB_PASSWORD=devpass123
# .env.staging
WP_ENVIRONMENT_TYPE=staging
WP_DEBUG=true
WP_DEBUG_LOG=true
WP_DEBUG_DISPLAY=false
WP_CACHE=true
SCRIPT_DEBUG=false
DB_HOST=mysql
DB_NAME=wordpress_staging
DB_USER=wp_staging
DB_PASSWORD=stagingpass456
# .env.production
WP_ENVIRONMENT_TYPE=production
WP_DEBUG=false
WP_DEBUG_LOG=false
WP_DEBUG_DISPLAY=false
WP_CACHE=true
SCRIPT_DEBUG=false
DB_HOST=mysql
DB_NAME=wordpress_prod
DB_USER=wp_prod
DB_PASSWORD=prodpass789!
Loading Environment-Specific Configuration
# Development
docker-compose --env-file .env.development up -d
# Staging
docker-compose --env-file .env.staging up -d
# Production
docker-compose --env-file .env.production up -d
Multi-Site Development Environments
Running multiple WordPress sites simultaneously:
Multi-Project Docker Compose
# docker-compose.multi.yml
version: '3.8'
services:
# Site 1: Client A
wordpress-client-a:
image: wordpress:latest
container_name: wp-client-a
ports:
- "8081:80"
environment:
WORDPRESS_DB_HOST: mysql:3306
WORDPRESS_DB_NAME: client_a_db
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppass
volumes:
- ./sites/client-a/themes:/var/www/html/wp-content/themes
- ./sites/client-a/plugins:/var/www/html/wp-content/plugins
networks:
- wp-multi-network
# Site 2: Client B
wordpress-client-b:
image: wordpress:latest
container_name: wp-client-b
ports:
- "8082:80"
environment:
WORDPRESS_DB_HOST: mysql:3306
WORDPRESS_DB_NAME: client_b_db
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppass
volumes:
- ./sites/client-b/themes:/var/www/html/wp-content/themes
- ./sites/client-b/plugins:/var/www/html/wp-content/plugins
networks:
- wp-multi-network
# Shared MySQL for all sites
mysql:
image: mysql:8.0
container_name: wp-multi-mysql
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_MULTIPLE_DATABASES: client_a_db,client_b_db
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppass
volumes:
- mysql-multi-data:/var/lib/mysql
- ./config/mysql/init-multi-db.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- wp-multi-network
# Reverse proxy for nice URLs
nginx-proxy:
image: nginx:alpine
container_name: wp-proxy
ports:
- "80:80"
volumes:
- ./config/nginx/proxy.conf:/etc/nginx/nginx.conf
depends_on:
- wordpress-client-a
- wordpress-client-b
networks:
- wp-multi-network
volumes:
mysql-multi-data:
networks:
wp-multi-network:
driver: bridge
Workflow Automation Scripts
Useful scripts to streamline your Docker WordPress workflow:
Makefile for Common Tasks
# Makefile
.PHONY: help up down restart logs shell backup restore clean
help:
@echo "Available commands:"
@echo " make up - Start all containers"
@echo " make down - Stop all containers"
@echo " make restart - Restart all containers"
@echo " make logs - View container logs"
@echo " make shell - Open WordPress container shell"
@echo " make backup - Backup database and files"
@echo " make restore - Restore from backup"
@echo " make clean - Remove containers and volumes"
up:
docker-compose up -d
@echo "WordPress is running at http://localhost:8080"
down:
docker-compose down
restart:
docker-compose restart
logs:
docker-compose logs -f
shell:
docker exec -it wp-dev bash
wp-cli:
docker exec -it wp-dev wp $(filter-out $@,$(MAKECMDGOALS))
backup:
@echo "Creating backup..."
@mkdir -p backups
@docker exec wp-mysql mysqldump -u root -prootpass wordpress > backups/db-$$(date +%Y%m%d-%H%M%S).sql
@docker run --rm -v wordpress-docker_wp-uploads:/data -v $$(pwd)/backups:/backup alpine tar czf /backup/uploads-$$(date +%Y%m%d-%H%M%S).tar.gz -C /data .
@echo "Backup completed!"
restore:
@echo "Restoring from backup..."
@docker exec -i wp-mysql mysql -u root -prootpass wordpress < $(filter-out $@,$(MAKECMDGOALS))
@echo "Restore completed!"
clean:
docker-compose down -v
@echo "All containers and volumes removed!"
# Catch all target for wp-cli
%:
@:
Docker Workflow Best Practices
- Use bind mounts for development:Mount your theme/plugin directories for instant changes
- Separate environments:Use different .env files for dev, staging, and production
- Version control strategy:Commit docker-compose.yml but not .env files
- Resource limits:Set memory and CPU limits to prevent resource exhaustion
- Health checks:Implement health checks for all services
- Log management:Configure log rotation and centralized logging
Real World Example: Complete Development Stack
A production-ready development environment with all tools:
# docker-compose.dev.yml
version: '3.8'
services:
wordpress:
image: wordpress:6.4-php8.2-apache
container_name: wp-dev
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: mysql:3306
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppass
WORDPRESS_DEBUG: 'true'
WORDPRESS_CONFIG_EXTRA: |
define('WP_ENVIRONMENT_TYPE', 'local');
define('SCRIPT_DEBUG', true);
define('WP_DISABLE_FATAL_ERROR_HANDLER', true);
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');
volumes:
- ./themes:/var/www/html/wp-content/themes
- ./plugins:/var/www/html/wp-content/plugins
- wp-uploads:/var/www/html/wp-content/uploads
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 10s
retries: 3
networks:
- wp-dev
mysql:
image: mysql:8.0
container_name: wp-mysql
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppass
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
networks:
- wp-dev
redis:
image: redis:alpine
container_name: wp-redis
ports:
- "6379:6379"
networks:
- wp-dev
elasticsearch:
image: elasticsearch:7.17.10
container_name: wp-elasticsearch
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
networks:
- wp-dev
adminer:
image: adminer
container_name: wp-adminer
ports:
- "8090:8080"
networks:
- wp-dev
volumes:
mysql-data:
wp-uploads:
networks:
wp-dev:
driver: bridge
Practice Exercise
Set up a complete development workflow:
Try It Now