How to Self-Host WordPress with Docker Compose
What Is WordPress?
WordPress is the world’s most popular content management system, powering over 40% of all websites. The self-hosted version (WordPress.org) gives you complete control over your site — no hosting fees to WordPress.com, no plugin restrictions, no arbitrary limits. You own everything: the code, the database, the content. Self-hosting WordPress replaces managed platforms like Squarespace, Wix, and WordPress.com.
Prerequisites
- A Linux server (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed (guide)
- 1 GB of RAM minimum (2 GB+ recommended)
- A domain name pointed at your server
- Ports 80/443 available (or a reverse proxy)
Docker Compose Configuration
Create a docker-compose.yml file:
services:
wordpress:
image: wordpress:6.9.4-php8.4-apache
container_name: wordpress
restart: unless-stopped
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: "wordpress-db"
WORDPRESS_DB_USER: "wordpress"
WORDPRESS_DB_PASSWORD: "change-me-strong-password" # CHANGE THIS
WORDPRESS_DB_NAME: "wordpress"
WORDPRESS_TABLE_PREFIX: "wp_"
volumes:
- wordpress-data:/var/www/html
depends_on:
wordpress-db:
condition: service_healthy
networks:
- wordpress-net
wordpress-db:
image: mariadb:11.7
container_name: wordpress-db
restart: unless-stopped
environment:
MARIADB_ROOT_PASSWORD: "change-me-root-password" # CHANGE THIS
MARIADB_DATABASE: "wordpress"
MARIADB_USER: "wordpress"
MARIADB_PASSWORD: "change-me-strong-password" # Must match WORDPRESS_DB_PASSWORD
volumes:
- wordpress-db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
networks:
- wordpress-net
volumes:
wordpress-data:
wordpress-db-data:
networks:
wordpress-net:
driver: bridge
Important: Change both WORDPRESS_DB_PASSWORD and MARIADB_PASSWORD to matching strong passwords. Change MARIADB_ROOT_PASSWORD to a separate strong password.
Start the stack:
docker compose up -d
Initial Setup
- Open WordPress at
http://your-server-ip:8080 - Select your language
- Set up the admin account:
- Site Title: Your site name
- Username: Choose something other than “admin” (security)
- Password: Use the generated strong password
- Email: Your admin email
- Click Install WordPress
- Log in at
http://your-server-ip:8080/wp-admin
Configuration
Performance: Add Redis Object Cache
WordPress makes many database queries per page load. Redis caches these queries in memory:
redis:
image: redis:7-alpine
container_name: wordpress-redis
restart: unless-stopped
volumes:
- redis-data:/data
networks:
- wordpress-net
Add the Redis volume:
volumes:
redis-data:
Then in WordPress:
- Install the Redis Object Cache plugin
- Add to
wp-config.php(or use theWORDPRESS_CONFIG_EXTRAenvironment variable):
wordpress:
environment:
WORDPRESS_CONFIG_EXTRA: |
define('WP_REDIS_HOST', 'wordpress-redis');
define('WP_REDIS_PORT', 6379);
- Activate the plugin and enable Redis in Settings > Redis
Upload Limits
The default PHP upload limit is 2 MB. Increase it:
wordpress:
environment:
WORDPRESS_CONFIG_EXTRA: |
define('WP_MEMORY_LIMIT', '256M');
volumes:
- wordpress-data:/var/www/html
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
Create uploads.ini:
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 300
Multisite
Enable WordPress Multisite to run multiple sites from one installation. Add before first setup:
environment:
WORDPRESS_CONFIG_EXTRA: |
define('WP_ALLOW_MULTISITE', true);
Then follow the Network Setup wizard in Tools > Network Setup after installation.
Security Hardening
Add security-related constants:
environment:
WORDPRESS_CONFIG_EXTRA: |
define('DISALLOW_FILE_EDIT', true); # Disable plugin/theme editor
define('WP_AUTO_UPDATE_CORE', true); # Auto-update core
define('FORCE_SSL_ADMIN', true); # Force HTTPS for admin
WP-CLI
The WordPress Docker image includes WP-CLI. Run commands with:
docker exec wordpress wp --allow-root plugin list
docker exec wordpress wp --allow-root core update
docker exec wordpress wp --allow-root cache flush
Reverse Proxy
Behind Nginx Proxy Manager or Traefik:
- Forward your domain to
http://wordpress:80(orhttp://your-server:8080) - Add these environment variables for proper URL handling:
environment:
WORDPRESS_CONFIG_EXTRA: |
define('FORCE_SSL_ADMIN', true);
if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($$_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) {
$$_SERVER['HTTPS'] = 'on';
}
Note: $$ is required in Docker Compose YAML to escape the dollar sign.
- Set your site URL in WordPress: Settings > General > WordPress Address and Site Address to
https://yourdomain.com
See Reverse Proxy Setup.
Backup
Two things to back up: the database and the WordPress files (themes, plugins, uploads).
Database Backup
docker exec wordpress-db mariadb-dump -u wordpress -p"your-password" wordpress > wordpress-backup.sql
Files Backup
docker run --rm \
-v wordpress-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/wordpress-files-backup.tar.gz /data
Automated Daily Backup Script
#!/bin/bash
DATE=$(date +%Y-%m-%d)
BACKUP_DIR="/opt/backups/wordpress"
mkdir -p "$BACKUP_DIR"
# Database
docker exec wordpress-db mariadb-dump -u wordpress -pchangeme wordpress > "$BACKUP_DIR/db-$DATE.sql"
# Files (uploads, plugins, themes)
docker run --rm \
-v wordpress-data:/data \
-v "$BACKUP_DIR":/backup \
alpine tar czf "/backup/files-$DATE.tar.gz" /data/wp-content
# Keep only 7 days of backups
find "$BACKUP_DIR" -type f -mtime +7 -delete
See Backup Strategy.
Troubleshooting
”Error establishing a database connection”
Symptom: WordPress shows a database connection error.
Fix:
- Verify MariaDB is running:
docker ps | grep wordpress-db - Check that
WORDPRESS_DB_PASSWORDmatchesMARIADB_PASSWORD - Verify the database host is
wordpress-db(Docker service name) - Check MariaDB logs:
docker logs wordpress-db
White screen of death
Symptom: WordPress shows a blank white page.
Fix: Enable debug mode temporarily:
environment:
WORDPRESS_DEBUG: "1"
WORDPRESS_CONFIG_EXTRA: |
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
Check logs at: docker exec wordpress cat /var/www/html/wp-content/debug.log
Redirect loop after enabling SSL
Symptom: Browser shows “too many redirects.”
Fix: Ensure the FORCE_SSL_ADMIN and HTTP_X_FORWARDED_PROTO configuration is set correctly (see Reverse Proxy section). Also verify Site URL and WordPress URL in the database:
docker exec wordpress wp --allow-root option update siteurl "https://yourdomain.com"
docker exec wordpress wp --allow-root option update home "https://yourdomain.com"
File upload fails
Symptom: Media uploads fail or timeout.
Fix: Increase PHP limits with uploads.ini (see Configuration section). Also check that the uploads directory is writable:
docker exec wordpress ls -la /var/www/html/wp-content/uploads
Plugin/theme installation fails
Symptom: “Could not create directory” errors when installing plugins.
Fix: Check file ownership inside the container:
docker exec wordpress chown -R www-data:www-data /var/www/html/wp-content
Resource Requirements
- RAM: 256 MB idle, 512 MB - 1 GB under traffic (depends on plugins and caching)
- CPU: Low for static pages, moderate with dynamic content and many plugins
- Disk: 100 MB for WordPress core. Grows with media uploads, plugins, and themes.
Frequently Asked Questions
WordPress.org vs WordPress.com — what’s the difference?
WordPress.org is the free, open-source software you self-host (this guide). WordPress.com is a hosted service by Automattic — you pay them to run it. Self-hosting gives you full control, no restrictions on plugins/themes, and no monthly fees beyond your server cost.
Is WordPress secure?
WordPress core is well-maintained. Most security issues come from outdated plugins, weak passwords, and misconfigured servers. Keep everything updated, use strong passwords, disable the file editor (DISALLOW_FILE_EDIT), and limit login attempts.
Should I use WordPress or a static site generator?
For blogs and content sites that need frequent updates, WordPress is easier to manage. For developer portfolios, documentation, or sites where maximum speed matters, a static site generator (Hugo, Astro) is better. WordPress has a larger ecosystem of themes and plugins.
Can I run multiple WordPress sites on one server?
Yes. Use separate Docker Compose stacks with different port mappings and database containers for each site, or enable WordPress Multisite to run multiple sites from a single WordPress installation sharing one database. Multisite is simpler to manage but limits plugin flexibility per site.
How do I update WordPress in Docker?
Pull the new WordPress image tag, then restart: docker compose pull && docker compose up -d. WordPress runs its database migration automatically on startup. Always back up your database before updating. Don’t use the built-in WordPress updater — it modifies files inside the container, which get lost on restart.
Does self-hosted WordPress need a CDN?
For small sites with modest traffic, no. For sites with heavy media or global visitors, a CDN like Cloudflare (free tier) significantly improves load times by caching static assets at edge locations. It also reduces server load and adds DDoS protection.
Verdict
WordPress is still the most practical choice for most websites. The plugin ecosystem is unmatched — SEO, e-commerce, membership sites, forums — there’s a plugin for everything. Self-hosting with Docker makes deployment and backups straightforward. The downsides: PHP-based sites are slower than static sites, and WordPress is a frequent target for automated attacks (keep it updated). For simple blogs, consider Ghost which is faster and cleaner. For everything else, WordPress’s flexibility is hard to beat.
Related
- WordPress Docker: Slow Performance — Fix
- Hugo vs WordPress: Static vs Dynamic Self-Hosting
- Wagtail vs WordPress: Which CMS Should You Self-Host?
- WordPress vs Strapi: CMS Comparison
- How to Self-Host Ghost
- WordPress vs Ghost
- WordPress vs Hugo
- Best Self-Hosted CMS
- Replace Squarespace
- Docker Compose Basics
- Reverse Proxy Setup
- SSL Certificates
- Backup Strategy
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments