A Guide to WordPress Development with Docker and VS Code

Tired of the “it works on my machine” headache? Sick of clunky local servers like MAMP or XAMPP that never quite match your live environment? It’s time to modernize your WordPress development workflow. This guide will walk you through creating a powerful, consistent, and isolated development environment using Docker, VS Code, and Dev Containers.

Once you’re done, you’ll have a professional setup that’s easy to version control, share with teammates, and spin up in minutes. Let’s dive in! πŸš€

Quick Review & Key Points

Quick Review for Interviews

This setup creates a professional, containerized WordPress development environment using Docker and VS Code. The primary goal is to solve “it works on my machine” problems by ensuring every developer has an identical, isolated workspace.

  • Core Technologies:
    • Docker: Used to create and manage isolated environments called containers for the WordPress site and its database.
    • VS Code + Dev Containers Extension: Integrates the code editor directly into the running Docker container for a seamless development experience.
  • Key Configuration Files:
    • docker-compose.yml: The main blueprint. It defines the two services (wordpress and db), connects them on a private network, maps ports (e.g., 8080 on your machine to 80 in the container), and crucially, defines the volumes.
    • .devcontainer/Dockerfile: A recipe for a custom WordPress image. It starts with an official wordpress image and adds essential developer tools like WP-CLI, Xdebug (for debugging), and Node.js/npm.
    • .devcontainer/devcontainer.json: The configuration file for VS Code. It tells the editor how to use the docker-compose.yml file, which service to connect to (wordpress), what extensions to automatically install inside the container, and what commands to run after creation (like fixing file permissions).
    • .env: A simple text file for storing secrets like database passwords. It’s kept out of version control to maintain security.
  • The Most Important Concept: Volumes and Data Persistence
    • The setup uses a bind mount to link your local ./wp-content folder directly into the container at /var/www/html/wp-content.
    • Your code (themes, plugins) lives on your computer’s filesystem, not inside the container. The container only gets a “view” of these files.
    • This is critical for safety: if you rebuild the container (e.g., to update PHP or install a new tool), the old container is destroyed, but your wp-content folder remains completely safe and untouched on your machine. The new container simply links to it again. This prevents any loss of work.
    • The database uses a named volume (db_data) for the same reason, ensuring your posts and settings are preserved separately from the database container’s lifecycle.

Prerequisites: What You’ll Need

Before we start building, make sure you have the following software installed on your system:

  1. Docker Desktop: This is the engine that runs our containers. You can download it for Windows, Mac, or Linux.
  2. Visual Studio Code (VS Code): Our code editor of choice. If you don’t have it, grab it here.
  3. Dev Containers Extension: This is the magic that connects VS Code to our Docker containers. Install it directly from the VS Code Marketplace.

A Note for Windows Users: Setting up WSL & Bash

For the best experience on Windows, Docker Desktop uses the Windows Subsystem for Linux (WSL). WSL lets you run a genuine Linux environment directly on Windows, without the overhead of a traditional virtual machine. This is how you’ll get access to a bash terminal.

Setting it up is a breeze.

  1. Open PowerShell or Command Prompt as an Administrator.
  2. Run the following command. This will enable all necessary features and install the default Ubuntu distribution of Linux.

PowerShell / Command Prompt:

wsl --install

After a restart, you’re all set! You can access your Linux terminal by simply typing wsl or bash in your command prompt or by launching the “Ubuntu” app from your Start Menu.


Project Structure: The Blueprint

A well-organized project is a happy project. Here’s the file and folder structure we’ll be creating. This layout keeps our configuration clean and separates our custom code from the WordPress core.

your-wordpress-project/
β”œβ”€β”€ .devcontainer/
β”‚   β”œβ”€β”€ Dockerfile
β”‚   └── devcontainer.json
β”œβ”€β”€ wp-content/
β”‚   β”œβ”€β”€ plugins/
β”‚   └── themes/
β”œβ”€β”€ .env
└── docker-compose.yml
  • your-wordpress-project/: The root of your entire project.
  • .devcontainer/: This special folder holds the configuration for our VS Code development container.
  • wp-content/: This is where you live! Your custom themes and plugins go here. We’ll mount this directory directly into our WordPress container.
  • .env: A simple text file to store our environment variables, like database passwords. It’s crucial to keep secrets out of your main configuration files.
  • docker-compose.yml: The master plan. This file tells Docker how to build and connect our services (WordPress and a database).

The Configuration Files: A Deep Dive

Let’s break down each configuration file. Copy and paste the following code into the corresponding files within your project structure.

1. The Environment File (.env)

First, create a file named .env in your project root. This file will hold all our secrets and environment-specific settings. Remember to add .env to your .gitignore file to avoid committing secrets to version control!

