• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Kubernetes-Native PHP Deployments: Optimizing Laravel Performance and Scalability with FPM, RoadRunner, and Advanced Caching Strategies

Kubernetes-Native PHP Deployments: Optimizing Laravel Performance and Scalability with FPM, RoadRunner, and Advanced Caching Strategies

Leveraging PHP-FPM with Kubernetes for Scalable Web Applications

For many years, PHP-FPM has been the de facto standard for running PHP applications in production, especially within containerized environments like Kubernetes. Its ability to manage worker processes efficiently and integrate seamlessly with web servers like Nginx makes it a robust choice. When deploying PHP-FPM on Kubernetes, the key is to configure it for optimal performance and scalability, leveraging Kubernetes primitives for resource management and auto-scaling.

Kubernetes Deployment Configuration for PHP-FPM

A typical Kubernetes deployment for a PHP-FPM application involves a Deployment object managing multiple Pods, each running the PHP-FPM process. We’ll also use a Service to expose these Pods internally and potentially an Ingress for external access. The critical part is the PHP-FPM configuration itself, which needs to be tuned for a containerized, dynamic environment.

PHP-FPM Pool Configuration (`www.conf`)

The `php-fpm.d/www.conf` file is central to controlling PHP-FPM’s behavior. In a Kubernetes context, we want to dynamically adjust the number of worker processes based on available resources and application load. The `pm` (process manager) setting is crucial here. For Kubernetes, `dynamic` or `ondemand` are generally preferred over `static` to avoid over-provisioning or under-utilization.

Here’s an example of a `www.conf` tailored for Kubernetes:

[global]
pid = /run/php/php7.4-fpm.pid
error_log = /var/log/php/php-fpm.log
daemonize = no

[www]
user = www-data
group = www-data
listen = /run/php/php7.4-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 2
pm.max_spare_servers = 10
pm.process_idle_timeout = 10s
pm.max_requests = 500

request_terminate_timeout = 60s
request_slowlog_timeout = 10s
slowlog = /var/log/php/php-fpm.slow.log

catch_workers_output = yes
access.log = /var/log/php/access.log
access.format = "%R - %u %t \"%m %r%Q%qHttp/%H\" %s %O \"%{Referer}i\" \"%{User-Agent}i\""
clear_env = no

Key considerations for this configuration:

  • daemonize = no: Essential for running within a container. The container orchestrator (Kubernetes) manages the process lifecycle.
  • listen: Using a Unix socket (`/run/php/php7.4-fpm.sock`) is generally faster than TCP/IP for communication between Nginx and PHP-FPM when they are on the same Pod.
  • pm = dynamic: Allows PHP-FPM to scale the number of worker processes based on demand.
  • pm.max_children: This should be carefully tuned. A common heuristic is to set it based on available memory. For example, if each PHP-FPM worker consumes ~30MB of RAM, and you have a 1GB Pod limit, you might set max_children to around 30. However, this is highly application-dependent.
  • pm.start_servers, pm.min_spare_servers, pm.max_spare_servers: These parameters control how PHP-FPM scales dynamically. Tuning these can reduce latency during traffic spikes.
  • pm.max_requests: Limits the number of requests a child process will serve before respawning. This helps prevent memory leaks from accumulating over time.
  • request_terminate_timeout and request_slowlog_timeout: Crucial for identifying and mitigating slow requests.
  • catch_workers_output = yes: Directs worker output (like `echo` statements) to the main error log, simplifying debugging.

Kubernetes Deployment Manifest

Here’s a simplified Kubernetes Deployment manifest for a PHP-FPM application. This assumes you’re using a custom Docker image that includes your application code and the configured PHP-FPM.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-php-app-fpm
  labels:
    app: php-fpm
spec:
  replicas: 3
  selector:
    matchLabels:
      app: php-fpm
  template:
    metadata:
      labels:
        app: php-fpm
    spec:
      containers:
      - name: php-fpm
        image: your-docker-registry/your-php-app:latest
        ports:
        - containerPort: 9000 # Default FPM port if using TCP/IP, or not needed if using socket
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        volumeMounts:
        - name: php-fpm-config-volume
          mountPath: /etc/php/7.4/fpm/pool.d/www.conf
          subPath: www.conf # Mount only the www.conf file
        - name: php-log-volume
          mountPath: /var/log/php
      volumes:
      - name: php-fpm-config-volume
        configMap:
          name: php-fpm-configmap
          items:
          - key: www.conf
            path: www.conf
      - name: php-log-volume
        emptyDir: {} # Temporary storage for logs within the Pod lifecycle

