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)
- public/
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:
- Nginx container
- PHP container
- 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 thevolumes
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 theuploads.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.