DEV Community

Vishwa Pratap Singh
Vishwa Pratap Singh

Posted on

Complete Guide: Deploying a Laravel + React Application on Kubernetes with Minikube

Introduction

As a beginner stepping into the world of Kubernetes, deploying a full-stack application can seem overwhelming. In this comprehensive guide, I'll walk you through deploying a Laravel + React application (BookMyShow) on Kubernetes using Minikube on Windows. By the end of this guide, you'll understand not just how to deploy, but why each step matters.

What is Kubernetes and Why Use It?

Kubernetes (often abbreviated as K8s) is an open-source container orchestration platform. Think of it as an intelligent manager that:

  • Automatically deploys and manages containers
  • Scales applications up or down based on demand
  • Handles service discovery and load balancing
  • Self-heals by restarting failed containers

Minikube is a lightweight Kubernetes implementation that creates a local Kubernetes cluster on your machine—perfect for learning and development.

Prerequisites
Before we begin, ensure you have the following installed on your Windows machine:

  1. Docker Desktop - Provides containerization capabilities
  2. Minikube - Local Kubernetes cluster
  3. kubectl - Command-line tool for interacting with Kubernetes

Verify Installation
Open PowerShell and verify installations:

# Check Docker
docker --version

# Check Minikube
minikube version

# Check kubectl
kubectl version --client
Enter fullscreen mode Exit fullscreen mode

Understanding the Architecture

Our application consists of multiple components:

Key Kubernetes Concepts Explained

  1. Pods A Pod is the smallest deployable unit in Kubernetes. It's a wrapper around one or more containers. In our case, we have separate pods for:

# 1. Laravel application
# 2. MySQL database
# 3. Redis cache
# 4. phpMyAdmin

  1. Deployments
    A Deployment describes the desired state for your pods. It ensures the specified number of pod replicas are running and handles updates.

  2. Services
    A Service provides a stable network endpoint to access pods. Even when pods restart and get new IP addresses, the service remains constant.

  3. Persistent Volumes
    Persistent Volumes (PV) provide storage that persists beyond pod lifecycles. Essential for databases where data must survive pod restarts.

  4. Secrets
    Secrets store sensitive information like passwords and API keys securely.

  5. Init Containers
    Init Containers run before the main application container starts. They're perfect for setup tasks like installing dependencies.

Step-by-Step Deployment Guide

Step 1: Start Minikube
First, ensure Docker Desktop is running, then start Minikube:

minikube start
Enter fullscreen mode Exit fullscreen mode

What happens here?

  • Minikube creates a virtual machine
  • Installs Kubernetes components
  • Sets up a single-node cluster Step 2: Configure Docker Environment When building Docker images for Minikube, we need to use Minikube's Docker daemon instead of Docker Desktop's:
minikube -p minikube docker-env --shell powershell | Invoke-Expression
Enter fullscreen mode Exit fullscreen mode

Why is this important? When you build an image using your local Docker, Minikube can't see it because it runs in its own VM. This command configures your terminal to use Minikube's Docker daemon, so images you build are directly accessible to Minikube.

Step 3: Build Your Application Image

docker build -t sail-8.5/app:latest .
Enter fullscreen mode Exit fullscreen mode

Understanding the Dockerfile: Your Dockerfile typically:

  1. Starts from a base PHP image with necessary extensions
  2. Installs Composer dependencies
  3. Copies application code
  4. Sets up permissions
  5. Configures the web server

Step 4: Deploy in the Correct Order
Kubernetes resources must be deployed in a specific order:

# 1. Secrets first (needed by other services)
kubectl apply -f k8s\secrets.yaml

# 2. Storage volumes
kubectl apply -f k8s\persistent-volumes.yaml

# 3. Database and supporting services
kubectl apply -f k8s\mysql-deployment.yaml
kubectl apply -f k8s\redis-deployment.yaml
kubectl apply -f k8s\phpmyadmin-deployment.yaml

# 4. Wait for database to be ready
kubectl wait --for=condition=ready pod -l tier=database --timeout=120s

# 5. Finally, deploy the application
kubectl apply -f k8s\deployment.yaml
kubectl apply -f k8s\service.yaml

# 6. Wait for application to be ready
`kubectl wait --for=condition=ready pod -l tier=frontend --timeout=120s`
Enter fullscreen mode Exit fullscreen mode

Why this order?

Secrets must exist before services reference them
Databases must be ready before applications try to connect
The kubectl wait command ensures each layer is fully operational before proceeding
Step 5: Initialize the Database
Once pods are running, initialize your database:

# Get the pod name dynamically
$podName = kubectl get pods -l tier=frontend -o jsonpath='{.items[0].metadata.name}'

# Run migrations
kubectl exec -it $podName -- php artisan migrate

# Seed database with sample data
kubectl exec -it $podName -- php artisan db:seed
Enter fullscreen mode Exit fullscreen mode

Understanding kubectl exec: This command runs a command inside a running container, similar to SSH-ing into a server. The -it flags mean:

-i: Keep stdin open (interactive)
-t: Allocate a pseudo-TTY (terminal)
Step 6: Start the Development Server
For Laravel + React applications using Vite, we need to start the Vite development server:

kubectl exec -it $podName -- npm run dev -- --host 0.0.0.0
Enter fullscreen mode Exit fullscreen mode

Why --host 0.0.0.0? By default, Vite binds to localhost, which is only accessible from within the container. Using 0.0.0.0 makes it accessible from outside the container.

Step 7: Handle CORS with Port Forwarding
Modern web development often involves CORS (Cross-Origin Resource Sharing) challenges. Here's the solution:

Port-forward the Vite dev server

Start-Job -Name "port-5173" -ScriptBlock { 
    kubectl port-forward $using:podName 5173:5173 
}
Enter fullscreen mode Exit fullscreen mode

What is port-forwarding? Port-forwarding creates a tunnel from your local machine to a pod. When your browser on localhost:5173 makes a request, it's forwarded to the pod's port 5173.

Step 8: Access Your Application

# Get the application URL
minikube service bookmyshow-service --url

# Get phpMyAdmin URL
minikube service phpmyadmin-service --url
Enter fullscreen mode Exit fullscreen mode

Understanding Minikube Services: Minikube exposes services through NodePort, which maps a port on the Minikube VM to your service. The --url flag returns the accessible URL.

Understanding the Kubernetes Configuration Files
secrets.yaml - Securing Sensitive Data

apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque
data:
  password: cGFzc3dvcmQ=  # base64 encoded
Enter fullscreen mode Exit fullscreen mode

Key Points:

  • Secrets are base64 encoded (not encrypted!)
  • In production, use tools like Sealed Secrets or external secret managers
  • Never commit real secrets to version control deployment.yaml - Application Blueprint The deployment file contains:

Init Containers:

initContainers:
- name: init-permissions
  # Sets up file permissions
- name: npm-install
  # Installs Node dependencies
Enter fullscreen mode Exit fullscreen mode

Why Init Containers? They run sequentially before the main container starts, perfect for:

  • Setting up permissions
  • Installing dependencies
  • Running database migrations
  • Downloading configuration

Main Container:

containers:
- name: app
  image: sail-8.5/app:latest
  imagePullPolicy: Never  # Use local image
Enter fullscreen mode Exit fullscreen mode

imagePullPolicy: Never tells Kubernetes not to pull from a registry, using the local image we built instead.

Volume Mounts:

volumeMounts:
- name: app-storage
  mountPath: /var/www/html/storage
- name: node-modules
  mountPath: /var/www/html/node_modules
Enter fullscreen mode Exit fullscreen mode

Volumes persist data across pod restarts. We use:

  • PersistentVolumeClaim for important data (storage)
  • emptyDir for temporary data (cache, node_modules)

service.yaml - Network Access

apiVersion: v1
kind: Service
metadata:
  name: bookmyshow-service
spec:
  type: NodePort
  selector:
    app: bookmyshow
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
Enter fullscreen mode Exit fullscreen mode

Service Types:

  • ClusterIP (default): Only accessible within cluster
  • NodePort: Accessible on each node's IP at a static port
  • LoadBalancer: Cloud provider load balancer (not available in Minikube)

Essential kubectl Commands

Viewing Resources


# View all resources
kubectl get all

# View specific resources
kubectl get pods
kubectl get deployments
kubectl get services

# Detailed information
kubectl describe pod <pod-name>
kubectl get pods -o wide  # Shows IP addresses and nodes
Enter fullscreen mode Exit fullscreen mode

Logs and Debugging

# View logs (last logs)
kubectl logs <pod-name>

# Stream logs (follow mode)
kubectl logs -f <pod-name>

# Logs from all pods with a label
kubectl logs -l tier=frontend -f

# View previous container's logs (if crashed)
kubectl logs <pod-name> --previous

Enter fullscreen mode Exit fullscreen mode

Executing Commands

# Run a command
kubectl exec <pod-name> -- ls -la

# Interactive shell
kubectl exec -it <pod-name> -- bash

# Run command in specific container (multi-container pods)
kubectl exec -it <pod-name> -c <container-name> -- bash

Enter fullscreen mode Exit fullscreen mode

Managing Resources

# Apply changes
kubectl apply -f <file.yaml>

# Delete resources
kubectl delete -f <file.yaml>
kubectl delete pod <pod-name>

# Force restart deployment
kubectl rollout restart deployment <deployment-name>

# Check rollout status
kubectl rollout status deployment <deployment-name>

Enter fullscreen mode Exit fullscreen mode

Daily Workflow: Starting Your Development Environment
After shutting down or restarting your computer, here's your quick-start routine:

# 1. Start Minikube
minikube start

# 2. Wait for pods to be ready (they persist between minikube stops)
kubectl wait --for=condition=ready pod -l tier=frontend --timeout=120s

# 3. Get pod name
$podName = kubectl get pods -l tier=frontend -o jsonpath='{.items[0].metadata.name}'

# 4. Start Vite dev server in background
Start-Job -Name "vite-dev" -ScriptBlock { 
    kubectl exec -it $using:podName -- npm run dev -- --host 0.0.0.0 
}

# 5. Port-forward for CORS
Start-Job -Name "port-5173" -ScriptBlock { 
    kubectl port-forward $using:podName 5173:5173 
}

# 6. Get application URLs
minikube service bookmyshow-service --url
minikube service phpmyadmin-service --url
Enter fullscreen mode Exit fullscreen mode

Common Issues and Solutions

Issue 1: Image Pull Errors
Error: ErrImagePull or ImagePullBackOff

Solution:

# Rebuild using Minikube's Docker
minikube -p minikube docker-env --shell powershell | Invoke-Expression
docker build -t sail-8.5/app:latest .
kubectl delete pod -l tier=frontend

Enter fullscreen mode Exit fullscreen mode

Why? Kubernetes tries to pull from a registry, but our image only exists locally in Minikube's Docker.

Issue 2: CORS Errors
Error: CORS request did not succeed

Solution:


# Port-forward the Vite dev server
Start-Job -ScriptBlock { kubectl port-forward $using:podName 5173:5173 }
Enter fullscreen mode Exit fullscreen mode

Why? Your browser can't directly access the Vite dev server inside the pod without port-forwarding.

Issue 3: Port Already in Use
Error: Port 5173 is already in use

Solution:

# Kill the existing process
kubectl exec -it $podName -- pkill -f "vite"

# Or restart the pod
kubectl delete pod -l tier=frontend
Enter fullscreen mode Exit fullscreen mode

Issue 4: Database Connection Failed
Solution:

# Check if MySQL pod is running
kubectl get pods -l tier=database

# Check MySQL logs
kubectl logs -l tier=database

# Verify service exists
kubectl get service mysql-service

Enter fullscreen mode Exit fullscreen mode

Debugging Tips:

  • Services use DNS names (e.g., mysql-service)
  • Check environment variables are correct
  • Ensure secrets are created before deployments

Best Practices

  1. Use Labels Consistently