And the corresponding ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: php-fpm-configmap
data:
  www.conf: |
    [global]
    pid = /run/php/php7.4-fpm.pid
    error_log = /var/log/php/php-fpm.log
    daemonize = no

    [www]
    user = www-data
    group = www-data
    listen = /run/php/php7.4-fpm.sock
    listen.owner = www-data
    listen.group = www-data
    listen.mode = 0660

    pm = dynamic
    pm.max_children = 30
    pm.start_servers = 3
    pm.min_spare_servers = 1
    pm.max_spare_servers = 5
    pm.process_idle_timeout = 10s
    pm.max_requests = 500

    request_terminate_timeout = 60s
    request_slowlog_timeout = 10s
    slowlog = /var/log/php/php-fpm.slow.log

    catch_workers_output = yes
    access.log = /var/log/php/access.log
    access.format = "%R - %u %t \"%m %r%Q%qHttp/%H\" %s %O \"%{Referer}i\" \"%{User-Agent}i\""
    clear_env = no

Explanation:

  • The Deployment manages the lifecycle of your PHP-FPM Pods.
  • resources.requests and resources.limits are crucial for Kubernetes to schedule Pods effectively and prevent resource starvation or noisy neighbor issues. Tune these based on your application’s actual resource consumption.
  • We mount the `www.conf` from a ConfigMap. This allows us to update PHP-FPM settings without rebuilding the Docker image.
  • subPath is used to mount only the `www.conf` file, not the entire directory.
  • An emptyDir volume is used for logs. These logs are ephemeral and will be lost when the Pod is deleted, which is typical for stateless applications in Kubernetes. For persistent logging, you’d integrate with a dedicated logging solution (e.g., EFK stack, Loki).

Integrating with Nginx and Horizontal Pod Autoscaler (HPA)

For a complete web application stack, you’ll typically pair your PHP-FPM deployment with an Nginx Ingress Controller or a dedicated Nginx Deployment. Nginx will proxy requests to the PHP-FPM service. To achieve true scalability, you’ll want to configure a Horizontal Pod Autoscaler (HPA) to automatically adjust the number of PHP-FPM replicas based on CPU or memory utilization.

Nginx Configuration Snippet

If you’re running Nginx in a separate Pod (e.g., as part of your application deployment or via an Ingress controller), here’s a relevant Nginx configuration snippet to proxy requests to PHP-FPM using a Unix socket:

server {
    listen 80;
    server_name your-domain.com;
    root /var/www/html; # Your application's public directory
    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock; # Matches the listen directive in www.conf
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    location ~ /\.ht {
        deny all;
    }
}

Horizontal Pod Autoscaler (HPA) Configuration

The HPA will monitor metrics (like CPU utilization) of your PHP-FPM Pods and scale the my-php-app-fpm Deployment up or down. Ensure your Pods have resource requests defined for the HPA to work effectively.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-php-app-fpm-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-php-app-fpm
  minReplicas: 3
  maxReplicas: 15
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70 # Target 70% CPU utilization across all Pods

With this setup, Kubernetes will automatically adjust the number of PHP-FPM replicas between 3 and 15, aiming to keep the average CPU utilization at 70%. When CPU usage exceeds this threshold, new replicas are created; when it drops, replicas are terminated.

Advanced: RoadRunner for High-Performance PHP

While PHP-FPM is robust, it has limitations, particularly regarding request overhead and the lifecycle of PHP processes. For applications demanding extreme performance, lower latency, and better resource utilization, frameworks like RoadRunner offer a compelling alternative. RoadRunner acts as a high-performance PHP application server, managing a pool of PHP workers that are kept alive indefinitely, eliminating the overhead of starting a new PHP process for each request.

RoadRunner Architecture

RoadRunner typically runs as a standalone process (often in a container) that listens for HTTP requests. It then forwards these requests to a pool of long-lived PHP workers. These workers execute your PHP application code. RoadRunner supports various communication protocols, including gRPC and direct TCP/IP, and can be integrated with web servers like Nginx or Caddy.

Kubernetes Deployment with RoadRunner

Deploying RoadRunner on Kubernetes involves a similar pattern to PHP-FPM but with different configurations.

RoadRunner Configuration (`.rr.yaml`)

A basic RoadRunner configuration file:

version: "3"

rpc:
  listen: tcp://127.0.0.1:6001 # RoadRunner's internal RPC port

