This article may contain affiliate links. If you buy some products using those links, I may receive monetary benefits. See affiliate disclosure here

Let me show you how to setup a local WordPress installation using Docker.

I did my setup on an Ubuntu Desktop. Even if you're using another operating system, the steps will be mostly the same.

I've created a git repository, which you can clone to get it done faster, so that there is no need to copy the below code blocks one-by-one

Pre-requisites

  • docker container engine
  • docker compose plugin

Depending on the operating system you're using, the steps to install docker container engine and the docker compose plugin may vary. So I suggest you to check the official docker documentation itself to get it done.

Once that is done, you need to create a project folder somewhere on your PC. I created one in the home folder: ~/home/abhinav/Dev/mysite.

Next, we need to place a few files and folders in the directory:

  • compose.yml
  • PHP.Dockerfile
  • uploads.ini
  • conf.d/
    • nginx.conf
  • app/
    • public/
      • (wp goes here)
    • (any other private files, e.g., SSL certs, env variables, etc)

Let's take a look at them one-by-one:

The Docker Compose file

At the root of the project folder, create a file named compose.yml, then place the following contents in it.

services:
    web:
        image: nginx:latest
        ports:
            - "8010:80"
        volumes:
            - ./conf.d:/etc/nginx/conf.d
            - ./app:/app
        depends_on:
            - php
        restart: always
        networks:
            mysite_network:
                aliases:
                    - mysite.local

    php:
        build:
            context: .
            dockerfile: PHP.Dockerfile
        volumes:
            - ./app:/app
            - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
        depends_on:
            - mysql
        restart: always
        networks:
            mysite_network:
                aliases:
                    - mysite.local

    mariadb:
        image: mariadb:latest
        environment:
            MYSQL_ROOT_PASSWORD: 'mystrongpassword'
            MYSQL_USER: 'myname'
            MYSQL_PASSWORD: 'mystrongpassword'
            MYSQL_DATABASE: 'mysite'
        volumes:
            - mysite:/var/lib/mysql
        ports:
            - "33061:3306"
        restart: always
        networks:
            - mysite_network

volumes:
    mysite: {}

networks:
    mysite_network:
        driver: bridge

It's configuration file in YAML format that tells the docker engine how to compose your site. You can think of it as a recipe, which contains three components, or containers:

  1. Nginx container
  2. PHP container
  3. MariaDB container

You can think of these containers as separate computers with their own operating systems (usually a lightweight Ubuntu) spun up and managed by the Docker engine.

Also, note that:

  • Both the Nginx and PHP containers have access to the app/ folder, attached using the volumes option.
  • The app folder contains a public/ folder, which is where you should put your site files, in this case the WordPress files.
  • In addition to the app/ folder, we have also mounted the uploads.ini file to /php/conf.d/ directory inside the PHP container. It has a few PHP configuration settings, like increasing the file upload size (see below).
  • So whenever we edit a file in our project folder, it's simultaneously reflected in all the containers where it is mounted.
  • All the three container are also attached to the same network - mysite_network - so that one container can communicate to another.
  • The Nginx and PHP containers are also given the a network alias name - mysite.local - which is the same as the WordPress site address. This is required for the WP Rest API to work.

PHP Dockerfile

I'm using the official php:fpm package as the base. But in addition to that, we need to set a few more things, like Xdebug for our local environment. That's why we have this Dockerfile.

Look at the above compose file and you can notice that the PHP container is built from this Dockerfile, which contains the instructions to create that container.

FROM php:fpm

RUN apt-get update

RUN apt-get install -y --no-install-recommends \
        libfreetype6-dev \
        libicu-dev \
        libjpeg-dev \
        libmagickwand-dev \
        libpng-dev \
        libwebp-dev \
        libzip-dev

RUN docker-php-ext-install -j "$(nproc)" \ 
        pdo \
        pdo_mysql \
        mysqli \
        bcmath \
        exif \
        gd \
        intl \
        zip

RUN docker-php-ext-configure gd \
        --with-freetype \
        --with-jpeg \
        --with-webp

RUN set -eux; \
    docker-php-ext-enable opcache; \
    { \
        echo 'opcache.memory_consumption=128'; \
        echo 'opcache.interned_strings_buffer=8'; \
        echo 'opcache.max_accelerated_files=4000'; \
        echo 'opcache.revalidate_freq=2'; \
        echo 'opcache.fast_shutdown=1'; \
    } > /usr/local/etc/php/conf.d/opcache-recommended.ini

RUN pecl install xdebug && docker-php-ext-enable xdebug

RUN adduser mypcusername
USER mypcusername

CMD ["php-fpm"]

These are basically Linux commands (like apt-get update), but written in Docker's style.

PHP Ini File

The INI file that configures a PHP variables:

file_uploads = On
memory_limit = 64M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 600

Nginx Site File

Notice that the domain name we intent to use is mysite.local.

client_max_body_size 256M;

server {
    listen 80;
    root /app/public;

    index index.php index.html index.htm;
    server_name mysite.local;

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

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 600s;
    }
}

Upstream Nginx Site File (optional)

Notice that in our Nginx container, the host-to-container port mapping is 8010:80. That means in order for me to access the site, I need to put http://mysite.local:8010 in the browser's address bar, which then gets redirected to port 80 in the container by Docker.

However, I don't like the request URL containing a port number. This also requires me to set the WordPress site address the same way. Otherwise it may cause redirection issues inside WordPress.

So I decided to have another proxy Nginx server on the host machine.

sudo apt update
sudo apt install nginx -y

Then place the following server block in the site file, located at /etc/nginx/sites-available/mysite.local.

server {
        listen 127.0.0.1:80;

        server_name mysite.local;

        location / {
                proxy_pass http://mysite.local:8010;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_read_timeout 600s;
       }
}

This server block receives the requests to mysite.local on the default port 80 (so that there is no need to put the IP address in the request URL). Then proxy it to port 8010 along with the proxy_set_header Host $host directive, so that it preserves the original request URL, which is then directed to the Nginx/WordPress container by Docker.

Then enable it:

sudo ln -s /etc/nginx/sites-available/mysite.local /etc/nginx/sites-enabled/mysite.local

Restart Nginx:

sudo nginx -t
sudo systemctl restart nginx

With this measure, I can cleanly mask out the 8010 part from both the browser's address bar and from the WordPress site address.

I could have avoided this whole proxy thing by directly mapping port 80 on the host machine to port 80 on the Nginx container. So in the compose.yml file, it becomes "80:80". But since 80 is the default HTTP port, I don't want to use it on the host machine.

Edit the hosts file

To use the local domain mysite.local, you need to point it to the loopback IP address.

In my case, I had to add a line to the /etc/hosts file:

sudo nano /etc/hosts
127.0.0.1 mysite.local

Save the file and exit.

Download WordPress

Download and extract WordPress into the app/public/ folder:

cd ~/Dev/mysite/app/public
wget https://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz

Start the Containers

Now it's time to build the containers from the compose file, then start them:

cd ~/Dev/mysite
docker compose build
docker compose up -d

Finish WordPress Installation

Now if you visit the site in a browser, http://mysite.local, you should be greeted with the WordPress installation wizard.

In the database connection details page, the default host name will be localhost. We need to change that to the name of the mariadb container, that is, the name we gave in compose.yml file. In this case it will be mariadb.

Complete the remaining steps and you should have a local WordPress site up and running using Docker.