# .env

# Database Credentials
MYSQL_ROOT_PASSWORD=supersecretrootpassword
MYSQL_DATABASE=wordpress_dev
MYSQL_USER=wp_user
MYSQL_PASSWORD=wp_password

# Xdebug Configuration
# Set XDEBUG_MODE to 'debug' to enable debugging. Leave as 'off' otherwise.
XDEBUG_MODE=off
XDEBUG_PORT=9003

These variables are automatically read by Docker Compose and injected into our containers.

2. The Docker Compose File (docker-compose.yml)

This file is the orchestra conductor. It defines the services (containers) that make up our application and how they interact.

# docker-compose.yml
version: '3.8'

services:
  # WordPress Service (where VS Code will connect)
  wordpress:
    build:
      context: . # Build context is the project root
      dockerfile: .devcontainer/Dockerfile # Path to your Dockerfile
    # IMPORTANT: Change this to a unique name for your project!
    container_name: your-project-name-wordpress
    restart: unless-stopped
    volumes:
      # Mount your local wp-content into the container's wp-content
      - ./wp-content:/var/www/html/wp-content:cached
    ports:
      - "8080:80" # Map port 8080 on host to 80 in container
    environment:
      WORDPRESS_DB_HOST: db:3306 # Service name 'db' and default port
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      # Add Xdebug configuration environment variables
      XDEBUG_MODE: ${XDEBUG_MODE:-off}
      XDEBUG_CONFIG: "client_host=host.docker.internal client_port=${XDEBUG_PORT:-9003}"
    depends_on:
      - db # Wait for the database to be ready
    networks:
      - wp_dev_network

  # Database Service (MariaDB - MySQL compatible)
  db:
    image: mariadb:10.6 # Use a specific version for stability
    # IMPORTANT: Change this to a unique name for your project!
    container_name: your-project-name-db
    restart: unless-stopped
    volumes:
      - db_data:/var/lib/mysql # Persist database data using a named volume
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    networks:
      - wp_dev_network

# Named volume for persistent database storage
volumes:
  db_data:

# Custom network for services to communicate
networks:
  wp_dev_network:
    driver: bridge

The Magic of Volumes: Keeping Your Code Safe

The single most important concept for development in this setup is the volume mount. Look at this line in the wordpress service:

volumes:
  - ./wp-content:/var/www/html/wp-content:cached

This line does not copy your files. Instead, it creates a live, two-way sync or “mirror” between the ./wp-content folder on your local computer (the host) and the /var/www/html/wp-content folder inside the running container.

  • When you edit a theme file in VS Code on your machine, that change is instantly available inside the container because it’s the exact same file.
  • When WordPress uploads a media file inside the container, that file instantly appears in your local ./wp-content/uploads folder.

How Volumes Protect Your Files

Containers are designed to be ephemeral, meaning they can be thrown away and replaced at any time. This is a good thing! It allows you to quickly update your environment or start fresh. But what happens to your code?

  • Without a volume: Any file created inside a container’s filesystem is part of that container. If you rebuild the container (for instance, because you used image: wordpress:latest and a new version was released), the old container is deleted, and all the code you wrote inside it is lost forever.
  • With a volume: Your wp-content folderβ€”the home of your themes and pluginsβ€”lives safely on your local filesystem. It is not part of the container. When you rebuild, Docker throws away the old container and creates a brand new one. This new container then simply “mounts” or connects to your existing local wp-content folder. All your work remains untouched and is immediately available to the new container.

This separation of your persistent code (the volume) from the disposable application environment (the container) is the key to a safe and flexible development workflow. The same principle applies to the db_data volume for the database, which ensures your posts, pages, and settings survive even if the database container is rebuilt.

3. The Dockerfile (.devcontainer/Dockerfile)

The Dockerfile contains the build instructions for our custom WordPress container. It starts with an official WordPress image and then adds our development tools on top.

# .devcontainer/Dockerfile

# Start from an official WordPress image
FROM wordpress:6.5-php8.2-apache

# Switch to root user to install packages
USER root

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    git \
    zip unzip \
    vim \
    sudo \
    libzip-dev \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    nodejs \
    npm \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

# --- Install and Configure Xdebug ---
RUN pecl install xdebug && docker-php-ext-enable xdebug
RUN echo "xdebug.mode = \${XDEBUG_MODE:-off}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.start_with_request = yes" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.client_host = host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.client_port = \${XDEBUG_PORT:-9003}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.log = /tmp/xdebug.log" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.idekey = VSCODE" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

# --- Install common PHP extensions ---
RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \
    docker-php-ext-install -j$(nproc) gd zip mysqli opcache