server:
  command: "php ./bin/rr serve -c .rr.yaml" # Command to start RoadRunner
  relay: "tcp://127.0.0.1:6002" # RoadRunner's relay for worker communication
  # http:
  #   address: ":8080" # If RoadRunner directly serves HTTP

# If using a web server like Nginx or Caddy to proxy to RoadRunner's workers
http:
  address: ":8080" # The port RoadRunner will listen on for HTTP requests
  max_request_size_bytes: 10485760 # 10MB

# Worker configuration
workers:
  # Number of workers to keep alive
  num_workers: 10

  # How workers are managed
  mode: "shared" # or "process"

  # Command to start a PHP worker
  # This command should execute your application's entry point (e.g., a Laravel/Symfony console command)
  # that initializes RoadRunner's worker.
  # For Laravel, this might be `php artisan serve` or a custom script.
  # For Symfony, it might be `php bin/console server:run` or similar.
  # The key is that this command must start a process that listens on the 'relay' address.
  # A common pattern is to use a framework-specific command that integrates with RoadRunner.
  # Example for Laravel with Laravel RoadRunner package:
  command: "php ./vendor/bin/rr serve -c .rr.yaml" # Assuming you have a framework integration

  # The directory where workers will be executed
  directory: "./"

  # Environment variables for workers
  environment:
    APP_ENV: "production"
    APP_DEBUG: "false"
    RR_MODE: "http" # Or "grpc" depending on your needs
    RR_RELAY: "tcp://127.0.0.1:6002"
    RR_RPC_LISTEN: "tcp://127.0.0.1:6001"
    RR_HTTP_LISTEN: ":8080"

# Example for logging
logs:
  mode: "native" # or "json"
  level: "info"
  output: "stderr"
  file: "/var/log/roadrunner.log" # If mode is not stderr

Note: The command for workers is critical. For frameworks like Laravel or Symfony, you’ll typically use a dedicated package (e.g., spiral/roadrunner-laravel) that provides the correct command to initialize the worker and integrate with the framework’s request/response cycle.

Docker Image for RoadRunner

Your Dockerfile would need to install RoadRunner and your application dependencies. A simplified example:

FROM php:8.1-fpm

# Install RoadRunner binary (example for Linux x86_64)
RUN curl -s https://releases.roadrunner.dev/roadrunner -o /usr/local/bin/rr \
    && chmod +x /usr/local/bin/rr

# Install Composer and application dependencies
COPY --chown=www-data:www-data . /var/www/html
WORKDIR /var/www/html
RUN composer install --no-dev --optimize-autoloader

# Copy RoadRunner configuration
COPY .rr.yaml /var/www/html/.rr.yaml

# Expose the port RoadRunner will listen on
EXPOSE 8080

# Set user
USER www-data

# Command to run RoadRunner
CMD ["/usr/local/bin/rr", "serve", "-c", ".rr.yaml"]

Kubernetes Deployment for RoadRunner

The Kubernetes Deployment would be similar to the PHP-FPM one, but the container image would be your RoadRunner-enabled application, and the `CMD` would be set to start RoadRunner.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-php-app-roadrunner
  labels:
    app: php-roadrunner
spec:
  replicas: 3
  selector:
    matchLabels:
      app: php-roadrunner
  template:
    metadata:
      labels:
        app: php-roadrunner
    spec:
      containers:
      - name: php-roadrunner
        image: your-docker-registry/your-php-app-roadrunner:latest
        ports:
        - containerPort: 8080 # The port RoadRunner listens on
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
      # If using Nginx/Caddy as a separate ingress, you'd configure it to proxy to this service.
      # If RoadRunner is handling HTTP directly, you might use a LoadBalancer Service.

Advanced Caching Strategies in Kubernetes

Effective caching is paramount for high-performance PHP applications, especially in a distributed Kubernetes environment. Beyond application-level caching (like object caching), consider infrastructure-level caching.

Redis for Object and Session Caching

Redis is a popular choice for distributed caching. Deploying Redis on Kubernetes can be done using a StatefulSet for persistent data or a simple Deployment for ephemeral cache instances. For production, consider using a managed Redis service or a robust Kubernetes operator.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
  labels:
    app: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:6.2-alpine
        ports:
        - containerPort: 6379
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        # For persistent storage, add volumeMounts and volumes with PersistentVolumeClaim
        # For ephemeral cache, this is sufficient.

---
apiVersion: v1
kind: Service
metadata:
  name: redis-cache-service
spec:
  selector:
    app: redis
  ports:
    - protocol: TCP
      port: 6379
      targetPort: 6379
  type: ClusterIP # Internal service, accessible by other Pods in the cluster

