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

  1. Open WordPress at http://your-server-ip:8080
  2. Select your language
  3. 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
  4. Click Install WordPress
  5. 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:

  1. Install the Redis Object Cache plugin
  2. Add to wp-config.php (or use the WORDPRESS_CONFIG_EXTRA environment variable):
  wordpress:
    environment:
      WORDPRESS_CONFIG_EXTRA: |
        define('WP_REDIS_HOST', 'wordpress-redis');
        define('WP_REDIS_PORT', 6379);
  1. 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:

  1. Forward your domain to http://wordpress:80 (or http://your-server:8080)
  2. 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.

  1. 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:

  1. Verify MariaDB is running: docker ps | grep wordpress-db
  2. Check that WORDPRESS_DB_PASSWORD matches MARIADB_PASSWORD
  3. Verify the database host is wordpress-db (Docker service name)
  4. 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.

Comments