Docker for WordPress: From Development to Production Without the Mess

If you have ever installed WordPress on a server manually with a LEMP stack, you have probably experienced the frustration of tweaking nginx.conf only to forget the PHP-FPM configuration, or moving a project to a new machine and spending an entire afternoon setting up the environment again. Docker solves this problem by packaging the entire application — WordPress, MySQL, Nginx — into lightweight, portable containers that run consistently across every environment. This guide will walk you through deploying WordPress with Docker from zero, without the mess, with copy-paste commands that work right away.

Docker for WordPress: From Development to Production Without the Mess

1. Why Docker for WordPress?

Before touching any code, let us understand what problem Docker solves:

Traditional ProblemHow Docker Solves ItReal-World Benefit
“It works on my machine” — dev environment differs from productionContainers hold all dependencies: PHP version, extensions, Nginx configRuns identically on your laptop, staging server, and production
Manual installation is time-consuming and error-proneOne docker-compose.yml file defines the entire stackdocker compose up and you are done, under one minute
Hard to manage multiple projects with different PHP/MySQL versionsEach project has its own isolated containersProject A uses PHP 7.4, Project B uses PHP 8.3 — no conflicts
Database backup/restore is complexDocker volumes store data separately, easy to backup with docker execBackup is just one SQL file, restore is just one command
Scaling is difficultRun multiple WordPress containers behind a load balancerHorizontal scaling is easy with Docker Swarm or Kubernetes

2. WordPress + Docker Stack Architecture

We will build a stack with three containers:

  1. wordpress: wordpress:latest image — runs PHP-FPM, holds WordPress source code
  2. db: mysql:8.0 image — MySQL database for WordPress
  3. nginx: nginx:alpine image — reverse proxy, handles static files, forwards PHP requests to the WordPress container

Client
Nginx (port 80)
WordPress PHP-FPM (port 9000)
MySQL (port 3306)

3. Project Directory Structure

Create a project directory with this structure:

wordpress-docker/
├── docker-compose.yml      # Defines the entire stack
├── nginx/
│   └── default.conf        # Nginx configuration
├── wordpress/              # WordPress source (mounted from container)
└── db_data/                # MySQL data (Docker volume)

4. The docker-compose.yml File — Heart of the Stack

This is the most important file. It defines three services, a shared network, and volumes for data persistence:

version: '3.8'

services:
  db:
    image: mysql:8.0
    container_name: wp_db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword123
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpresspass
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - wp_network
    command: --default-authentication-plugin=mysql_native_password

  wordpress:
    image: wordpress:php8.3-fpm
    container_name: wp_app
    restart: unless-stopped
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpresspass
    volumes:
      - ./wordpress:/var/www/html
    networks:
      - wp_network
    depends_on:
      - db

  nginx:
    image: nginx:alpine
    container_name: wp_nginx
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./wordpress:/var/www/html
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    networks:
      - wp_network
    depends_on:
      - wordpress

volumes:
  db_data:

networks:
  wp_network:
    driver: bridge

Explaining each part:

  • restart: unless-stopped — Container auto-restarts when the server reboots, unless you manually stop it.
  • volumesdb_data is a named volume, Docker manages it and data persists even when the container is deleted. ./wordpress is a bind mount, allowing you to edit files from the host.
  • networks — Containers in the same network can reach each other by service name. WordPress connects to MySQL via db:3306.
  • depends_on — Ensures MySQL starts before WordPress, avoiding connection refused errors.

5. Nginx Configuration — Static Files and PHP Proxy

Create nginx/default.conf:

server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 6M;
        add_header Cache-Control "public, immutable";
    }

    location ~ /\.ht {
        deny all;
    }
}

Key points:

  • fastcgi_pass wordpress:9000 — Nginx sends PHP requests to the WordPress container on port 9000 (PHP-FPM). Docker DNS automatically resolves wordpress to the container IP.
  • try_files — Supports WordPress permalinks. If the file does not exist, fall back to index.php.
  • expires 6M — Cache static assets for 6 months, reducing server load.

6. Launch the Stack — Up and Running in 30 Seconds

Open a terminal, cd into the project directory, and run:

# 1. Create directories
mkdir -p wordpress-docker/nginx wordpress-docker/wordpress

# 2. Create docker-compose.yml and nginx/default.conf (copy from above)

# 3. Launch
cd wordpress-docker
docker compose up -d

# 4. Check running containers
docker compose ps

# 5. View logs if needed
docker compose logs -f

After running, visit http://localhost and you will see the WordPress setup screen. Complete the setup as usual.

7. Managing WordPress in Docker — Common Commands

OperationCommand
List containersdocker compose ps
View real-time logsdocker compose logs -f
Access WordPress container shelldocker exec -it wp_app bash
Run WP-CLIdocker exec -it wp_app wp --allow-root [command]
Backup databasedocker exec wp_db mysqldump -u wordpress -pwordpresspass wordpress > backup.sql
Restore databasedocker exec -i wp_db mysql -u wordpress -pwordpresspass wordpress < backup.sql
Restart a servicedocker compose restart nginx
Rebuild after config changesdocker compose up -d --build
Stop the entire stackdocker compose down
Stop and delete volumes (careful)docker compose down -v

8. Installing WP-CLI — Manage WordPress from the Command Line