Your PHP application (Laravel, Symfony, etc.) can then be configured to use this Redis instance for object caching and session storage. For Laravel:

// config/cache.php
'stores' => [
    // ... other stores
    'redis' => [
        'driver' => 'redis',
        'connection' => 'cache',
    ],
    // ...
],

// config/session.php
'driver' => env('SESSION_DRIVER', 'file'), // Change to 'redis'
'store' => 'redis', // Use the Redis store defined above
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
'lottery' => [0, 100],
'cookie' => env('SESSION_COOKIE', 'myapp_session'),
'path' => '/',
'domain' => env('SESSION_DOMAIN', null),
'secure' => env('SESSION_SECURE_COOKIE', false),
'http_only' => true,
'same_site' => 'lax',

// config/database.php (for Redis connections)
'redis' => [
    'client' => 'phpredis', // or 'predis'
    'options' => [
        'cluster' => 'redis',
        'parameters' => [
            'password' => env('REDIS_PASSWORD', null),
            'scheme' => 'tcp',
            'host' => env('REDIS_HOST', 'redis-cache-service'), // Kubernetes service name
            'port' => env('REDIS_PORT', 6379),
            'database' => env('REDIS_DB', 0),
        ],
    ],
],

Nginx Caching (Proxy Cache)

For static assets or even dynamic API responses that don’t change frequently, Nginx’s proxy cache can significantly reduce the load on your PHP application and backend services. This is configured within your Nginx deployment or Ingress controller.

http {
    # Define cache path and parameters
    proxy_cache_path /var/cache/nginx/my_cache levels=1:2 keys_zone=my_cache_zone:10m max_size=10g inactive=60m use_temp_path=off;

    server {
        listen 80;
        server_name your-domain.com;
        root /var/www/html;
        index index.php index.html;

        # ... other configurations ...

        location / {
            proxy_pass http://your-php-app-service; # Assuming a Kubernetes Service for your PHP app
            proxy_cache my_cache_zone; # Use the defined cache zone
            proxy_cache_valid 200 302 10m; # Cache successful responses for 10 minutes
            proxy_cache_valid 404 1m; # Cache 404s for 1 minute
            proxy_cache_use_stale error timeout updating http_500; # Serve stale content if backend is down
            add_header X-Cache-Status $upstream_cache_status; # Useful for debugging cache hits/misses
        }

        # ... PHP-FPM or RoadRunner configuration ...
    }
}

This Nginx configuration enables caching for all requests proxied to your PHP application service. The `X-Cache-Status` header is invaluable for debugging cache behavior.

Conclusion

Deploying PHP applications on Kubernetes offers immense scalability and resilience. By carefully configuring PHP-FPM or adopting high-performance solutions like RoadRunner, and by implementing robust caching strategies at both the application and infrastructure levels, you can build highly performant and scalable PHP applications that leverage the full power of Kubernetes.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Leveraging Laravel Forge and AWS Lambda for Serverless PHP 9 Microservices: A Performance and Scalability Deep Dive
  • Kubernetes-Native PHP Deployments: Optimizing Laravel Performance and Scalability with FPM, RoadRunner, and Advanced Caching Strategies
  • Mastering Kubernetes Deployments for Laravel Applications: Advanced Strategies for High Availability and Scalability
  • Orchestrating High-Availability WordPress with Docker Swarm and AWS ECS: A Deep Dive into Scalability and Resilience
  • Leveraging PHP 8.3’s JIT and Vectorization for Extreme WordPress Performance in Headless Architectures

Categories

  • apache (1)
  • AWS (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (88)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (91)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • Performance & Security Optimization (10)
  • PHP (329)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (10)
  • Python (20)
  • Ruby on Rails (1)
  • Security (1)
  • Security & Compliance (650)
  • SEO & Growth (492)
  • Server (118)
  • Softwares (1)
  • Ubuntu (9)
  • Uncategorized (656)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (174)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Leveraging Laravel Forge and AWS Lambda for Serverless PHP 9 Microservices: A Performance and Scalability Deep Dive
  • Kubernetes-Native PHP Deployments: Optimizing Laravel Performance and Scalability with FPM, RoadRunner, and Advanced Caching Strategies
  • Mastering Kubernetes Deployments for Laravel Applications: Advanced Strategies for High Availability and Scalability

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (728)
  • Debugging & Troubleshooting (664)
  • Uncategorized (656)
  • Security & Compliance (650)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala