
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Moving from traditional VPS hosting to containers introduces specific challenges when you deploy a PHP service to Kubernetes. Unlike stateless Go or Node.js microservices, PHP applications typically require a two-process architecture (Nginx and PHP-FPM), persistent storage for uploads, and careful environment variable injection. This guide provides the exact configuration patterns I use in production to ensure reliability, security, and performance for Laravel and Symfony workloads on modern clusters.
How do you architect a container to deploy a PHP service to Kubernetes?
The most common failure mode when teams first deploy a PHP service to Kubernetes is treating the application like a single binary. PHP requires a web server (Nginx/Apache) to handle static assets and proxy dynamic requests to the PHP-FPM process manager. In a containerized environment, you have two primary architectural choices: separate containers per pod or a unified supervisor-managed container.
For most teams, especially those transitioning from traditional LEMP stack setups, the unified approach reduces operational complexity. Running Nginx and PHP-FPM in the same pod eliminates network latency between processes and simplifies scaling logic. However, this requires a proper init system or supervisor to manage both processes gracefully.
Multi-stage Dockerfile for production
Avoid using the official php:apache image for production. It lacks optimization and includes unnecessary modules. Instead, use a multi-stage build that compiles only required extensions and configures Nginx as a reverse proxy. This pattern aligns with best practices covered in containerizing Laravel apps.
# Stage 1: Install dependencies
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --ignore-platform-reqs
# Stage 2: Production image
FROM php:8.4-fpm-alpine AS base
RUN apk add --no-cache nginx supervisor \
&& docker-php-ext-install pdo_mysql opcache bcmath \
&& rm -rf /var/cache/apk/*
COPY --from=vendor /app/vendor /var/www/html/vendor
COPY . /var/www/html
COPY docker/nginx.conf /etc/nginx/http.d/default.conf
COPY docker/supervisord.conf /etc/supervisor/conf.d/
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
EXPOSE 8080
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] This Dockerfile produces an immutable artifact. Dependencies are cached in a separate layer, and the final image contains only runtime requirements. Always run composer install without --dev flags to reduce attack surface and image size.
What Kubernetes manifests are needed to deploy a PHP service?
Once your image is built and pushed to a registry, you need three core resources: a Deployment, a Service, and an Ingress. The Deployment manages pod replicas and rollout strategy; the Service provides stable internal DNS; and the Ingress handles external traffic routing with TLS.
Deployment with health checks and resource limits
PHP-FPM workers consume memory proportional to request complexity. Without explicit resource limits and requests, pods will be evicted unpredictably during traffic spikes. Configure liveness probes against a lightweight health endpoint rather than the homepage to avoid false positives during cache warming.
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app
spec:
replicas: 3
selector:
matchLabels:
app: php-app
template:
metadata:
labels:
app: php-app
spec:
containers:
- name: php-app
image: registry.example.com/php-app:v1.2.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
envFrom:
- secretRef:
name: php-app-secrets
volumeMounts:
- name: storage
mountPath: /var/www/html/storage/app/public
volumes:
- name: storage
persistentVolumeClaim:
claimName: php-app-storage Note the separation of liveness and readiness probes. The readiness probe gates traffic during deployments, while the liveness probe restarts hung processes. For Laravel applications, create dedicated routes at /healthz and /readyz that check database connectivity and cache availability without triggering heavy middleware.
Ingress configuration with TLS
External access requires an Ingress resource. Most managed clusters (EKS, GKE, AKS) provide default controllers, but verify compatibility with your chosen Ingress controller. Always enforce HTTPS redirects and specify TLS certificates managed by cert-manager or cloud-native integrations.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: php-app-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- app.example.com
secretName: php-app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: php-app
port:
number: 80 How should secrets and environment variables be managed securely?
Never bake .env files into Docker images or commit them to version control. When you deploy a PHP service to Kubernetes, use native Secrets objects injected as environment variables or mounted files. For SOC 2 or ISO 27001 compliance, integrate with external secret stores like HashiCorp Vault or AWS Secrets Manager via the External Secrets Operator.
Creating and referencing secrets
Store sensitive values like APP_KEY, database passwords, and API tokens in Kubernetes Secrets. Encode values in base64 or use stringData for plaintext input that Kubernetes encodes automatically.
apiVersion: v1
kind: Secret
metadata:
name: php-app-secrets
type: Opaque
stringData:
APP_KEY: "base64:your-app-key-here"
DB_PASSWORD: "super-secure-password"
REDIS_PASSWORD: "redis-auth-token"
MAIL_USERNAME: "[email protected]" Reference these in your Deployment using envFrom for bulk injection or individual env.valueFrom entries for selective mapping. Rotate secrets regularly and audit access logs. For deeper guidance, review Kubernetes secrets management done right.
What are common pitfalls when running PHP on Kubernetes?
PHP's synchronous execution model and reliance on filesystem state create unique challenges in distributed environments. Understanding these failure modes prevents production incidents during peak traffic.
| Pitfall | Symptom | Solution |
|---|---|---|
| Missing persistent storage | Uploaded files disappear after pod restart | Use PVC with ReadWriteMany or object storage (S3) |
| Opcache misconfiguration | Code changes not reflected; high CPU usage | Enable opcache.validate_timestamps=0 in prod; use graceful reload |
| Session stored locally | Users logged out randomly across pods | Configure Redis/database session driver; never use file driver |
| No graceful shutdown | 502 errors during deployments | Add preStop hook; configure PHP-FPM process_control_timeout |
| Insufficient FPM workers | Request queuing; high latency under load | Tune pm.max_children based on memory limits; monitor with Prometheus |
Handling sessions and file uploads
Kubernetes pods are ephemeral. Local filesystem writes for sessions, caches, or user uploads will be lost during scaling events or node drains. Configure your application to use external stores:
- Sessions: Use Redis or database drivers. Never rely on
filesession storage in multi-pod deployments. - Uploads: Offload to S3/GCS/R2 via SDK, or mount a shared PVC using NFS/EFS/Ceph. For Nepal-based deployments with limited bandwidth, consider regional object storage endpoints to reduce latency.
- Caches: Use Redis/Memcached for route, config, and view caches. Avoid file-based caching entirely.
Graceful shutdown and zero-downtime deploys
PHP-FPM does not handle SIGTERM gracefully by default. During rolling updates, in-flight requests may be terminated abruptly, causing 502 errors. Add a preStop hook to allow active connections to complete:
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5; kill -QUIT $(cat /run/php-fpm.pid)"] The sleep allows the load balancer to remove the pod from endpoints before PHP-FPM begins shutting down. Combine this with progressive delivery strategies for critical production services.
Deploy a PHP Service to Kubernetes: Next Steps
Successfully running PHP on Kubernetes requires attention to process management, state externalization, and observability. Start with the unified Nginx+PHP-FPM container pattern, enforce resource boundaries, and validate health probes before promoting to production. Monitor FPM worker saturation and request latency using Prometheus metrics exported via php-fpm-exporter or OpenTelemetry instrumentation.
If your team needs help designing compliant, scalable PHP infrastructure or migrating legacy applications to Kubernetes, reach out for a consultation. I assist organizations in Nepal and globally with audit-ready deployments, performance tuning, and secure CI/CD pipelines tailored to PHP workloads.