# --- Install WP-CLI ---
RUN 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

# --- Permissions ---
RUN chown -R www-data:www-data /var/www/html/wp-content

# Switch back to the default Apache user for better security
USER www-data

4. The Dev Container JSON (.devcontainer/devcontainer.json)

This file is purely for VS Code. It tells the Dev Containers extension how to integrate our Docker setup into the editor, creating a seamless experience.

// .devcontainer/devcontainer.json
{
    "name": "WordPress Dev Environment (Docker)",
    "dockerComposeFile": [
        "../docker-compose.yml"
    ],
    "service": "wordpress",
    "workspaceFolder": "/var/www/html",
    "postCreateCommand": "sudo chown -R www-data:www-data /var/www/html/wp-content && echo 'Container ready!'",
    "customizations": {
        "vscode": {
            "settings": {
                "intelephense.environment.phpVersion": "8.2.0",
                "php.validate.executablePath": "/usr/local/bin/php",
                "[php]": {
                    "editor.defaultFormatter": "bmewburn.vscode-intelephense-client",
                    "editor.formatOnSave": true
                }
            },
            "extensions": [
                "bmewburn.vscode-intelephense-client",
                "xdebug.php-debug",
                "eamodio.gitlens",
                "streetsidesoftware.code-spell-checker",
                "esbenp.prettier-vscode",
                "EditorConfig.EditorConfig"
            ]
        }
    },
    "remoteUser": "root"
}

Let’s Build It! Launching Your Environment

With all the files in place, the magic is about to happen.

  1. Open the Project in VS Code: Launch VS Code and go to File > Open Folder... and select your your-wordpress-project directory.
  2. Reopen in Container: As soon as you open the folder, VS Code’s Dev Containers extension will detect the .devcontainer folder and a notification will pop up in the bottom-right corner. Click “Reopen in Container”. (If you miss the pop-up, open the Command Palette with Ctrl+Shift+P or Cmd+Shift+P and search for “Dev Containers: Reopen in Container”.)
  3. Wait for the Build: The first time you do this, Docker will need to build your custom image. This might take a few minutes, but subsequent launches will be much faster.
  4. Welcome to Your Dev Site! Once the build is complete, your VS Code window will reload. Open your web browser and navigate to http://localhost:8080.
  5. Install WordPress: You’ll be greeted by the famous WordPress installation screen. When it asks for database credentials, use the exact same values you put in your .env file:
    • Database Name: wordpress_dev
    • Username: wp_user
    • Password: wp_password
    • Database Host: db (This is crucial! Don’t use localhost.)

That’s it! You now have a fully functional, local WordPress site running in Docker.


Using Your New Superpowers

Your environment is ready. Now what?

Seamless Theme and Plugin Development

Go to the wp-content/themes folder on your local machine. Create a new theme or modify an existing one. Save the file. Because our wp-content directory is mounted as a volume, the changes are instantly reflected inside the container. Just refresh your browser to see them. No FTP, no file syncingβ€”just pure, simple development.

Using WP-CLI

Open the terminal inside VS Code (Ctrl+\`` or Cmd+`). You are now in a bashshell *inside* thewordpress` container. You have full access to WP-CLI. Try it out:

Bash (inside the VS Code terminal):

# List installed plugins
wp plugin list

# Install and activate a plugin
wp plugin install query-monitor --activate

# Check the WordPress version
wp core version

Debugging with Xdebug

This is the real game-changer. Let’s set up step-debugging.

  1. Enable Xdebug: In your .env file, change XDEBUG_MODE=off to XDEBUG_MODE=debug.
  2. Rebuild the Container: Open the Command Palette (Ctrl+Shift+P) and run “Dev Containers: Rebuild Container”.
  3. Create a Launch Configuration: In VS Code, go to the “Run and Debug” panel (the bug icon on the sidebar). Click “create a launch.json file” and select “PHP”. This will create a .vscode/launch.json file. Replace its contents with this:
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9003,
            "pathMappings": {
                "/var/www/html/": "${workspaceFolder}"
            }
        }
    ]
}
  1. Start Debugging: Set a breakpoint (a red dot) next to a line number in a PHP file. In the “Run and Debug” panel, press the green “Play” button next to “Listen for Xdebug.” Refresh your WordPress site, and VS Code will pause execution at your breakpoint.

Conclusion

You’ve successfully built a modern, robust WordPress development environment that will serve you well on projects of any size. By defining your entire stack in code and separating your persistent data with volumes, you’ve created a portable, reproducible, and safe workspace that will save you countless hours and headaches.

Welcome to the future of WordPress development. Happy coding!