WordPress in Docker: Dockerization, automation, and Kubernetes.
The Tres Hombres
The following guide sports three technological heroes:
- WordPress: the most popular CMS on the market, period.
- Docker: the trendiest tech since the first iPhone, now somewhat domesticated.
- Kubernetes: the go-to container orchestration solution for developers worldwide, and the latest buzzword of the web.
Each of them is a real powerhouse on their own, yet getting all three them work together multiplies the benefits to levels that simply cannot be ignored – something we know very well from the history of music.
Image loading...Meet the Hombres
What you're going to learn
In this guide, we're going to show you three things:
- How to dockerize your WordPress website
- How to automate building images with Buddy CI/CD
- How to scale WordPress by running it on a Kubernetes cluster
Before we start, let us answer one essential question.
Why should I dockerize my WordPress sites?
Many benefits await those, who marry WordPress and Docker. We could bury you under an avalanche of examples and specifics, but we'd rather stick to specific use cases instead of writing lengthy essays. Let's cut to the chase then!
What is Docker?
To put it simply, Docker is a type of virtualization which is faster and lighter than regular VMs. With Docker containers, the app and its entire environment, including libraries and dependencies, is bundled into one, system-agnostic and self-contained unit – a container. Compared to regular VMs, which can take even a few minutes to launch, a Dockerized app can be up and running in a matter of seconds.
What's more, due to their light weight, you can easily run dozens of Docker containers on a single machine that could otherwise launch no more than 5 VMs.
What does it mean in practice?
- To run WordPress locally, you don't need to set up Apache, MySQL server, XAMPP, Wamp, and MAMP. All you need is Docker – installation itself takes seconds and no additional configuration is required.
- No matter if it's a developer's machine, a staging server, or a LIVE server and what OS it is, Docker works the same everywhere. This means you won't have to look for bugs that, say, appear on the LIVE server and cannot be reproduced locally.
Install Docker
If you already have Docker installed, you can jump to the next part of the article. If not, follow instructions for your OS:
1. Mac
- Download the latest stable version of Docker for Mac.
Open the installer and drag Moby the whale to your Applications folder:
Image loading...Docker installation
Run Docker.app and authorize it with your system password. Privileged access is required to install networking components and links to the Docker apps:
Image loading...Docker installation
Click the whale icon in the status bar, go to Preferences and set the amount of RAM that you can assign to Docker:
Image loading...Docker installation
Check if everything works properly by running
docker info
in your terminal.
2. Windows
- Download the latest stable version of Docker for Windows.
- Double-click Docker for Windows Installer.exe to run the installer.
Follow the install wizard to accept the license, authorize the installer, and proceed with the installation process:
Image loading...Docker on Windows
- Docker will not start automatically – look it up in your applications and launch it
Set RAM by entering Docker's Settings and clicking its tray icon:
Image loading...Docker on Windows
3. Linux
Follow install instructions for your distribution of Linux:
How to run WordPress in Docker
The application will be run in a Docker container, which is an equivalent of a VM. The Docker container is launched on the basis of a Docker image. Docker images are built based on the specification described in a Dockerfile.
Create a WordPress Dockerfile
The first thing we need to do is to define how the image will look like in a Dockerfile. The Dockerfile is a text file that is added to the directory with the sources of your application.
In general, our Dockerfile will consist of two commands:
FROM
– to build the application, use the official WP image"FROM wordpress:php7.1-apache"
COPY
– copy my code to the defined directory in my image
Dockerfile
.
Below you'll see how the Dockerfile will look depending on the contents of your repo.
WP Engine
If your repository contains the entire engine together with the contents, plugins, and themes, make sure to add a Dockerfile with the following content along wp-admin
, wp-content
, and wp-includes
directories:
FROM wordpress:php7.1-apache
COPY . /var/www/html
WP Theme
If your repository contains only the sources of the theme that you deploy, add a Dockerfile with the following content to the directory with your theme's sources:
FROM wordpress:php7.1-apache
COPY . /var/www/html/wp-content/themes/mytheme/
WP Plug-in
If your repository contains only the sources of the plugins that you deploy, add a Dockerfile with the following content to the directory with your plugin's sources:
FROM wordpress:php7.1-apache
COPY . /var/www/html/wp-content/plugins/myplugin/
WP Plug-in and theme
If your repository contains plugins and themes, but lacks the WP Engine, you must use the Dockerfile to upload them to the proper location.
Let's assume that your repository contains 4 directories: mytheme1
, mytheme2
, myplugin1
and myplugin2
. In such case, you must add these lines to the Dockerfile to copy all the directories with their content into appropriate locations:
FROM wordpress:php7.1-apache
COPY mytheme1 /var/www/html/wp-content/themes/mytheme1/
COPY mytheme2 /var/www/html/wp-content/themes/mytheme2/
COPY myplugin1 /var/www/html/wp-content/plugins/myplugin1/
COPY myplugin2 /var/www/html/wp-content/plugins/myplugin2/*
Build WP Docker Image
Once you have your Dockerfile defined, you can build the image in the terminal:
cd my_repo
docker build -t 'wp-image' .
$$
The command will build an image named wp-image
in the context of your repository (the .
at the end). The first execution will take a while since the official WordPress image needs to be downloaded to your disk. Next builds will be much faster.
Run WP Image
Finally, the time has come to run the image:
docker run --name mysql-cont -e MYSQL_ROOT_PASSWORD=qwerty -d mysql:5.7
docker run --name wp-cont --link mysql-cont:mysql -p 8000:80 -d wp-image
$$
The first command starts a MySQL Docker container in version 5.7
named mysql-cont
. It is running in the background (-d
flag). We are also passing MYSQL_ROOT_PASSWORD
environment variable which defines the root password for MySQL server.
The second command starts a container based on the image that we build (wp-image
). The container is named wp-cont
, runs in the background (-d
) and has port 8000
mapped from the host to port 80
. This means that if you go to 127.0.0.1:8000
in your browser, you will be redirected to the Apache server in the container, listening on port 80
.
Another option we use is --link
. It means that the MySQL container is visible inside the wp-cont
container through a MySQL alias.
bash
docker run --name wp-cont -e WORDPRESS_DB_HOST=host:port -e WORDPRESS_DB_USER=user -e WORDPRESS_DB_PASSWORD=pass -p 8000:80 -d wp-image
Once the container starts, you can open its URL in the browser and start using it: http://127.0.0.1:8000
Run WordPress with docker-compose
At this moment we need to do three things:
- Build the image
- Run MySQL
- Run the image
You can simplify these steps using docker-compose, a tool for defining and running multi-container Docker applications. Once you define the application's environment in the Dockerfile, you can use docker-compose to define the services that make up your app.
Start with creating a new docker-compose.yml
file to live alongside the Dockerfile that you defined in the previous steps. Populate it with this config:
version: '3'
services:
wp:
build: .
ports:
- "8000:80"
environment:
WORDPRESS_DB_PASSWORD: qwerty
mysql:
image: "mysql:5.7"
environment:
MYSQL_ROOT_PASSWORD: qwerty
In this file we define two services that are automatically linked with each other:
wp
– here we build a Docker image based on the Dockerfile created before and map port 8000 on the host to port 80 inside the container. Then we pass the MySQL password via an environment variable (the WP base image is designed to handle such variables)mysql
– here we run the MySQL image from the Docker Hub in version 5.7 with theqwerty
password passed via an environment variable (the MySQL image is designed to handle such variables, too)
As a result, the WordPress base image automatically takes care of the linked MySQL service and configures WP database access.
Right now, we can run Docker Compose and start both containers with one command:
docker-compose up -d
$
Once the containers have started, you can go to http://127.0.0.1:8000 in the web browser and start using your application.
To stop the application, run:
docker-compose down
$
If you need to rebuild the WordPress image (eg. because you changed something in the sources), run:
docker-compose up -d --build
$
Data in MySQL
Data in the containers is not persistent. This means that you lose all data if you stop the container and run it again, there will no longer be any data inside. It's not very convenient as you have to initiate your WordPress every time.
This can be avoided by adding a Docker volume to docker-compose
:
version: '3'
services:
wp:
build: .
ports:
- "8000:80"
environment:
WORDPRESS_DB_PASSWORD: qwerty
mysql:
image: "mysql:5.7"
environment:
MYSQL_ROOT_PASSWORD: qwerty
volumes:
- my-datavolume:/var/lib/mysql
volumes:
my-datavolume:
Docker will create a volume for you in the /var/lib/docker/volumes
folder. This volume will persist as long as you don't run docker-compose down -v
.
How to ship a WordPress image to the Production server?
Once you have your image built locally, a tempting question arises: can I do all of these on the Production server? The answer is simple: no. Actually, the only thing that you could do on the Production server is to run pre-built images.
Push the image to a private registry
You may have missed that, but we already used the image registries twice in this article. For the first time, we typed the following in the Dockerfile:
"FROM wordpress:php7.1-apache".
The second one was when we ran MySQL:
docker run --name mysql-cont -e MYSQL_ROOT_PASSWORD=qwerty -d mysql:5.7
$
In both cases, we used images from the Docker Hub. You can also push your own Docker image there:
- Sign up to Docker Hub if you haven't done it already.
- Build your image using your account's username in its name:
docker build -t my-docker-hub-username/my-image .
$
- Push the image to Docker Hub:
docker push my-docker-hub-username/my-image
$
Pull the image to Staging/Production
Once you upload your image to the registry, all you need to do is to pull it onto your server. You don't have to upload your application sources: what you need to do is create the same docker-compose
that you used locally, but instead of using build: .
you'll refer to the already pushed image and change the application port from 8000 to 80:
version: '3'
services:
wp:
image: "my-docker-hub-username/my-image"
ports:
- "80:80"
environment:
WORDPRESS_DB_PASSWORD: qwerty
mysql:
image: "mysql:5.7"
environment:
MYSQL_ROOT_PASSWORD: qwerty
volumes:
- "my-datavolume:/var/lib/mysql"
volumes:
my-datavolume:
Once docker-compose
is configured, you can run it just like you did locally:
docker-compose up -d
$
Automation
Now that you've successfully dockerized WordPress, it's time to shave off some precious seconds by introducing automation. This way you'll be able to build the Docker image and running it on a production server with a single click or push.
Set up a project with Buddy
First you need to hook up your repository with Buddy:
- Sign up to Buddy with your GitHub/Bitbucket account or email.
- Create a new project, select your Git provider, and choose your WP project:
Image loading...Attaching a repository
Set up a delivery pipeline
Now you need to configure a delivery pipeline so that it automatically executes on every push to branch:
- Click Add a new pipeline
- Set the trigger mode to On every push
- Select the branch that will trigger the execution
- Click Add a new pipeline when ready: Image loading...Adding a new pipeline
Define the delivery actions
Now it's time to reproduce your workflow into actions:
${execution.to_revision.short_revision}
.
(Optional) Add a build action to process your files before the deployment. In this example we'll use Gulp to test the application: Image loading...Build Action Example Pipeline
- Add the Docker build action and configure the details:
- select the Dockerfile from the repository
- select the registry to which Buddy will push your image (eg. Docker Hub) Image loading...Configuring the details
- Add the SFTP transfer action to upload
docker-compose
to the server:- provide authentication details to your server
- select upload from the repository
- set the source path to the directory with
docker-compose
: Image loading...Adding the SFTP action
docker-compose
file and enter the credentials to your Docker registry or Buddy will not be able to connect and pull the image!
Image loading...Editing docker-compose
Add the SSH action to pull and run your Docker image on the server:
- provide authentication details to your server
- in the SSH commands field enter:
docker-compose down docker-compose up -d
$$Image loading...Adding the SSH action
Run the pipeline
Congratulations! You have successfully configured the delivery pipeline. Make a push to the repository and watch Buddy automatically dockerize your project and run it on the server:
Awesome, isn't it?
Kubernetes and WordPress
If you think that building Docker from WordPress is great, just wait until you see what you can upon adding Kubernetes to the mix.
In this section we'll focus on creating K8s volumes and configuring a WP instance with a MySQL database in the most simple way – a perfect introduction to Kubernetes and Docker for newbies.
Configure a PersistentVolume in the Kubernetes cluter
In this example, we'll use a hostPath
volume since we only have one node in the cluster. This type of volume mounts the path from the node's filesystem into K8s.
hostPath
volume is not recommended for multi-node production clusters. If you're working on multiple nodes, follow the instructions here.
Create volumes.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-1
labels:
type: local
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /k8/volume/pv-1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-2
labels:
type: local
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /k8/volume/pv-2
This config will create two R/W 10GB volumes in the node paths:
/k8/volume/pv-1
/k8/volume/pv-2
Create your Kubernetes volumes
To create the volumes, run
kubectl apply -f volumes.yml
$
You can check if everything's working correctly by running
kubectl get pv
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv-1 10Gi RWO Retain Available 13s
local-pv-2 10Gi RWO Retain Available 13s
$$$$$
Configure a MySQL database
Start with creating a secret with a password for the MySQL root user:
kubectl create secret generic mysql-pass --from-literal=password=ROOT_PASSWORD
$
You can check if the password was properly configured by running
kubectl get secrets
NAME TYPE DATA AGE
mysql-pass Opaque 1 17h
$$$$
Create mysql.yml
The file below will create a single MySQL instance with a proper volume and port mapping. It also uses the secret that we created earlier:
apiVersion: v1
kind: Service
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
ports:
- port: 3306
selector:
app: wordpress
tier: mysql
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: mysql
spec:
containers:
- image: mysql:5.6
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
TableMain of mysql.yml
The file consists of 3 separate configs:
- Service – maps MySQL's port 3306 and makes it available for all containers with labels
app:wordpress
&tier:mysql
- Persistent volume claim – declares claim on the volume that will be used in the MySQL container configuration
- Deployment – declares the creation strategy and specs of our MySQL container:
TableMain of the MySQL container:
- it's an image from the Docker Hub:
mysql:5.6
- it has
app:wordpress
&tier:frontend
labels (used inService
) - it contains an environment variable called
MYSQL_ROOT_PASSWORD
which holds the value from our secret password - it has an open port 3306
- it has a volume claim mounted in
/var/lib/mysql
Create your MySQL instance on Kubernetes
To create the database, run
kubectl apply -f mysql.yml
$
You can check the progress of deployment by running
kubectl get pods
$
Once you see status:Running
, the MySQL service is ready for action.
Deploy WordPress to Kubernetes
Begin with downloading WordPress sources from https://wordpress.org/download/.
Configure the Docker file
Now we need to dockerize the WordPress instance. The Docker file only requires WP sources:
FROM wordpress:php7.1-apache
COPY . /usr/src/wordpress/
Build & push the Docker image
The next step is building the Docker image and pushing it to your Docker registry:
docker login
docker build -t buddy/wordpress .
docker push buddy/wordpress
$$$
Create wordpress.yml
To deploy WordPress on a Kubernetes node you need to create a proper config file:
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
nodePort: 30000
selector:
app: wordpress
tier: frontend
type: NodePort
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wp-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: frontend
spec:
containers:
- image: buddy/wordpress:latest
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: wordpress-mysql
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wp-pv-claim
TableMain of wordpress.yml
The file consists of 3 separate configs:
- Service – maps port 80 of the container to the node's external IP:30000 for all containers with labels
app:wordpress
&tier:frontend
- Persistent volume claim – declares claim on the volume that will be used in the WP container configuration
- Deployment – declares the creation
strategy
andspec
of our WordPress container:
TableMain of the WP container:
- it's an image from the Docker Hub:
buddy/wordpress:latest
- it has
app:wordpress
&tier:frontend
labels (used inService
) - it contains environment variables
WORDPRESS_DB_HOST
, which is the internal host name of the MySQL instance, andWORDPRESS_DB_PASSWORD
, which holds the value from our secret password - it has an open port 80
- it has a volume claim mounted in
/var/www/html
from which the WP sources are served
Create your WP instance on Kubernetes
To deploy your WP instance, run
kubectl apply -f wordpress.yml
$
You can check the progress of deployment by running
kubectl get pods
$
Once you see status:Running
, the WordPress service is ready for action.
Advantages & Extenstions
Running WordPress and other web projects on Kubernetes gives you a series of benefits:
- easy configuration in just a few files
- you can recreate the whole configuration on any host with a couple of commands:
kubectl apply -f volumes.yml
kubectl create secret generic mysql-pass --from-literal=password=ROOT_PASSWORD
kubectl apply -f mysql.yml
kubectl apply -f wordpress.yml
$$$$
- you can extend the configuration by using volumes on AWS or other production-ready volumes
- you can change the external port mapping for proper load balancing
- you can change the deployment strategy from
Recreate
toRollingUpdate
to increase container counts and ensure no downtime during the deployment
Automate Kubernetes delivery with Buddy
With Buddy you can streamline the whole K8s deployment down to a single push to branch: from building the Docker image to applying config changes, to updating the image on your Kubernetes cluster. Check out the guide to see how it looks in action!
Jarek Dylewski
Customer Support
A journalist and an SEO specialist trying to find himself in the unforgiving world of coders. Gamer, a non-fiction literature fan and obsessive carnivore. Jarek uses his talents to convert the programming lingo into a cohesive and approachable narration.