Labels help organize and select resources:

labels:
  app: bookmyshow
  tier: frontend
  environment: development
Enter fullscreen mode Exit fullscreen mode
  1. Set Resource Limits Prevent resource exhaustion:
resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"
    cpu: "500m"
Enter fullscreen mode Exit fullscreen mode
  1. Use Health Checks Add liveness and readiness probes:
livenessProbe:
  httpGet:
    path: /health
    port: 80
  initialDelaySeconds: 30
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /ready
    port: 80
  initialDelaySeconds: 10
  periodSeconds: 5
Enter fullscreen mode Exit fullscreen mode
  1. Namespace Your Resources For larger projects, use namespaces:
# Create namespace
kubectl create namespace bookmyshow

# Deploy to namespace
kubectl apply -f deployment.yaml -n bookmyshow

# Set default namespace
kubectl config set-context --current --namespace=bookmyshow

Enter fullscreen mode Exit fullscreen mode
  1. Use ConfigMaps for Configuration Separate configuration from code:
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_ENV: "local"
  LOG_LEVEL: "debug"
Monitoring and Observability
Viewing Resource Usage
# Pod resource usage
kubectl top pods

# Node resource usage
kubectl top nodes

# Describe for events
kubectl describe pod <pod-name>

Enter fullscreen mode Exit fullscreen mode

Accessing Kubernetes Dashboard

# Start dashboard
minikube dashboard

Enter fullscreen mode Exit fullscreen mode

This opens a web UI showing all cluster resources visually.

Cleaning Up
Partial Cleanup (Keep Cluster)

# Delete all application resources
kubectl delete -f k8s\service.yaml
kubectl delete -f k8s\deployment.yaml
kubectl delete -f k8s\phpmyadmin-deployment.yaml
kubectl delete -f k8s\redis-deployment.yaml
kubectl delete -f k8s\mysql-deployment.yaml
kubectl delete -f k8s\persistent-volumes.yaml
kubectl delete -f k8s\secrets.yaml

# Stop Minikube (preserves cluster state)
minikube stop

Enter fullscreen mode Exit fullscreen mode

Complete Cleanup

# Delete entire cluster
minikube delete

# This removes everything: all pods, services, volumes

Enter fullscreen mode Exit fullscreen mode

Understanding PowerShell Background Jobs
PowerShell jobs let processes run in the background:

# Start a background job
Start-Job -Name "my-job" -ScriptBlock { 
    # Long-running command
}

# List jobs
Get-Job

# View job output
Receive-Job -Name "my-job"

# Stop and remove
Stop-Job -Name "my-job"
Remove-Job -Name "my-job"

Enter fullscreen mode Exit fullscreen mode

When to use jobs:

Port-forwarding (needs to stay active)
Running dev servers
Any long-running process that blocks your terminal
Advanced Topics to Explore Next

  1. Helm Charts
    Package manager for Kubernetes—like npm for K8s.

  2. Ingress Controllers
    Advanced routing and load balancing.

  3. StatefulSets
    For stateful applications like databases with specific pod identity requirements.

  4. Horizontal Pod Autoscaling
    Automatically scale pods based on CPU/memory usage.

  5. CI/CD Integration
    Automate deployment with GitHub Actions, Jenkins, or GitLab CI.

Conclusion

Congratulations! You've successfully deployed a full-stack Laravel + React application on Kubernetes using Minikube. You now understand:

  • Core Kubernetes concepts (Pods, Deployments, Services)
  • How to use kubectl effectively
  • Troubleshooting common issues
  • Daily development workflow with Kubernetes
  • Best practices for container orchestration Kubernetes has a steep learning curve, but with hands-on practice like this, you'll quickly become proficient. The skills you've learned here apply not just to local development with Minikube, but also to production Kubernetes clusters in the cloud (AWS EKS, Google GKE, Azure AKS).

Additional Resources

This guide is based on deploying the BookMyShow application on Windows with Docker Desktop and Minikube. For production deployments, consider additional security measures, monitoring solutions, and cloud-native services.

Top comments (0)