{"id":500,"date":"2025-07-16T15:33:00","date_gmt":"2025-07-16T15:33:00","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=500"},"modified":"2025-07-16T15:33:00","modified_gmt":"2025-07-16T15:33:00","slug":"deploy-wordpress-on-kubernetes","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=500","title":{"rendered":"How to Deploy WordPress on Kubernetes with Nginx and MySQL"},"content":{"rendered":"<p>In this blog, you are going to learn about the steps to deploy WordPress on Kubernetes cluster with Nginx and MySQL database.<\/p><h2 id=\"prerequisites\">Prerequisites<\/h2><p>Here are the prerequisites to deploy <a href=\"https:\/\/devopscube.com\/wordpress-installation-guide\/\">WordPress<\/a> on Kubernetes with Nginx and <a href=\"https:\/\/devopscube.com\/setup-mysql-master-slave-replication\/\">MySQL<\/a><\/p><ol><li><a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/\">Kubernetes Cluster<\/a><\/li><li>kubectl<\/li><li>Cluster with admin permission to deploy applications.<\/li><li><a href=\"https:\/\/devopscube.com\/how-to-install-and-configure-docker\/\">Install Docker<\/a> and should have access to a Docker Hub repo (If you are going to create a custom Docker image)<\/li><\/ol><div class=\"kg-card kg-callout-card kg-callout-card-blue\"><div class=\"kg-callout-emoji\">\ud83d\udca1<\/div><div class=\"kg-callout-text\">If you are looking for managed WordPress hosting on Kubernetes platform, checkout <a href=\"https:\/\/devopscube.com\/cloudways-autonomous-review\/\" rel=\"noreferrer\">Cloudways Autonomous<\/a> service.<\/div><\/div><h2 id=\"setup-architecture\">Setup Architecture<\/h2><p>The workflow of this setup is given in the below diagram.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/wordpress-mysql.gif\" class=\"kg-image\" alt=\"architecture diagram of wordpress application\" loading=\"lazy\" width=\"761\" height=\"961\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/07\/wordpress-mysql.gif 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/wordpress-mysql.gif 761w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><p>You will practically use the following key <a href=\"https:\/\/devopscube.com\/kubernetes-objects-resources\/\">Kubernetes objects.<\/a><\/p>\n<!--kg-card-begin: html-->\n<ol class=\"wp-block-list is-style-cnvs-list-styled\">\n<li><strong>Namespace<\/strong> &#8211; This isolates the resources used in this deployment.<\/li>\n\n\n<li><strong>Secret <\/strong>&#8211; Used to store MySQL database credentials securely, which will be fetched when deploying MySQL and WordPress.<\/li>\n\n\n<li><!--kg-card-begin: html--><span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\"><strong>ConfigMap<\/strong>&nbsp;&#8211; We will create two configmaps, one for Nginx configuration <\/span><!--kg-card-end: html-->and the other for MySQL init script.<\/li>\n\n\n<li><strong>PersistantVolumeClaim (PVC)<\/strong> &#8211; This creates and attaches PVs to WordPress Applications and MySQL databases to store data.<\/li>\n\n\n<li><strong>StatefulSet<\/strong> &#8211; MySQL database is deployed as a statefulset.<\/li>\n\n\n<li><strong>Deployment<\/strong> &#8211; WordPress application is deployed as a deployment.<\/li>\n\n\n<li><strong>Services<\/strong> &#8211; Exposes WordPress application to the outside world and exposes MySQL database to WordPress within the cluster.<\/li>\n\n\n<li><strong>NetworkPolicy<\/strong> &#8211; Assign a network policy for MySQL to restrict its incoming traffic.<\/li>\n<\/ol>\n<!--kg-card-end: html-->\n<h2 id=\"manifest-repository\">Manifest Repository<\/h2><p>Clone the <a href=\"https:\/\/github.com\/techiescamp\/kubernetes-projects.git?ref=devopscube.com\" rel=\"noreferrer noopener\">Kubernetes Projects repository<\/a> given below which contains every file we are going to use in this deployment.<\/p><pre><code>git clone https:\/\/github.com\/techiescamp\/kubernetes-projects.git<\/code><\/pre><p>CD into <code>04-WordPress-deployment<\/code> and follow the below steps<\/p><p>The directory structure is given below<\/p><pre><code>.\n\u251c\u2500\u2500 Docker\n\u2502   \u251c\u2500\u2500 Dockerfile\n\u2502   \u251c\u2500\u2500 php.ini\n\u2502   \u251c\u2500\u2500 startup.sh\n\u2502   \u251c\u2500\u2500 wordpress.conf\n\u2502   \u2514\u2500\u2500 wp-config.php\n\u251c\u2500\u2500 mysql-cm.yaml\n\u251c\u2500\u2500 mysql.yaml\n\u251c\u2500\u2500 networkpolicy.yaml\n\u251c\u2500\u2500 nginx-cm.yaml\n\u251c\u2500\u2500 secret.yaml\n\u251c\u2500\u2500 vpa.yaml\n\u2514\u2500\u2500 wordpress.yaml<\/code><\/pre><h2 id=\"steps-to-deploy-wordpress-on-kubernetes-with-nginx-and-mysql\">Steps to Deploy WordPress on Kubernetes with Nginx and MySQL<\/h2><blockquote><strong>Note: <\/strong>You can skip the steps 1 &amp; 2 if you only want to try the Kubernetes deployment. Just use the container image provided in the guide.<\/blockquote><p>Let&#8217;s start the deployment process, follow the below steps one by one.<\/p><h3 id=\"step-1-build-a-custom-docker-image-of-wordpress\">Step 1: Build a Custom Docker Image of WordPress<\/h3><p>In this custom image, we are going to install Nginx instead of Apache webserver and also add the configuration file of Nginx and WordPress.<\/p><p>CD into the <strong>Docker<\/strong> folder, you can see the following file<\/p><pre><code>.\n\u251c\u2500\u2500 Dockerfile\n\u251c\u2500\u2500 php.ini\n\u251c\u2500\u2500 startup.sh\n\u251c\u2500\u2500 wordpress.conf\n\u2514\u2500\u2500 wp-config.php<\/code><\/pre><p>To <a href=\"https:\/\/devopscube.com\/build-docker-image\/\">build the Docker<\/a> image of WordPress with Nginx, use the Dockerfile which is inside the Docker directory<\/p><pre><code># Use Ubuntu as the base image\nFROM ubuntu:24.04\n\n# Set the Timezone to UTC\nENV TZ=UTC\n\n# Install Nginx, and other necessary Packages\nRUN apt-get update &amp;&amp; apt-get install -y \\\n    software-properties-common \\\n    nginx \\\n    curl \\\n    wget \\\n    sudo \\\n    unzip \\\n    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\n\n# Add PHP repository\nRUN sudo add-apt-repository ppa:ondrej\/php\n\n# Install PHP\nRUN apt-get update &amp;&amp; apt-get install -y \\\n    php8.2 php8.2-fpm php8.2-mysql php8.2-cli php8.2-gd \\\n    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\nADD php.ini \/etc\/php\/8.2\/nginx\/\n\n# Install WordPress\nRUN wget https:\/\/wordpress.org\/latest.zip \\\n    &amp;&amp; unzip latest.zip -d \/var\/www\/html\/ \\\n    &amp;&amp; rm latest.zip\n\n# Add Nginx config file\nRUN rm \/etc\/nginx\/sites-enabled\/default\nADD wordpress.conf \/etc\/nginx\/sites-available\/\nRUN ln -s \/etc\/nginx\/sites-available\/wordpress.conf \/etc\/nginx\/sites-enabled\/\n\n# Add WordPress config file\nRUN rm \/var\/www\/html\/wordpress\/wp-config-sample.php\nADD wp-config.php \/var\/www\/html\/wordpress\/wp-config.php\n\n# Change the user and Permission of WordPress root directory\nRUN chown -R www-data:www-data \/var\/www\/html\/wordpress \\\n    &amp;&amp; chmod -R 755 \/var\/www\/html\/wordpress\n\n# Exposing container to port 80\nEXPOSE 80\n\n# Copy the startup script and give excuitable permission\nCOPY startup.sh \/usr\/local\/bin\/startup.sh\nRUN chmod +x \/usr\/local\/bin\/startup.sh\n\n# Command to run the startup script\nCMD [\"\/usr\/local\/bin\/startup.sh\"]<\/code><\/pre><p>This <a href=\"https:\/\/devopscube.com\/create-dockerfile-using-docker-init\/\">Dockerfile<\/a> uses ubuntu:24.04 as the base image and sets the timezone to UTC. Then it installs Nginx, PHP, WordPress, and other required packages.<\/p><p>Once the packages are installed the configuration files of Nginx and WordPress are added and the permission of the WordPress root directory is changed.<\/p><p>The container is exposed to port 80 and the startup script is added. The command runs the startup script when the container starts.<\/p><pre><code>#!\/bin\/bash\n\nWP_CONFIG=\"\/var\/www\/html\/wordpress\/wp-config.php\"\n\nsed -i \"s\/\\${DB_NAME}\/${DB_NAME}\/g\" $WP_CONFIG\nsed -i \"s\/\\${DB_USER}\/${DB_USER}\/g\" $WP_CONFIG\nsed -i \"s\/\\${DB_PASSWORD}\/${DB_PASSWORD}\/g\" $WP_CONFIG\nsed -i \"s\/\\${DB_HOST}\/${DB_HOST}\/g\" $WP_CONFIG\n\nservice php8.2-fpm start\nnginx -g 'daemon off;'\n<\/code><\/pre><p>This is the startup script that uses the SED command to replace the values of the MySQL database name, username, password, and host URL to the WordPress configuration file <strong>wp-config.php<\/strong>.<\/p><p>Add runs the command that starts the PHP and Nginx webserver.<\/p><p>Run the below command from the same directory where the Dockerfile is present to build the Docker image.<\/p><pre><code>docker build -t wordpress-nginx:7.0.0 .<\/code><\/pre><h3 id=\"step-2-push-the-image-to-dockerhub\">Step 2: Push the image to DockerHub<\/h3><p>First, tag the image using the following command<\/p><pre><code>docker tag wordpress-nginx:7.0.0 techiescamp\/wordpress-nginx:7.0.0<\/code><\/pre><p>Now,&nbsp;use the command to push the image to DockerHub. Before pushing, make sure you have logged in to the&nbsp;<strong>Docker Hub <\/strong>from the CLI.<\/p><pre><code>docker push techiescamp\/wordpress-nginx:7.0.0<\/code><\/pre><p>Check if your image is pushed into DockerHub as shown below.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-165.png\" class=\"kg-image\" alt=\"docker image pushed to docker hub registry\" loading=\"lazy\" width=\"661\" height=\"415\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/07\/image-165.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-165.png 661w\"><\/figure><h3 id=\"step-3-create-a-namespace\">Step 3: Create a Namespace<\/h3><p>CD into <code>04-WordPress-deployment<\/code> and run the below commands, you will find all the manifest files inside the <strong>04-WordPress-deployment<\/strong> directory.<\/p><p>Let&#8217;s start by creating a WordPress namespace to isolate the resources we are going to deploy in the cluster.<\/p><p>Run the following command to create the namespace <strong>wordpress<\/strong>.<\/p><pre><code>kubectl create ns wordpress<\/code><\/pre><p>Since we are going to create every object inside the WordPress namespace, let&#8217;s set the namespace WordPress as the default namespace so that we don&#8217;t have to specify the namespace every time we use the kubectl command.<\/p><p>Run the following command to set the <strong>WordPress<\/strong> namespace as the default namespace.<\/p><pre><code>kubectl config set-context --current --namespace=wordpress<\/code><\/pre><h3 id=\"step-4-create-secret\">Step 4: Create Secret<\/h3><p>The next step is to create a secret with a database admin password, username, password, and database name.<\/p><p>CD into the <strong>04-WordPress-deployment<\/strong> directory and run the <strong>secret.yaml <\/strong>to create a secret on the WordPress namespace.<\/p><pre><code>apiVersion: v1\nkind: Secret\nmetadata:\n  name: mysql-cred\n  namespace: wordpress\ntype: Opaque\ndata:\n  admin-password: YWRtaW5AMTIz\n  username: Y3J1bmNob3Bz\n  password: Y3J1bmNob3BzQDEyMw==\n  db-name: d29yZHByZXNzX2Ri<\/code><\/pre><p>We will be passing this secret as environment values in both WordPress and MySQL deployment.<\/p><p>Make sure to change the data in secret with your values, you can see the data are given in <strong>base64-encoded<\/strong> values.<\/p><p>You can use the <strong>echo<\/strong> command with <strong>base64<\/strong> to encode your data, the echo command will give the encoded values of your data. The command to encode your data is given below<\/p><pre><code>echo -n '&lt;data&gt;' | base64<\/code><\/pre><p>For example, if I set my <strong>admin-password<\/strong> as <strong>admin@123<\/strong>, then the echo command will be<\/p><pre><code>echo -n 'admin@123' | base64<\/code><\/pre><p>You will get an output as given below<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-167.png\" class=\"kg-image\" alt=\"encoding the value\" loading=\"lazy\" width=\"960\" height=\"390\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/07\/image-167.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-167.png 960w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><p>Run the following command to create the secret.<\/p><pre><code>kubectl apply -f secret.yaml<\/code><\/pre><p>To verify if the secret is created, run the following command<\/p><pre><code>kubectl get secret<\/code><\/pre><p>You will get the following output<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-166.png\" class=\"kg-image\" alt=\"verifying if the secret is created\" loading=\"lazy\" width=\"960\" height=\"450\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/07\/image-166.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-166.png 960w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-5-create-configmap\">Step 5: Create ConfigMap<\/h3><p>Now, let&#8217;s create two configmap one for MySQL and one for Nginx.<\/p><p><strong>Create Nginx ConfigMap<\/strong><\/p><p>First, create configmap for Nginx, and run the <strong>nginx-cm.yaml<\/strong> file to create configmap on the WordPress namespace.<\/p><pre><code>apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: nginx-configmap\n  namespace: wordpress\ndata:\n  wordpress.conf: |\n    server {\n      listen 80;\n\n      root \/var\/www\/html\/wordpress;\n      index index.php index.html index.htm;\n      \n      server_name localhost:30004;\n\n      location \/ {\n        try_files $uri $uri\/ \/index.php?$args;\n      }\n\n      location ~ \\.php$ {\n        include snippets\/fastcgi-php.conf;\n        fastcgi_pass unix:\/var\/run\/php\/php8.2-fpm.sock;\n        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n        include fastcgi_params;\n      }\n    }<\/code><\/pre><p>This configmap contains the Nginx configuration file which configures Nginx with WordPress to serve the WordPress files on the web.<\/p><pre><code>server_name localhost:30004;<\/code><\/pre><p>Make sure to update your Domain name or IP on this file instead of localhost:30004.<\/p><p>Run the following command to create configmap for Nginx<\/p><pre><code>kubectl apply -f nginx-cm.yaml<\/code><\/pre><p><strong>Create MySQL ConfigMap<\/strong><\/p><p>Next, create the configmap for MySQL, and run the mysql-cm.yaml file to create the configmap on the WordPress namespace.<\/p><pre><code>apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: mysql-configmap\n  namespace: wordpress\ndata:\n  init.sql: |\n    CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD';\n    GRANT ALL PRIVILEGES ON '$MYSQL_DATABASE'.* TO '$MYSQL_USER'@'%';\n    FLUSH PRIVILEGES;<\/code><\/pre><p>This configmap contains the SQL command to create a new user and password and also has the command to grant all privileges to the user on the specified database.<\/p><p>Run the following command to create configmap for MySQL<\/p><pre><code>kubectl apply -f mysql-cm.yaml<\/code><\/pre><p>Now, run the following command to check if the configmaps are created<\/p><pre><code>kubectl get cm<\/code><\/pre><p>You will get the following output.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-168.png\" class=\"kg-image\" alt=\"verifying if the configmaps are created\" loading=\"lazy\" width=\"960\" height=\"570\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/07\/image-168.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-168.png 960w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-6-deploy-mysql-database\">Step 6: Deploy MySQL Database<\/h3><div class=\"kg-card kg-callout-card kg-callout-card-blue\"><div class=\"kg-callout-emoji\">\ud83d\udca1<\/div><div class=\"kg-callout-text\">In this setup, we are using a single replica MySQL. If you want a production grade MySQL setup, refer the <a href=\"https:\/\/devopscube.com\/install-mysql-operator-on-kubernetes\/\" rel=\"noreferrer\">MySQL Kubernetes Operator<\/a> setup.<\/div><\/div><p>Now, that the PVC and ConfigMap have been created, it is time to deploy MySQL database and WordPress.<\/p><p>First, we are going to deploy the MySQL database, and run the <strong><code>mysql.yaml<\/code><\/strong> file to deploy the database on the WordPress namespace.<\/p><pre><code>apiVersion: apps\/v1\nkind: StatefulSet\nmetadata:\n  name: mysql\n  namespace: wordpress\nspec:\n  serviceName: \"mysql\"\n  replicas: 1\n  selector:\n    matchLabels:\n      app: mysql\n  template:\n    metadata:\n      labels:\n        app: mysql\n    spec:\n      containers:\n      - name: mysql\n        image: mysql:latest\n        env:\n        - name: MYSQL_ROOT_PASSWORD\n          valueFrom:\n            secretKeyRef:\n              name: mysql-cred\n              key: admin-password\n        - name: MYSQL_USER\n          valueFrom:\n            secretKeyRef:\n              name: mysql-cred\n              key: username\n        - name: MYSQL_PASSWORD\n          valueFrom:\n            secretKeyRef:\n              name: mysql-cred\n              key: password\n        - name: MYSQL_DATABASE\n          valueFrom:\n            secretKeyRef:\n              name: mysql-cred\n              key: db-name\n        volumeMounts:\n        - name: mysql-volume\n          mountPath: \/var\/lib\/mysql\n        - name: mysql-config\n          mountPath: \/docker-entrypoint-initdb.d\n        resources:\n            requests:\n              memory: \"256Mi\"\n              cpu: \"100m\"\n            limits:\n              memory: \"512Mi\"\n              cpu: \"200m\"\n        livenessProbe:\n          tcpSocket:\n            port: 3306\n          initialDelaySeconds: 30\n          periodSeconds: 10\n          timeoutSeconds: 5\n          successThreshold: 1\n          failureThreshold: 3\n        readinessProbe:\n          tcpSocket:\n            port: 3306\n          initialDelaySeconds: 30\n          periodSeconds: 10\n          timeoutSeconds: 5\n          successThreshold: 1\n          failureThreshold: 3\n      volumes:\n        - name: mysql-config\n          configMap:\n            name: mysql-configmap\n  volumeClaimTemplates:\n    - metadata:\n        name: mysql-volume\n      spec:\n        accessModes: [\"ReadWriteOnce\"]\n        resources:\n          requests:\n            storage: 2Gi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: mysql-service\n  namespace: wordpress\nspec:\n  type: ClusterIP\n  selector:\n    app: mysql\n  ports:\n    - protocol: TCP\n      port: 3306\n      targetPort: 3306<\/code><\/pre><p>This file deploys a <strong>mysql<\/strong> statefulset database on the wordpress namespace which uses the latest MySQL container image.<\/p><p>The environment variable gets the values from the secret we created in <strong>Step 2<\/strong> and the configmap gets the values during startup.<\/p><p>The <strong><code>docker-entrypoint-initdb.d<\/code><\/strong> directory is part of the MySQL image. Any MySQL scripts placed in this folder will be executed during MySQL startup. It is primarily used for running database initialization scripts.<\/p><p>In our setup, we placed an init script (<strong><code>init.sql<\/code><\/strong>) in this folder through a ConfigMap (<strong>mysql-configmap<\/strong>). <strong><code>init.sql <\/code><\/strong>creates the username and password, and gives the user all privileges on the WordPress db.<\/p><p>The script retrieves the username and password from environment variables set by the secret object using <strong><code>secretKeyRef<\/code><\/strong><\/p><p>And it also creates a PVC and mounts the PV on the <strong>\/var\/lib\/mysql<\/strong> directory.<\/p><p>Also, I have added health checks for MySQL:<\/p><ol><li><strong>livenessProbe<\/strong>&nbsp;\u2013&nbsp;This checks if MySQL is up and running by using a TCP socket check on port 3306.<\/li><li><strong>readinessProbe<\/strong>&nbsp;\u2013 This checks if MySQL is ready to accept traffic.<\/li><\/ol><p>Then, this file also creates a Service that exposes the database on port <strong>3306<\/strong>.<\/p><p>Run the following command to deploy MySQL and its service<\/p><pre><code>kubectl apply -f mysql.yaml<\/code><\/pre><p>Now, run the following commands to check if the MySQL StatefulSet and its service is deployed<\/p><pre><code>kubectl get sts\n\nkubectl get svc<\/code><\/pre><p>You will get the following output.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-170.png\" class=\"kg-image\" alt=\"checking if the mysql statefulset and its service are created\" loading=\"lazy\" width=\"1896\" height=\"750\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/07\/image-170.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/07\/image-170.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/07\/image-170.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-170.png 1896w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-7-deploy-wordpress\">Step 7: Deploy WordPress<\/h3><p>After you finish deploying the MySQL Database, the next step is to deploy the WordPress application by running the file <strong>wordpress.yaml<\/strong>.<\/p><pre><code>apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: wordpress-pvc\n  namespace: wordpress\nspec:\n  accessModes:\n    - ReadWriteOnce\n  resources:\n    requests:\n      storage: 2Gi\n---\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: wordpress\n  namespace: wordpress\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: wordpress\n  template:\n    metadata:\n      labels:\n        app: wordpress\n    spec:\n      securityContext:\n        fsGroup: 33\n      containers:\n        - name: wordpress\n          image: techiescamp\/wordpress-nginx:7.0.0\n          env:\n            - name: DB_HOST\n              value: mysql-service.wordpress.svc.cluster.local\n            - name: DB_USER\n              valueFrom:\n                secretKeyRef:\n                  name: mysql-cred\n                  key: username\n            - name: DB_PASSWORD\n              valueFrom:\n                secretKeyRef:\n                  name: mysql-cred\n                  key: password\n            - name: DB_NAME\n              valueFrom:\n                secretKeyRef:\n                  name: mysql-cred\n                  key: db-name\n          ports:\n            - containerPort: 80\n              name: wordpress\n          resources:\n            requests:\n              memory: \"256Mi\"\n              cpu: \"100m\"\n            limits:\n              memory: \"512Mi\"\n              cpu: \"200m\"\n          volumeMounts:\n            - name: nginx-config-volume\n              mountPath: \/etc\/nginx\/conf.d\n            - name: wordpress-persistent-storage\n              mountPath: \/var\/www\/html\/wordpress\/wp-content\/uploads\n          readinessProbe:\n            httpGet:\n              path: \/wp-login.php\n              port: 80\n              scheme: 'HTTP'\n            initialDelaySeconds: 30\n            periodSeconds: 10\n            timeoutSeconds: 5\n            successThreshold: 1\n            failureThreshold: 3\n          livenessProbe:\n            httpGet:\n              path: \/wp-admin\/install.php\n              port: 80\n              scheme: 'HTTP'\n            initialDelaySeconds: 60\n            periodSeconds: 10\n            timeoutSeconds: 5\n            successThreshold: 1\n            failureThreshold: 3\n      volumes:\n        - name: nginx-config-volume\n          configMap:\n            name: nginx-configmap\n        - name: wordpress-persistent-storage\n          persistentVolumeClaim:\n            claimName: wordpress-pvc\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: wordpress-service\n  namespace: wordpress\nspec:\n  type: NodePort\n  selector:\n    app: wordpress\n  ports:\n    - name: http\n      protocol: TCP\n      port: 80\n      targetPort: 80\n      nodePort: 30004<\/code><\/pre><p>First, this application creates a PVC for the WordPress application.<\/p><p>Then it deploys the <strong>WordPress<\/strong> application on the WordPress namespace which uses the <strong>techiescamp\/wordpress-nginx:7.0.0<\/strong> container image we created in <strong>Step 1<\/strong>.<\/p><p>The environment variable gets the values from the secret we created in <strong>Step 2<\/strong> and the startup script inside the container image fetches the values and updates them on the <strong>\/var\/www\/html\/wordpress\/wp-config.php\/uploads<\/strong> WordPress configuration file.<\/p><p>It mounts the data on the configmap <strong>nginx-cm<\/strong> we created in <strong>Step 3<\/strong> on the directory <strong>\/etc\/nginx\/conf.d<\/strong> and mounts the PV on the <strong>\/var\/www\/html\/wordPress\/wp-content\/uploads<\/strong>  directory.<\/p><p>In the resource configuration, the container\u2019s resource request is set to <strong>256Mi memory and 100m CPU<\/strong>, and the limit is set to <strong>512Mi memory and 200m CPU<\/strong>.<\/p><p>Also, I have added health checks for this application:<\/p><ol><li><strong>readinessProbe<\/strong>&nbsp;\u2013 This checks if the application is ready to accept traffic using the readiness path&nbsp;<strong>\/wp-login.php<\/strong>. If this probe fails, it won\u2019t receive any traffic.<\/li><li><strong>livenessProbe<\/strong>&nbsp;\u2013 Checks if the application is up and running using the liveness path&nbsp;<strong>\/wp-admin\/install.php<\/strong>. If the probe fails, Kubernetes will restart the pod.<\/li><\/ol><p>Then, this file also creates a Service that exposes WordPress to the outside world using nodeport 30004.<\/p><div class=\"kg-card kg-callout-card kg-callout-card-blue\"><div class=\"kg-callout-emoji\">\ud83d\udca1<\/div><div class=\"kg-callout-text\">You can also configure Ingress Controller instead of nodeport service to access the application on the web.<\/div><\/div><p>Run the following command to deploy the WordPress.<\/p><pre><code>kubectl apply -f wordpress.yaml<\/code><\/pre><p>Now, run the following commands to check if the WordPress deployment and its service is deployed.<\/p><pre><code>kubectl get deploy\n\nkubectl get svc<\/code><\/pre><p>You will get the following output.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-171.png\" class=\"kg-image\" alt=\"checking if the wordpress deployment and its service are created\" loading=\"lazy\" width=\"1896\" height=\"750\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/07\/image-171.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/07\/image-171.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/07\/image-171.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-171.png 1896w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><p>Since WordPress and MySQL have been created, check the status of PVC and PV using the following commands<\/p><pre><code>kubectl get pvc\n\nkubectl get pv<\/code><\/pre><p>You will get the following output.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-174.png\" class=\"kg-image\" alt=\"listing the pv's and pvc's\" loading=\"lazy\" width=\"821\" height=\"386\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/07\/image-174.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-174.png 821w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><p>As you can see, the PVC is in a bound state, and two PV for WordPress and MySQL have been created.<\/p><h3 id=\"step-8-create-a-networkpolicy\">Step 8: Create a NetworkPolicy<\/h3><p>Now, that MySQL and WordPress are deployed, create a NetworkPolicy for MySQL running the <strong>networkpolicy.yaml<\/strong> file<\/p><pre><code>apiVersion: networking.k8s.io\/v1\nkind: NetworkPolicy\nmetadata:\n  name: mysql-network-policy\n  namespace: wordpress\nspec:\n  podSelector:\n    matchLabels:\n      app: mysql\n  policyTypes:\n    - Ingress\n    - Egress\n  ingress:\n    - from:\n        - podSelector:\n            matchLabels:\n              app: wordpress\n      ports:\n        - port: 3306\n  egress:\n    - {}<\/code><\/pre><p>This file creates a Networkpolicy for MySQL on the WordPress namespace.<\/p><p>The <strong>mysql-network-policy<\/strong> only allows incoming traffic from the pod labeled as <strong>wordpress<\/strong> through port 3306 and allows all outgoing traffic.<\/p><p>Run the following command to deploy the NetworkPolicy.<\/p><pre><code>kubectl apply -f networkpolicy.yaml<\/code><\/pre><p>To check if the NetworkPolicies are created, run the following command<\/p><pre><code>kubectl get netpol<\/code><\/pre><p>You will get the following output.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-175.png\" class=\"kg-image\" alt=\"check if the network policy is created\" loading=\"lazy\" width=\"1078\" height=\"450\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/07\/image-175.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/07\/image-175.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/07\/image-175.png 1078w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-9-log-in-to-wordpress-ui\">Step 9: Log in to WordPress UI<\/h3><p>Once every object has been created, try to access WordPress on the browser by searching on the browser as&nbsp;<strong>{node-IP:nodeport}<\/strong>&nbsp;or if you have configured the Ingress Controller, search the Domain name on the browser.<\/p><p>Since I have deployed using NodePort, I am going to use the <strong>node-IP:nodeport<\/strong>.<\/p><p>You can see a tab to select the language and will get the following on the browser.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-7-23.png\" class=\"kg-image\" alt=\"page to give information about wordpress username and password\" loading=\"lazy\" width=\"1002\" height=\"628\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-7-23.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-7-23.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-7-23.png 1002w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><p>After specifying the site title, username, password, and Email, it will be forwarded to the WordPress dashboard as shown below<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-8-19.png\" class=\"kg-image\" alt=\"wordpress dashboard\" loading=\"lazy\" width=\"1076\" height=\"608\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-8-19.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-8-19.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-8-19.png 1076w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h2 id=\"step-10-clean-up-the-setup\">Step 10: Clean Up the Setup<\/h2><p>Once you are done with the setup, run the following command inside the <strong>04-WordPress-deployment<\/strong> to delete all resources<\/p><pre><code>kubectl delete -f .<\/code><\/pre><p>This command will delete all the resources created by the manifest files inside the <strong>04-WordPress-deployment<\/strong> directory.<\/p><h2 id=\"conclusion\">Conclusion<\/h2><p>In this blog, we have seen the steps to deploy WordPress on Kubernetes clusters with Nginx and MySQL databases.<\/p><p>Not just an installation, also added NetworkPolicy, and Probes like readiness and liveness probes to WordPress and MySQL as best practices.<\/p><p>I hope this blog gives you a clear understanding of deploying WordPress on Kubernetes clusters with Nginx and MySQL databases, as well as the objects used in this setup.<\/p>\n<hr><p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/deploy-wordpress-on-kubernetes\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Deploy WordPress on Kubernetes with Nginx and MySQL \u2014 DevOpsCube<\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/deploy-wordpress-on-kubernetes\/<\/p>\n","protected":false},"author":1,"featured_media":501,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-500","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/500","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=500"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/500\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/501"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=500"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=500"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=500"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}