Instead of using wp-admin, you can install plugins, update core, and create users with WP-CLI:

# Install WP-CLI into the WordPress container
docker exec -it wp_app bash -c "
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
mv wp-cli.phar /usr/local/bin/wp
"

# Check WP-CLI
docker exec -it wp_app wp --allow-root --info

# Install plugin
docker exec -it wp_app wp --allow-root plugin install wordpress-seo --activate

# Create admin user
docker exec -it wp_app wp --allow-root user create admin [email protected] --role=administrator --user_pass=admin123

# Update WordPress core
docker exec -it wp_app wp --allow-root core update

9. Production Optimization

Development and production differ in several important ways:

9.1. Use .env for Secrets

Never hardcode passwords in docker-compose.yml. Create a .env file:

MYSQL_ROOT_PASSWORD=your_secure_root_password
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=your_secure_db_password
WORDPRESS_DB_PASSWORD=your_secure_db_password

Update docker-compose.yml:

environment:
  MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
  MYSQL_DATABASE: ${MYSQL_DATABASE}
  MYSQL_USER: ${MYSQL_USER}
  MYSQL_PASSWORD: ${MYSQL_PASSWORD}

Add .env to .gitignore so it does not get pushed to Git.

9.2. SSL/TLS with Let’s Encrypt

Add a certbot container for automatic SSL renewal:

  certbot:
    image: certbot/certbot
    container_name: wp_certbot
    volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait ${!}; done;'"

  nginx:
    # ... existing config
    volumes:
      - ./wordpress:/var/www/html
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot

Update nginx/default.conf to redirect HTTP to HTTPS and use certificates:

server {
    listen 80;
    server_name yourdomain.com;
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    root /var/www/html;
    index index.php;

    # ... rest of config same as before
}

9.3. PHP OPcache and Tuning

Create a custom php.ini and mount it into the WordPress container:

; php.ini
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300

opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 4000
opcache.revalidate_freq = 2
opcache.fast_shutdown = 1

Add to docker-compose.yml:

  wordpress:
    # ...
    volumes:
      - ./wordpress:/var/www/html
      - ./php.ini:/usr/local/etc/php/conf.d/custom.ini

9.4. Automated Backup with Cron

Create a daily backup script:

#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
docker exec wp_db mysqldump -u wordpress -pwordpresspass wordpress | gzip > ./backups/wordpress_$DATE.sql.gz
find ./backups -name "*.sql.gz" -mtime +7 -delete

Add to crontab:

0 2 * * * /path/to/wordpress-docker/backup.sh

10. Comparison: Docker vs Traditional LEMP

CriteriaManual LEMPDocker
Setup time30-60 minutes (install each component, configure each file)1-2 minutes (docker compose up -d)
ReproducibilityLow — each server may differ due to manual configurationHigh — same docker-compose.yml runs identically everywhere
Version managementHard — downgrading PHP/MySQL is complexEasy — change image tag, restart container
Multi-projectHard — port conflicts, PHP version clashesEasy — each project has its own isolated container stack
Resource usageLower (runs directly on host)Slightly higher (container overhead ~1-5%)
Learning curveLow — just need to know Linux + Nginx + MySQLMedium — need to understand containers, volumes, networks
Production-readyRequires significant manual optimization effortEasy to scale, easy to backup, easy to CI/CD

11. Troubleshooting — Common Errors

ErrorCauseFix
Error establishing database connectionWordPress cannot connect to MySQL yetWait a few more seconds for MySQL to finish starting, or run docker compose restart wordpress
502 Bad GatewayNginx cannot connect to PHP-FPMCheck fastcgi_pass wordpress:9000 in nginx config. Ensure the WordPress container is running.
Port 80 already in useAnother Apache/Nginx is running on the hostStop the service using the port: sudo systemctl stop nginx or change port mapping to 8080:80
Permission denied when editing WordPress filesFiles in the container belong to www-data userRun sudo chown -R $USER:$USER ./wordpress on the host
Database lost after docker compose downUsed -v flag or did not use a named volumeEnsure you use the named volume db_data, and do not run down -v unless you really want to delete data

12. From Docker to Kubernetes — The Next Step

As your site grows, you might need:

  • Docker Swarm — simple orchestration, built into Docker. Suitable for 1-3 servers.
  • Kubernetes — powerful orchestration, auto-scaling, self-healing. Suitable for large systems, multi-cloud.
  • Docker + Cloud — AWS ECS, Google Cloud Run, Azure Container Instances all support running WordPress containers without managing servers.

But do not rush. For most WordPress sites, a single server with Docker Compose is more than enough. Only consider Kubernetes when you truly need horizontal scaling or running across multiple availability zones.

13. Conclusion

Docker is not a “magic” technology — it is just a tool. But for WordPress, Docker solves exactly the most painful problems: inconsistent environments, repetitive setup, complex backup/restore.

With just two files — docker-compose.yml and nginx/default.conf — you have a production-ready WordPress stack that can run on any machine with Docker, from your personal laptop to a cloud VPS.

Try copying the config above, run docker compose up -d, and experience the feeling of “everything just works” — that is exactly why Docker has become the industry standard for application deployment.

Comments


  • No comments yet.

Web-Based Tools

Press Ctrl + \ on desktop, or swipe left anywhere on mobile.

Login