Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Kubernetes for Laravel Getting Started

By Kokil Thapa | Last reviewed: August 2026

Moving from traditional VPS or shared hosting to container orchestration is a significant architectural shift, and Kubernetes for Laravel getting started requires understanding both PHP runtime specifics and cluster networking. While many tutorials focus on generic Node.js examples, Laravel applications have unique requirements around writable storage, queue workers, cron scheduling, and OPcache invalidation that directly impact your manifest design. This guide bridges that gap by providing battle-tested configurations specifically validated for Laravel 12.x on PHP 8.4 in production environments during 2026.

How do you structure a production-ready Dockerfile for Laravel on Kubernetes?

The foundation of any successful modern Laravel architecture on Kubernetes is an optimized container image. Unlike simple development setups, production images must minimize attack surface, reduce startup time, and handle immutable infrastructure patterns correctly. I've seen too many teams ship bloated images containing Composer dev dependencies or missing critical PHP extensions, leading to runtime failures that only surface after deployment.

Stage 1: Basephp:8.4-fpm-alpineSystem deps + extensionsOPcache + Redis configStage 2: DepsComposer install --no-devnpm ci && npm run buildVendor + public/buildStage 3: RuntimeCOPY from base + depsNginx + PHP-FPM supervisor~180MB final imageCritical: Storage Volume Mount Point/var/www/html/storage/app/public → PVC (ReadWriteMany)
Multi-stage Docker build pipeline separating build-time dependencies from the minimal runtime image required for Kubernetes for Laravel getting started
# Dockerfile - Optimized for Laravel 12.x on PHP 8.4
FROM php:8.4-fpm-alpine AS base

RUN apk add --no-cache \
    nginx supervisor libpng-dev libjpeg-turbo-dev freetype-dev \
    oniguruma-dev libzip-dev redis-dev icu-dev postgresql-dev

RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
        pdo_mysql pdo_pgsql mbstring zip exif pcntl bcmath opcache redis intl

COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
COPY docker/php/www.conf /usr/local/etc/php-fpm.d/www.conf

FROM composer:2.7 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --ignore-platform-reqs

FROM node:22-alpine AS assets
WORKDIR /app
COPY package.json package-lock.json vite.config.js ./
RUN npm ci
COPY resources ./resources
RUN npm run build

FROM base AS runtime
WORKDIR /var/www/html
COPY --from=vendor /app/vendor ./vendor
COPY --from=assets /app/public/build ./public/build
COPY . .

RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

COPY docker/nginx/default.conf /etc/nginx/http.d/default.conf
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

EXPOSE 8080
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

This multi-stage approach keeps your final image under 200MB while ensuring all compiled assets and production-only Composer packages are present. The supervisord process manager runs both Nginx and PHP-FPM within a single pod, which simplifies networking but requires careful health check configuration. For higher traffic applications, consider separating Nginx into its own sidecar container to allow independent scaling of the web server layer.

What Kubernetes resources does a Laravel application actually need?

A common mistake when approaching Kubernetes for Laravel getting started is treating the application as a single monolithic deployment. In practice, Laravel applications consist of at least three distinct workload types: HTTP request handling, asynchronous queue processing, and scheduled task execution. Each has fundamentally different scaling characteristics, resource profiles, and failure modes. Bundling them together creates operational headaches that compound as traffic grows.

Resource TypePurposeScaling StrategyStorage NeedHealth Check
Deployment (Web)HTTP requests via PHP-FPMHPA on CPU/RPS metricsReadOnly root + RW uploads/up endpoint (Laravel 11+)
Deployment (Queue)Background job processingKEDA on queue depth or manual replicasShared session/cache optionalProcess liveness only
CronJobScheduler every minuteFixed schedule, concurrency forbiddenNone typicallyCompletion timeout
PersistentVolumeClaimUser uploads, temp filesN/A - provisioned onceReadWriteMany requiredN/A
ConfigMap / Secret.env values, DB credentialsUpdated via rollout restartN/AN/A

The separation between web and queue deployments is non-negotiable for production systems. Queue workers can consume significant memory during large imports or PDF generation, and you don't want that starving your HTTP response handlers. On projects like Nepal Gift Card, where digital product delivery involves async license key generation, isolating queue workers prevented checkout timeouts during peak load. Similarly, running php artisan schedule:run inside your web pods leads to duplicate executions during horizontal scaling; always use a dedicated CronJob resource with concurrencyPolicy: Forbid.

How do you handle persistent storage and file uploads in Kubernetes?

Storage is where most Laravel teams hit their first major wall with Kubernetes. Unlike stateless APIs, Laravel applications frequently write to storage/app/public for user uploads, generated reports, or cached views. Containers are ephemeral by design, meaning any file written to the local filesystem disappears when the pod restarts. For database-driven website development in Nepal projects involving document-heavy legal portals or e-commerce product catalogs, solving this reliably is essential before going live.

Web Pod 1PHP-FPM + Nginx/storage mounted RWWeb Pod 2PHP-FPM + Nginx/storage mounted RWQueue Pod 1artisan queue:work/storage mounted RWCronJobschedule:runEphemeral onlyPersistentVolumeClaim (ReadWriteMany)NFS / EFS / CephFS / Longhornstorage/app/public + framework/cache⚠ Alternative: S3/GCS Driver eliminates shared filesystem dependency entirelyRecommended for cloud-native Laravel deployments on AWS/GCP/Azure
Shared persistent storage topology enabling multiple Laravel pods to access uploaded files consistently across Kubernetes nodes

You have two viable paths. The first uses a ReadWriteMany (RWX) PersistentVolumeClaim backed by NFS, Amazon EFS, CephFS, or Longhorn. This allows all pods to read and write the same directory simultaneously. The second, often superior path for cloud deployments, configures Laravel's filesystem driver to use S3, Google Cloud Storage, or Azure Blob directly, eliminating shared state entirely. I strongly prefer the object storage approach for new projects because it removes an entire class of distributed filesystem bugs and scales without provisioning larger volumes.

# k8s/pvc.yaml - Only needed if NOT using S3/GCS
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: laravel-storage
spec:
  accessModes:
    - ReadWriteMany  # CRITICAL: Not ReadWriteOnce
  storageClassName: efs-sc  # or nfs-client, longhorn
  resources:
    requests:
      storage: 20Gi

# In your Deployment spec:
volumes:
  - name: storage
    persistentVolumeClaim:
      claimName: laravel-storage
containers:
  - name: laravel
    volumeMounts:
      - name: storage
        mountPath: /var/www/html/storage/app/public
        subPath: public
      - name: storage
        mountPath: /var/www/html/storage/framework/sessions
        subPath: sessions

If you choose the object storage route, set FILESYSTEM_DISK=s3 in your ConfigMap and ensure your IAM roles or service accounts grant appropriate bucket permissions. Run php artisan storage:link during container build, not at runtime, since the symlink target won't exist on ephemeral containers. For local development parity, use MinIO or LocalStack to emulate S3 behavior without touching production infrastructure.

How should environment variables and secrets be managed securely?

Laravel's .env file pattern doesn't translate directly to Kubernetes. Hardcoding secrets in manifests commits credentials to version control, while mounting entire .env files as volumes prevents granular updates. The correct approach splits configuration into ConfigMaps for non-sensitive values and Secrets for credentials, then injects them as environment variables or mounted files depending on sensitivity. When working as a Laravel developer in Nepal serving international clients, maintaining this separation ensures compliance expectations are met regardless of where the cluster runs.

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: laravel-config
data:
  APP_NAME: "My Application"
  APP_ENV: "production"
  APP_DEBUG: "false"
  APP_URL: "https://example.com"
  LOG_CHANNEL: "stderr"  # Critical for kubectl logs
  CACHE_STORE: "redis"
  QUEUE_CONNECTION: "redis"
  SESSION_DRIVER: "redis"
  FILESYSTEM_DISK: "s3"

---
# k8s/secret.yaml (create via CLI, never commit plaintext)
# kubectl create secret generic laravel-secrets \
#   --from-literal=APP_KEY='base64:...' \
#   --from-literal=DB_PASSWORD='...' \
#   --from-literal=REDIS_PASSWORD='...' \
#   --from-literal=AWS_SECRET_ACCESS_KEY='...'

Always set LOG_CHANNEL=stderr in Kubernetes environments. Laravel's default stack channel writes to files inside the container, which vanish on restart and aren't captured by cluster logging solutions like Fluent Bit or Loki. Writing to standard error ensures logs flow through kubectl and your observability stack. Similarly, prefer Redis over database or file drivers for cache, sessions, and queues; these stateful components perform poorly when backed by shared filesystems or add unnecessary database load.

What deployment strategy prevents downtime during Laravel releases?

Zero-downtime deployments require coordinating three factors: graceful PHP-FPM shutdown, asset versioning, and database migration timing. Laravel's Vite-manifest approach handles frontend assets well when combined with proper cache headers, but backend code changes need careful orchestration. On sister sites sharing Deployer 7 pipelines, we solved this with symlink swaps; Kubernetes achieves equivalent safety through rolling updates with configured surge and unavailability parameters.

Service Endpoint (ClusterIP / LoadBalancer)Routes traffic ONLY to Ready podsOld Pod v1.2.3TerminatingDraining connectionspreStop: sleep 5sNew Pod v1.2.4StartingRunning migrations?Readiness: /up ✓New Pod v1.2.4ReadyServing trafficOPcache warmedPending PodWaiting for quotamaxSurge: 1maxUnavailable: 0Migration Strategy Decision TreeBackward-compatible schema? → Run in initContainer BEFORE rolloutBreaking changes? → Two-phase deploy: migrate first, then release codeNEVER run migrate:fresh or destructive ops in production entrypoints
Rolling update lifecycle demonstrating safe pod replacement order and migration timing for Laravel Kubernetes deployments
# k8s/deployment-web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: laravel-web
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0  # Zero downtime guarantee
  template:
    spec:
      terminationGracePeriodSeconds: 30
      containers:
        - name: laravel
          image: registry.example.com/laravel-app:v1.2.4
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: laravel-config
            - secretRef:
                name: laravel-secrets
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 5"]
          readinessProbe:
            httpGet:
              path: /up
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /up
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: "1"
              memory: 512Mi

The preStop hook sleeping five seconds is crucial. Kubernetes removes the pod from service endpoints and sends SIGTERM nearly simultaneously, but kube-proxy rules propagate asynchronously. Without this delay, in-flight requests may still route to a terminating pod, causing connection resets. Laravel 11+'s built-in /up endpoint provides ideal health checks; for older versions, create a simple controller returning 200 OK. Database migrations should run in an initContainer or separate Job resource before the main deployment rolls out, preventing race conditions where new code queries columns that don't yet exist.

Getting Started with Kubernetes for Laravel: Next Steps

Successfully adopting Kubernetes for Laravel getting started means accepting that operational complexity increases even as scalability improves. Begin with a managed Kubernetes offering like DigitalOcean DOKS, AWS EKS, or Google GKE rather than self-managing etcd and control planes; the monthly cost difference (~Rs 8,000–15,000 NPR / $60–110 USD) buys enormous reliability and frees you to focus on application concerns. Validate your Docker builds locally with Docker Compose before pushing to a cluster, and invest early in CI/CD pipelines that automate image tagging and manifest updates.

Monitor aggressively from day one using Prometheus and Grafana or managed alternatives. Track PHP-FPM active processes, queue lag, p95 latency, and OOM kill rates—these signals reveal problems before users notice. If your team lacks dedicated DevOps capacity, consider whether a well-configured Platform-as-a-Service like Laravel Vapor or Render might deliver similar benefits with less overhead. Kubernetes earns its keep at scale, but premature orchestration distracts from shipping features that grow your business.

Ready to architect your Laravel infrastructure properly? Reach out to discuss your deployment needs and get a tailored assessment of whether Kubernetes, serverless, or traditional hosting best serves your current stage and growth trajectory.

Frequently Asked Questions

No. For most Laravel projects, a single VPS with Deployer and PHP-FPM is simpler and cheaper. Kubernetes adds significant operational complexity that only pays off at massive scale or when managing dozens of microservices.

You need at least three nodes with 4GB RAM each for a stable control plane and workload separation. Expect to spend Rs 15,000 to 25,000 monthly on cloud infrastructure before adding database or storage costs.

Sessions must be externalized because pods are ephemeral and stateless. Configure Laravel to use Redis or database drivers instead of file sessions. I always use Redis for session storage in clustered environments to ensure user data persists across pod restarts and scaling events without sticky sessions.

Yes, but treat queue workers as separate deployments from your web pods. Use Kubernetes Jobs or CronJobs for scheduled tasks instead of Laravel Scheduler running inside a web container. This prevents duplicate task execution during horizontal scaling and allows independent resource allocation for CPU-intensive background processing workloads.

Run migrations as a Kubernetes Job that executes before the main application deployment starts. Never run migrate:fresh or artisan migrate automatically during pod startup, as multiple pods starting simultaneously will cause race conditions and database locks. Use init containers or pre-deployment hooks in your CI pipeline to guarantee schema consistency.

Store secrets in Kubernetes Secrets objects and mount them as environment variables or files. Never commit .env files to Git or bake them into Docker images. For Laravel, map Kubernetes secrets to APP_KEY, DB_PASSWORD, and API keys. Consider tools like External Secrets Operator to sync from HashiCorp Vault or AWS Secrets Manager for production-grade security.

No. Kubernetes orchestrates containers but does not optimize PHP execution. Performance still depends on proper OPcache configuration, query optimization, and caching strategies. In my experience, poorly configured Laravel apps run slower on Kubernetes due to network latency between services. Optimize your application first before assuming clustering solves performance bottlenecks.

Create dedicated /up and /health endpoints using Laravel's built-in health routing. Configure liveness probes to check if PHP-FPM responds and readiness probes to verify database connectivity. Set initialDelaySeconds to 30-60 seconds to allow OPcache warmup. Misconfigured probes cause cascading restarts that look like downtime during deployments.

Use object storage like S3, MinIO, or Cloudflare R2 via Laravel Filesystem adapters. Local pod storage is ephemeral and lost on restart. While PersistentVolumeClaims exist, they create node affinity issues and complicate scaling. On projects like Nepal Gift Card, we moved all media to S3-compatible storage before considering Kubernetes to avoid stateful pod complications entirely.

Use kubectl exec to access running pods for artisan commands and log inspection. Forward ports locally with kubectl port-forward for testing. Install Laravel Debugbar only in development namespaces, never production. Centralize logs using Fluent Bit or Vector to Elasticsearch since pod logs disappear on restart. Debugging distributed systems requires observability tooling beyond traditional SSH access.

Service discovery failures, DNS resolution timeouts, and ingress misconfiguration cause most issues. Laravel expects direct database hostnames, but Kubernetes uses service names. Verify CoreDNS is healthy and test connectivity with kubectl exec. Ingress controllers must properly forward X-Forwarded headers or Laravel generates wrong URLs. I have spent hours troubleshooting trust proxy settings because load balancers terminated SSL without passing correct headers.

HPA scales pods based on CPU, memory, or custom metrics like queue length. Laravel requests vary wildly, so CPU-based scaling often reacts too slowly. Configure custom metrics via Prometheus adapter tracking active queue jobs or request latency. Set conservative min/max replicas to prevent cold-start storms. Remember that each new pod needs OPcache warmup time before handling traffic efficiently.

A basic managed Kubernetes cluster costs Rs 12,000-20,000 monthly versus Rs 2,000-5,000 for a capable VPS. Add expenses for managed databases, load balancers, and monitoring. Kubernetes makes financial sense only when you need auto-scaling across many services or have compliance requirements demanding orchestration. For typical Nepali business applications, traditional hosting delivers better ROI with lower operational overhead.

Choose managed Kubernetes unless you have dedicated DevOps staff. Self-hosting requires maintaining etcd, certificate rotation, and upgrade paths that distract from application development. Managed services handle control plane reliability while you focus on Laravel code. For Nepal-based teams without full-time infrastructure engineers, the premium for managed Kubernetes prevents costly outages and reduces maintenance burden significantly.

Consider Docker Compose for local parity, then graduate to single-server Docker Swarm or Nomad before Kubernetes. Platforms like Laravel Forge, Ploi, or Coolify automate multi-server deployments without orchestration complexity. Many production Laravel apps I maintain run perfectly on two-node setups with shared Redis and managed MySQL. Only adopt Kubernetes when you have proven scaling pain that simpler solutions cannot solve.

Share this article

Quick Contact Options
Choose how you want to connect me: