WordPress in Docker: Dockerization, automation, and Kubernetes.

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 HombresMeet the Hombres

What you're going to learn

In this guide, we're going to show you three things:

  1. How to dockerize your WordPress website
  2. How to automate building images with Buddy CI/CD
  3. 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.
Hint
If you already know how to dockerize apps, skip ahead to the later parts of this guide which cover automation and deploying WordPress to Kubernetes.

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

  1. Download the latest stable version of Docker for Mac.
  2. Open the installer and drag Moby the whale to your Applications folder:

    Image loading...Docker installationDocker installation

  3. 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 installationDocker installation

  4. 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 installationDocker installation

  5. Check if everything works properly by running docker info in your terminal.

2. Windows

  1. Download the latest stable version of Docker for Windows.
  2. Double-click Docker for Windows Installer.exe to run the installer.
  3. Follow the install wizard to accept the license, authorize the installer, and proceed with the installation process:

    Image loading...Docker on WindowsDocker on Windows

  4. Docker will not start automatically – look it up in your applications and launch it
  5. Set RAM by entering Docker's Settings and clicking its tray icon:

    Image loading...Docker on WindowsDocker on Windows

3. Linux

Tip
Before you start, make sure to install docker-compose on your system

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:

  1. FROM – to build the application, use the official WP image "FROM wordpress:php7.1-apache"
  2. COPY – copy my code to the defined directory in my image
Tip
The Dockerfile itself should be named 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.

Tip
You can also run the application and make it connect to your SQL server. You just need to run one command in which you provide your server details: 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:

  1. Build the image
  2. Run MySQL
  3. 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 the qwerty 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.

Hint
It can take some time until the MySQL server inside the container actually starts

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:

  1. Sign up to Docker Hub if you haven't done it already.
  2. Build your image using your account's username in its name:
docker build -t my-docker-hub-username/my-image .$
  1. 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:

  1. Sign up to Buddy with your GitHub/Bitbucket account or email.
  2. Create a new project, select your Git provider, and choose your WP project:
Hint
For the purpose of this guide, we'll use a Git repository with an existing WP theme: https://github.com/buddy-works/buddy-wp-theme.git

Image loading...Attaching a repositoryAttaching 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:

  1. Click Add a new pipeline
  2. Set the trigger mode to On every push
  3. Select the branch that will trigger the execution
  4. Click Add a new pipeline when ready: Image loading...Adding a new pipelineAdding a new pipeline

Define the delivery actions

Now it's time to reproduce your workflow into actions:

Tip
You can use environment variables to tag your images in the registry, for example ${execution.to_revision.short_revision}.
  1. (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 PipelineBuild Action Example Pipeline

  2. 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 detailsConfiguring the details
  3. 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 actionAdding the SFTP action
Warning
If you're using our example repository make sure to edit the 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-composeEditing docker-compose
  1. 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 actionAdding 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.

Tip
If you already have a running K8s application, check out this guide to learn how to automate your K8s deployments.

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.

Warning
The 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$$$$
Tip
Secrets in K8s are hidden and cannot be displayed. This means there's no risk of exposing them in config files in public repositories.

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:
Hint

TableMain of the MySQL container:

  • it's an image from the Docker Hub: mysql:5.6
  • it has app:wordpress & tier:frontend labels (used in Service)
  • 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 and spec of our WordPress container:
Hint

TableMain of the WP container:

  • it's an image from the Docker Hub: buddy/wordpress:latest
  • it has app:wordpress & tier:frontend labels (used in Service)
  • it contains environment variables WORDPRESS_DB_HOST, which is the internal host name of the MySQL instance, and WORDPRESS_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.

Tip
Congratulations! You have successfully deployed your WordPress project to Kubernetes. You can visit the site by going to node IP:30000.

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 to RollingUpdate 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!

Hint
You can also automate the deployment of any other popular language or framework. Just let us know what you do on the live chat or drop a line support@buddy.works and we'll create a delivery pipeline for your project free of charge!
Jarek Dylewski

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.