Deploy a WordPress blog as a Kubernetes cluster using a WordPress Helm chart that can be versioned.


K3d/K3s, Helm, Kubectl

Clone WordPress-Helm Repo

Clone the hardened Ansible-based wordpress-helm repository from
cd /Users/derungsm/_git && \
git clone && \
cd wordpress-helm

A lot of work went into this helm chart to make deployments easy. Open the repo in code editor and view the README. There are a lot of options for deployment. Not all options will apply. Also see notes on the Bitnami MariaDB helm chart.

Download and store the repo's WordPress and WordPress CLI Docker images from the wordpress-helm container registry.
docker pull && \
docker pull

Pull a specific tag. The "latest" is not always found in manifest.

Customize Deploy

Copy values.local.yaml.example => values-local.yaml and make edits.
set wordpress.config.adm.usid => blog-admin
set wordpress.config.adm.pssw => blog-admin_password
add => blog-admin@domain.extension

set => ????????
set => blog-title
add => 5.8.1

set database.auth.database => blog
set database.auth.username => wordpress-admin
set database.auth.password => wordpress-admin-password
set database.auth.rootPassword => wordpress-admin-password
set database.auth.replicationPassword => wordpress-admin-password

set themes_install => bloggist, twentytwentyone
set theme_active => bloggist
set theme_fallback => twentytwentyone

Copy => and make the script executable
chmod +x

Create Cluster

Create a three-node cluster called "k3s-wordpress" and expose Kubernetes API on a port that does not conflict with any other clusters already running (though the API port is not strictly necessary)
k3d cluster create k3s-wordpress --api-port 6551 --agents 2

If you get the error "Unable to connect to the server: x509: certificate signed by unknown authority" (typically in a secondary tab on Mac when trying to run kubectl commands like kubectl get pods), then set the API context with export KUBECONFIG=$(k3d kubeconfig write k3s-wordpress). It could also mean you need to clear the K3d .kube/cache folders.

Deploy WordPress

Set the API context
export KUBECONFIG=$(k3d kubeconfig write k3s-wordpress)

expected output /Users/derungsm/.k3d/kubeconfig-k3s-wordpress.yaml

Add the Bitnami charts repository to Helm
helm repo add bitnami

Get all helm dependencies from the Git working directory
helm dependency update

This avoids the error "found in Chart.yaml, but missing in charts/ directory: mariadb, redis)"

expected output Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "rancher-stable" chart repository ...Successfully got an update from the "rancher-latest" chart repository ...Successfully got an update from the "bitnami" chart repository Update Complete. ⎈Happy Helming!⎈ Saving 2 charts Downloading mariadb from repo Downloading redis from repo Deleting outdated charts

Run the install script

expected output Release "wordpress-production" does not exist. Installing it now. NAME: wordpress-production LAST DEPLOYED: Tue Oct 5 12:33:24 2021 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None

Monitor progress with Watch utility (brew install watch)
watch kubectl get all

It can take about 5 minutes for pod/wordpress-production0 to enter a running state and 5.5 min for statefulset/apps/wordpress-production to enter ready state.

Check status
kubectl -n default rollout status statefulset wordpress-production kubectl get all

View Setup in Lens

Open Lens and view performance. May need to save the context first:
k3d kubeconfig write k3s-wordpress -o ``pwd``/kubeconfig-k3d-dev

Import Existing WordPress Database

Get a dump of the current WordPress database from existing server to Mac
scp -r user@server:/home/backup/mysqldump/blog.sql .

Upload dump file to K3d PV
kubectl exec -i wordpress-production-database-primary-0 \
-- mysql \
-uwordpress \
-ppassword \
--database=blog < ./data/blog.sql

Moving WordPress to a different URL/environment means changing the site and home urls in the database. For example, to work with the database on locally (localhost):

Connect to the database running in K3s
kubectl exec -it \
wordpress-production-database-primary-0 \
--namespace=default \
-- /bin/bash

-it means the same as --stdin --tty or -i -t

Access/review the database/settings
mysql -u root -p
use wp_database_name;
show tables;

SELECT option_name, option_value
FROM blog.wp_option
WHERE option_name
LIKE 'siteurl'
OR option_name
LIKE 'home';

Update the URLs
UPDATE database_name.wp_options
SET option_value = replace(option_value, 'https://somedomain/blog', 'http://localhost');

In my case, I also needed to change the URLs for an SSO Plugin For Azure AD to allow a "http://localhost/wp-admin" login with "http://localhost/?sso_for_azure_ad=callback" callback:
FROM wp_database_name.wp_options
WHERE option_name
LIKE 'sso%';

UPDATE wp_database_name.wp_options
SET option_value = '12345abc-b723-2229-aa00-03b0ece6eac5'
WHERE option_name
LIKE 'sso_for_azure_ad_client_id';

UPDATE wp_database_name.wp_options
SET option_value = 'aHgO.0_rU5NFGCtufooe0-AY_~Obuzz3nc'
WHERE option_name
LIKE 'sso_for_azure_ad_client_secret';

The ids and secrets are fake, but you get the idea.


Connect to the primary or secondary database PV
kubectl exec -it \
wordpress-production-database-primary-0 \
--namespace=default \
-- /bin/bash

kubectl exec -it \
wordpress-production-database-secondary-0 \
-- /bin/bash

File structure of database PVs

Import Existing WordPress Admin Content

Copy files from server to Mac
scp -r user@server:/var/www/html/blog/wp-content .

Copy files from Mac to K3d WordPress pod
kubectl cp ./data/wp-content/plugins wordpress-production-0:/var/www/wp-content-mount && \
kubectl cp ./data/wp-content/themes wordpress-production-0:/var/www/wp-content-mount && \
kubectl cp ./data/wp-content/uploads wordpress-production-0:/var/www/wp-content-mount

Change ownership of the files to UID 33 (the www-data user in the WordPress container):
kubectl exec -it wordpress-production-0 -- chown -R 33:33 /var/www/wp-content-mount

This may say chown: changing ownership of '/var/www/wp-uploads-mount/.htaccess': Read-only file system, which is the mounted .htaccess file. Ownership of all the other files will have changed.

Check uploads
kubectl exec -it \
wordpress-production-0 \
--namespace=default \
-- /bin/bash

ls -la /var/www/html
ls -la /var/www/wp-content-mount
ls -la /var/www/wp-uploads-mount

Remove the default plugins (Classic Editor) from inside the pod
rm -rf /var/www/wp-content-mount/plugins/classic-editor

Remove the default plugins (Classic Editor) from outside the pod
kubectl delete wordpress-production-0:/var/www/wp-content-mount/plugins/classic-editor

Browse the Site

Forward the k3s-wordpress port to Mac - make sure that this URL doesn't go through the proxy
export KUBECONFIG=$(k3d kubeconfig write k3s-wordpress) && \
sudo -E kubectl port-forward service/wordpress-production 80:8080

Port 80 is generally reserved, so use sudo with the -E flag to pass the KUBECONFIG environment to kubectl


Delete pods, services and statefulsets

Modify release name in if not using git repo

Stop the cluster
k3d cluster stop k3s-wordpress

(or restart the Docker Engine)

If the init container fails rebuilding WordPress, see workaround

Stop and remove the cluster
k3d cluster stop k3s-wordpress && \ k3d cluster delete k3s-wordpress

To Do

  • Get a version of the wordpress-helm images from Azure Container Registry using authentication.
  • Get an outside dump file from inside the K3s pod directly.
  • Get outside wp-content from inside the K3s pod directly.
  • Automate WordPress content and database backups to external host.


Install WordPress in Kubernetes

Simple Nginx web server

Wordpress Helm

Persistent Volumes and Claims