WordPress with PHP 7.4 and MySQL 8.0 using Docker Compose
Disclaimer: This post was not meant to be published. While I was doing WordPress with PHP 7.4 setup I thought someone might find it useful and decided to make it public. Only for testing / development purposes.
TL;DR: You can find source code in https://github.com/aurkenb/docker-wordpress-lemp
It’s been a while since I last posted to the public. So long that, to my shame, I just found out my self-hosted WordPress blog won’t upgrade anymore. It was left in the dark on some server untouched for years. So here I am again, willing to share some ideas. Considering whether to move to Medium in order to avoid blog maintenance or stick with well known WordPress.
Medium became very popular in the last few years. It’s a great tool for writing, sharing ideas and getting discovered. They’ve built-in audience which you cannot really match on your own. Like most (hosted) platforms though, it has a major drawback: you don’t own your distribution. You are not in control of the channel. You just rented out the space. And therefore you are subject to nasty things happening: censorship, changes in algorithms that may affect your visibility, control over your content, inability to reach your audience, or some out-of-your-control random action going on.
🤔 Medium added a link to another story of theirs, in the middle of my story, without letting me know. I get the direction they want to go in, but I don't think I'll be part of the journey anymore. pic.twitter.com/2CTMJewpHk
— Sahil (@shl) April 27, 2020
Don’t get me wrong, it’s perfectly fine to publish on Medium. It’s just that it doesn’t sound reasonable to build my content solely on them. Good old-fashioned blogging suits me best for my own publishing: my site, my brand, my content, my rules.
WordPress with PHP 7.4
It’s clear by now. I’m sticking with WordPress. Let’s try to avoid falling into past mistakes. The new blog installation must be easy setup, maintain, remove and re-create at will. Web pages are complex systems and many parts are involved. Each part needs its own setup. Years ago the process was long and painful. Luckily, the state of the art of the tools have dramatically improved over time. And there’s a new player in the room since I first installed my blog: it’s called Docker.
What’s Docker? AWS Docker page defines it as “a software platform that allows you to build, test, and deploy applications quickly. Docker packages software into standardized units called containers that have everything the software needs to run including libraries, system tools, code, and runtime.“
In other words, Docker lets you create a “box” (container) with all you need to run your software. In our case, we will use Docker Compose, that runs multiple containers as a single service. The main advantage is you setup once and deploy as many times as you want. It will let you install and configure your self-hosted WordPress with a single click. Yes, you are right. You can re-install the whole blog with a single click once and again.
Having introduced a little bit of context, without further delay, let’s dig into the code.
Show me the code
We will start by creating a file named “.env”. This file will store all relevant variables used by the stack.
DOMAIN_NAME=localhost.example
NGINX_VERSION=latest
NGINX_HTTP_PORT=80
NGINX_HTTPS_PORT=443
NGINX_LOG_DIR=./core/logs/nginx
WORDPRESS_VERSION=php7.4-fpm
WORDPRESS_DIR=./core/wordpress
WORDPRESS_THEME_DIR=
WORDPRESS_IMPORT_FILE=
WORDPRESS_DB_NAME=wordpress
WORDPRESS_DB_HOST=mysql
WORDPRESS_DB_USER=root
WORDPRESS_DB_PASSWORD=password
WORDPRESS_TABLE_PREFIX=wp_
MYSQL_VERSION=latest
MYSQL_ROOT_PASSWORD=password
MYSQL_USER=root
MYSQL_PASSWORD=password
MYSQL_DATABASE=wordpress
MYSQL_ALLOW_EMPTY_PASSWORD=no
MYSQL_DIR=./core/mysql
Pretty straightforward stuff.
We define a DOMAIN_NAME
that will be URL of the installation. Ports 80/443 are used by default. Obviously changing NGINX_HTTP_PORT
and NGINX_HTTPS_PORT
values changes the URL to something like http(s)://DOMAIN_NAME:PORT/
. We chose PHP 7.4 + MySQL 8.0 which take advantage of caching_sha2_password auth plugin instead of the old mysql_native_password.
Our project will consist of 3 containers:
- Latest WordPress with PHP 7.4 as CMS.
- MySQL 8.0 as database.
- Nginx latest version as webserver.
Let’s define them “docker-compose.yml” file.
Starting with WordPress …
wordpress:
image: wordpress:${WORDPRESS_VERSION:-php7.4-fpm}
container_name: wordpress
depends_on:
- mysql
environment:
- WORDPRESS_TABLE_PREFIX=${WORDPRESS_TABLE_PREFIX:-wp_}
- WORDPRESS_DB_NAME=${WORDPRESS_DB_NAME:-wordpress}
- WORDPRESS_DB_HOST=${WORDPRESS_DB_HOST:-mysql}
- WORDPRESS_DB_USER=${WORDPRESS_DB_USER:-wordpress}
- WORDPRESS_DB_PASSWORD=${WORDPRESS_DB_PASSWORD:-password}
volumes:
- ${WORDPRESS_DIR:-./core/wordpress}:/var/www/html
restart: always
networks:
- wordpress-network
Things start to get interesting here. We tell Docker to use latest WordPress version image with PHP 7.4 and set environment variables to the ones adjusted in .env
file. If they are missing, we use defaults. Volumes:
section sets where WordPress will be placed.
mysql:
image: mysql:${MYSQL_VERSION:-latest}
container_name: mysql
volumes:
- ${MYSQL_DIR:-./core/mysql}:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-password}
- MYSQL_DATABASE=${MYSQL_DATABASE:-wordpress}
- MYSQL_USER=${MYSQL_USER:-root}
- MYSQL_PASSWORD=${MYSQL_PASSWORD:-password}
- MYSQL_ALLOW_EMPTY_PASSWORD=${MYSQL_ALLOW_EMPTY_PASSWORD:-no}
restart: always
networks:
- wordpress-network
MySQL part is no different from PHP. We define working directory and some MySQL variables.
nginx:
build:
context: ./config/nginx
dockerfile: Dockerfile
args:
- NGINX_IMAGE=nginx:${NGINX_VERSION:-latest}
- DOMAIN_NAME=${DOMAIN_NAME:-localhost}
container_name: nginx
depends_on:
- wordpress
ports:
- '${NGINX_HTTP_PORT:-80}:80'
- '${NGINX_HTTPS_PORT:-443}:443'
volumes:
- ${WORDPRESS_DIR:-./core/wordpress}:/var/www/html
- ${NGINX_LOG_DIR:-./core/logs/nginx}:/var/log/nginx
restart: always
networks:
- wordpress-network
Nginx section uses a slight variation. It’s not required but adds a little extra flexibility to the build. Instead of choosing an image, we build our own passing two variables (NGINX_IMAGE
+ DOMAIN_NAME
) and adding some magic. A file named Dockerfile
is expected inside config/nginx/
folder. It will hold instructions to create the container.
Also defines ports:
to redirect ports to the ones adjusted in .env
. Volumes
section stays the same, nothing new.
Create config
folder and another folder nginx
inside it. Create two files there: Dockerfile
and default.conf
.
Copy and paste the following into Dockerfile
file:
ARG NGINX_IMAGE
FROM $NGINX_IMAGE
ARG DOMAIN_NAME
COPY ./default.conf /etc/nginx/conf.d/default.conf
RUN sed -i -r "s/localhost/${DOMAIN_NAME}/g" /etc/nginx/conf.d/default.conf
It will tell Docker which image to select $NGINX_IMAGE
and copy default configuration to be used by Nginx. Additionally sets Nginx server_name
to DOMAIN_NAME
(remember .env
file).
Copy and paste in default.conf
:
server {
listen 80;
listen [::]:80;
server_name localhost;
root /var/www/html;
index index.php;
server_tokens off;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
The full docker-compose.yml
file looks like this:
version: '3'
services:
wordpress:
image: wordpress:${WORDPRESS_VERSION:-php7.4-fpm}
container_name: wordpress
depends_on:
- mysql
environment:
- WORDPRESS_TABLE_PREFIX=${WORDPRESS_TABLE_PREFIX:-wp_}
- WORDPRESS_DB_NAME=${WORDPRESS_DB_NAME:-wordpress}
- WORDPRESS_DB_HOST=${WORDPRESS_DB_HOST:-mysql}
- WORDPRESS_DB_USER=${WORDPRESS_DB_USER:-wordpress}
- WORDPRESS_DB_PASSWORD=${WORDPRESS_DB_PASSWORD:-password}
volumes:
- ${WORDPRESS_DIR:-./core/wordpress}:/var/www/html
restart: always
networks:
- wordpress-network
mysql:
image: mysql:${MYSQL_VERSION:-latest}
container_name: mysql
volumes:
- ${MYSQL_DIR:-./core/mysql}:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-password}
- MYSQL_DATABASE=${MYSQL_DATABASE:-wordpress}
- MYSQL_USER=${MYSQL_USER:-root}
- MYSQL_PASSWORD=${MYSQL_PASSWORD:-password}
- MYSQL_ALLOW_EMPTY_PASSWORD=${MYSQL_ALLOW_EMPTY_PASSWORD:-no}
restart: always
networks:
- wordpress-network
nginx:
build:
context: ./config/nginx
dockerfile: Dockerfile
args:
- NGINX_IMAGE=nginx:${NGINX_VERSION:-latest}
- DOMAIN_NAME=${DOMAIN_NAME:-localhost}
container_name: nginx
depends_on:
- wordpress
ports:
- '${NGINX_HTTP_PORT:-80}:80'
- '${NGINX_HTTPS_PORT:-443}:443'
volumes:
- ${WORDPRESS_DIR:-./core/wordpress}:/var/www/html
- ${NGINX_LOG_DIR:-./core/logs/nginx}:/var/log/nginx
restart: always
networks:
- wordpress-network
volumes:
mysql: {}
wordpress: {}
networks:
wordpress-network:
driver: bridge
You can now run:
docker-compose up -d --build
Here we are
You are ready to go! Up and running latest version of WordPress with PHP 7.4 and Nginx successfully installed.
Complete source code is on Github.
Do you have questions? Suggestions? Feel free to ping me!