Home » How to Deploy a WordPress Website using Docker, AWS ECR, and AWS EKS

How to Deploy a WordPress Website using Docker, AWS ECR, and AWS EKS

Deploy Wordpress Using Docker, ECR & EKS

Introduction

Deploying monolithic applications like WordPress into microservice architectures requires robust container orchestration to achieve enterprise-grade availability, auto-scaling, and failover support. While traditional single-server installations introduce single points of failure, containerizing WordPress with Docker, hosting images in Amazon Elastic Container Registry (ECR), and orchestrating worker nodes via Amazon Elastic Kubernetes Service (EKS) delivers a fault-tolerant cloud platform.

In this comprehensive production guide, we will containerize WordPress and MySQL, build local Docker images, push production artifacts to Amazon ECR repositories, provision an Amazon EKS cluster with AWS EBS CSI storage drivers, and deploy stateful Kubernetes manifests.

Prerequisites

Before starting, make sure you have:

  • An AWS Account
  • AWS CLI configured (aws configure)
  • Docker installed
  • kubectl installed and configured
  • eksctl installed
  • Basic knowledge of Docker and Kubernetes

Step 1: Containerize WordPress & MySQL with Docker Compose

First, construct local Docker environment manifests to verify database connections before pushing to AWS ECR:

  • Create Dockerfile for setup WordPress
  • Create a Dockerfile for WordPress Mysql
  • Create a root project directory containing a docker-compose.yml file
Multi Copy Code Blocks
yaml

version: '3.8'

services:
  db:
    build: ./backend
    image: local/wp-backend:latest
    container_name: wp-db
    environment:
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${DB_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5

  wordpress:
    build: ./frontend
    image: local/wp-frontend:latest
    container_name: wp-frontend
    depends_on:
      db:
        condition: service_healthy
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: ${DB_USER}
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WORDPRESS_DB_NAME: ${DB_NAME}
    ports:
      - "8080:80"
    volumes:
      - wp_data:/var/www/html

volumes:
  db_data:
  wp_data:
    
  • Now, Build Images using docker composeMulti Copy Code Blocks
Multi Copy Code Blocks
bash

docker compose up -d --build
    
  • Visit http://localhost:8080 in your web browser to confirm local setup health.

Step 2: Provision ECR Repositories & Push Container Images

Next, create private Amazon ECR repositories, authenticate your local Docker daemon, tag your built images, and push them to AWS:

  • Set environmental variables for your AWS Region and Account ID:
Multi Copy Code Blocks
bash

export AWS_REGION="ap-south-1"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
    
  • Provision ECR repositories for both frontend and backend workloads:
Multi Copy Code Blocks
bash

# For WordPress
aws ecr create-repository --repository-name wp-frontend --region $AWS_REGION

# For Mysql
aws ecr create-repository --reposiroty-name wp-backend --region $AWS_REGION
    
  • Authenticate Docker against your private AWS ECR registry:Multi Copy Code Blocks
Multi Copy Code Blocks
bash

aws ecr get-login-password -–region $AWS_REGION | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
    
  • Tag and push both local container images to Amazon ECR:Multi Copy Code Blocks
Multi Copy Code Blocks
bash

# Tag Local Images to ECR Format

docker tag cloudwithyuvi/wp-frontend:latest ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/wp-frontend:latest

docker tag cloudwithyuvi/wp-backend:8.0 ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/wp-backend:8.0

# Push the Image to ECR

docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/wp-frontend:latest

docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/wp-backend:8.0
    

Step 3: Provision Amazon EKS Cluster & Storage Driver

Now, initialize a managed Kubernetes cluster using eksctl and enable the AWS EBS CSI driver for persistent block storage:

  • Install kubectl
    Multi Copy Code Blocks
Multi Copy Code Blocks
bash

curl -o kubectl https://amazon-eks.s3.us-west-2.amazonaws.com/1.30.0/2024-06-14/bin/linux/amd64/kubectl
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin

kubectl version --client
    
  • Install eksctl
    Multi Copy Code Blocks
Multi Copy Code Blocks
bash

curl --silent --location "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/local/bin

eksctl version
    
  • Create role for EKS
    • Go to AWS Console > IAM > Roles
    • Select Use case EC2 and attach Policy AmazonEKSWorkerNodePolicy, AmazonEC2ContainerRegistryReadOnly, AmazonEKS_CNI_Policy and provide role name EKSNodeRole
  • Provision a 2-node managed Amazon EKS cluster:
Multi Copy Code Blocks
bash

eksctl create cluster \
  --name wordpress-cluster \
  --region ap-south-1 \
  --nodegroup-name wordpress-nodes \
  --node-type t3.medium \
  --nodes 2 \
  --nodes-min 2 \
  --nodes-max 4 \
  --managed
    

Note : It will take few minutes to create AWS EKS Cluster. You can take a tea break. 😂😂

  • Configure local kubectl context to connect to your new EKS cluster:
Multi Copy Code Blocks
bash

aws eks --region ap-south-1 update-kubeconfig --name wordpress-cluster
    
  • Create IAM service accounts and attach the EBS CSI Driver add-on for dynamic volume provisioning:
Multi Copy Code Blocks
bash

eksctl create iamserviceaccount \
  --name ebs-csi-controller-sa \
  --namespace kube-system \
  --cluster wordpress-cluster \
  --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
  --approve \
  --role-only \
  --role-name AmazonEKS_EBS_CSI_DriverRole

aws eks create-addon \
  --cluster-name wordpress-cluster \
  --addon-name aws-ebs-csi-driver \
  --service-account-role-arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/AmazonEKS_EBS_CSI_DriverRole
    
  • Verifying driver
Multi Copy Code Blocks
bash

kubectl get pods -n kube-system | grep ebs
    
  • To check and verifying nodes
Multi Copy Code Blocks
bash

kubectl get nodes
    

Step 4: Deploy Stateful Kubernetes Manifests & Secrets

Finally, deploy isolated namespace resources, persistent volumes, database secrets, and load balancer services:

  • Create a unified Kubernetes manifest file (wordpress-k8s-manifest.yaml):
Multi Copy Code Blocks
yaml

apiVersion: v1
kind: Namespace
metadata:
  name: wordpress
---
apiVersion: v1
kind: Secret
metadata:
  name: wp-db-password
  namespace: wordpress
type: Opaque
stringData:
  db-password: wpproductionpassword
  root-password: rootsecretpassword
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  namespace: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: gp2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-pv-claim
  namespace: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: gp2
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: wordpress
spec:
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: ACCOUNT_ID.dkr.ecr.ap-south-1.amazonaws.com/wp-backend:latest
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: wp-db-password
              key: root-password
        - name: MYSQL_DATABASE
          value: wordpress
        - name: MYSQL_USER
          value: wpuser
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: wp-db-password
              key: db-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
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: mysql
  clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  namespace: wordpress
spec:
  replicas: 2
  selector:
    matchLabels:
      app: wordpress
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
      - image: ACCOUNT_ID.dkr.ecr.ap-south-1.amazonaws.com/wp-frontend:latest
        name: wordpress
        env:
        - name: WORDPRESS_DB_HOST
          value: mysql.wordpress.svc.cluster.local:3306
        - name: WORDPRESS_DB_USER
          value: wpuser
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: wp-db-password
              key: db-password
        - name: WORDPRESS_DB_NAME
          value: wordpress
        ports:
        - containerPort: 80
        volumeMounts:
        - name: wp-persistent-storage
          mountPath: /var/www/html
      volumes:
      - name: wp-persistent-storage
        persistentVolumeClaim:
          claimName: wp-pv-claim
---
apiVersion: v1
kind: Service
metadata:
  name: wordpress-service
  namespace: wordpress
spec:
  type: LoadBalancer
  ports:
    - port: 80
  selector:
    app: wordpress
    
  • Replace ACCOUNT_ID in the manifest with your actual AWS Account ID and apply the configuration:
Multi Copy Code Blocks
yaml

kubectl apply -f wordpress-k8s-manifest.yaml
    
  • Retrieve the active Application Load Balancer endpoint to access your live WordPress site:
Multi Copy Code Blocks
yaml

kubectl get svc -n wordpress wordpress-service -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
    
  • Copy the external IP for run your website on browser
Multi Copy Code Blocks
yaml

kubectl get svc -n wordpress
    

If all are running status then copy the External IP of wordpress-service and paste it on your browser. 

Stateful Workload & Storage Safeguards in Kubernetes:

  • EBS CSI Driver Requirement: Without the aws-ebs-csi-driver installed, your Persistent Volume Claims (PVC) will remain permanently stuck in the Pending state, preventing pod scheduling.
  • ReadWriteOnce Access Modes: AWS EBS volumes can only be attached to a single AWS EC2 instance at a time (ReadWriteOnce). For multi-node WordPress autoscaling with shared media uploads, consider utilizing Amazon EFS (Elastic File System) instead of EBS.

Production Troubleshooting: Common EKS & ECR Deployment Errors

Deploying stateful microservices to Kubernetes requires monitoring volume attachments and image credentials. Use this matrix to debug common runtime failures:

Error 1: PersistentVolumeClaim Stuck in Pending State

  • The Error Log (kubectl describe pvc -n wordpress):
Multi Copy Code Blocks
plaintext

Events:
  Type     Reason              Age   From                         Message
  Warning  ProvisioningFailed  1m    ebs.csi.aws.com_controller   storageclass.storage.k8s.io "gp2" not found / waiting for first consumer

  • The Root Cause: The AWS EBS CSI Controller addon is either not installed or lacks necessary IAM execution permissions via OIDC.
  • The Fix: Verify that AmazonEKS_EBS_CSI_DriverRole is attached to your cluster service account as shown in Step 3.

Error 2: Pod Status Shows ImagePullBackOff or ErrImagePull

  • The Error Log (kubectl get pods -n wordpress):
Multi Copy Code Blocks
plaintext

NAME                        READY   STATUS             RESTARTS   AGE
wordpress-685794698-x29lp   0/1     ImagePullBackOff   0          2m
  • The Root Cause: The node IAM role lacks access to ECR, or the image tag/path specified in the deployment YAML does not match the actual ECR repository path.
  • The Fix: Attach AmazonEC2ContainerRegistryReadOnly policy to your EKS Node Group IAM Role or verify the image string format: ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}[.amazonaws.com/wp-frontend:latest](https://.amazonaws.com/wp-frontend:latest).

Error 3: Database Connection Error on WordPress Screen

  • The Error Log (Container Logs):
Multi Copy Code Blocks
plaintext

Error establishing a database connection.
  • The Root Cause: The WordPress pod cannot resolve the headless MySQL service name (mysql.wordpress.svc.cluster.local) or database password credentials in wp-db-password secret are mismatched.
  • The Fix: Run kubectl get svc -n wordpress to verify that the headless mysql service port 3306 is active and correctly labeled.

      Leave a Reply

      Your email address will not be published. Required fields are marked *