Deploy a PHP application on Kubernetes by packaging the code and its PHP dependencies in an immutable image, running that image in a Deployment, and placing a Service in front of the Pods. For the usual PHP-FPM architecture, NGINX handles HTTP and static files while PHP-FPM executes PHP scripts over FastCGI. Add a ConfigMap for ordinary settings, a Secret for credentials, and an Ingress only when you need external HTTP routing or TLS.
Need a server to try this on? Antihost offers Linux VPS plans with NVMe storage, full root access and a 14-day free trial.
This guide shows a production-shaped deployment, explains the alternatives, and gives commands for building, applying, exposing, and troubleshooting the application.
As an Amazon Associate I earn from qualifying purchases.
What the Kubernetes architecture looks like
Kubernetes does not run a PHP source directory by itself. Your application and its runtime dependencies must be packaged into a container image, published to a registry the cluster can reach, and referenced by a workload. A Deployment keeps the desired number of Pods running and performs controlled updates. A Service gives those Pods a stable DNS name and virtual IP even when individual Pods are replaced.
PHP is a runtime, not an HTTP server. PHP-FPM listens for FastCGI requests, conventionally on TCP port 9000. NGINX accepts browser requests, serves static assets, and forwards requests for .php scripts to PHP-FPM. You can run the two processes in one container, in two containers in the same Pod, or in separate Deployments. The examples below use two containers in one Pod because both processes scale together and can communicate over localhost.
#1 Best Overall
- Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
- Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
- Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
- Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
- 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.
Choose an arrangement before writing manifests
| Arrangement | How it works | Advantages | Trade-offs |
|---|---|---|---|
| PHP with Apache | A PHP-Apache image serves HTTP and executes PHP in one container. | Fewest moving parts and a simple Service. | Less separation between web serving and PHP runtime; independent scaling is not possible. |
| NGINX sidecar plus PHP-FPM | NGINX and PHP-FPM share a Pod and communicate through 127.0.0.1:9000. |
Simple local networking and one unit to scale; static and dynamic traffic stay together. | Both containers scale at the same rate, and application files must be available to both. |
| Separate NGINX and PHP Deployments | NGINX forwards FastCGI requests through a Service to PHP-FPM Pods. | Web and PHP capacity can scale independently. | Requires shared application files or carefully built matching images, plus another Service. |
| Ingress directly to PHP-FPM | An ingress-nginx controller sends FastCGI traffic to a PHP-FPM Service. | Can remove a dedicated NGINX web tier for simple cases. | Controller-specific annotations and FastCGI parameter configuration are required; static-file handling needs separate planning. |
For a first deployment, the sidecar pattern is a practical compromise. Move to separate Deployments when web requests and PHP execution have materially different scaling or release requirements.
Prerequisites and application assumptions
- A Kubernetes cluster and a
kubectlcontext with permission to create Deployments, Services, ConfigMaps, Secrets, and Ingress resources. - A container registry accessible from every node. Use a tested, pinned image tag rather than a floating tag such as
latest. - An application that can run non-interactively in a container, with its Composer dependencies and required PHP extensions declared.
- A health endpoint that can return a fast success response without requiring a database write. The examples use
/healthz. - A plan for the database, Redis, user uploads, sessions, backups, and migrations. These are not made durable merely by placing the PHP process in Kubernetes.
Create a working directory for the Dockerfile and Kubernetes manifests. Keep the manifests in version control, and apply them with kubectl apply so changes are reviewable and repeatable.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Step 1: Build an immutable PHP application image
The image should contain the PHP version, extensions, Composer dependencies, and application code needed at runtime. Build it in CI, scan and test it there, then publish the exact tag that the Deployment will use.
Example multi-stage Dockerfile
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --prefer-dist --classmap-authoritative --no-interaction
FROM php:8.3-fpm-alpine
WORKDIR /var/www/html
# Add only the extensions your application actually requires.
RUN docker-php-ext-install pdo pdo_mysql opcache
COPY --from=vendor /app/vendor ./vendor
COPY . .
# Ensure the FPM process stays in the foreground for the container runtime.
CMD ["php-fpm", "-F"]
The PHP version in this example is illustrative; pin the version and extension set that your application has passed in testing. If your framework needs a different database driver, image package, document root, or cache extension, change the image accordingly. Do not copy development secrets into the build context. A .dockerignore should normally exclude local caches, test artifacts, and files containing credentials.
Build, test, and publish
docker build -t registry.example.com/acme/php-app:2026-10-15.1 .
docker run --rm registry.example.com/acme/php-app:2026-10-15.1 php -v
docker push registry.example.com/acme/php-app:2026-10-15.1
Replace the registry hostname and tag with your own immutable release identifier. If the registry is private, configure an image-pull Secret in the namespace and reference it from the Pod specification.
Step 2: Provide NGINX configuration for PHP-FPM
In the sidecar design, both containers mount the same application files. An init container copies the files from the application image into an emptyDir; the PHP-FPM and NGINX containers then mount that directory at /var/www/html. This volume exists only for the lifetime of the Pod, which is acceptable for read-only application code but not for durable uploads.
Rank #2
- 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
- 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
- Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
- 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
- What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.
Create an NGINX configuration as a ConfigMap. The fastcgi_pass target is localhost because both processes are in the same Pod.
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
default.conf: |
server {
listen 8080;
server_name _;
root /var/www/html/public;
index index.php index.html;
location = /healthz {
access_log off;
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ .php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_PROXY "";
fastcgi_pass 127.0.0.1:9000;
}
}
Adjust the document root if your framework uses a different public directory. The health file must exist at public/healthz in the image. For applications that generate the response dynamically instead, point the probe at a route that is safe to call repeatedly.
Step 3: Store ordinary settings and secrets separately
A ConfigMap is intended for non-confidential key-value data such as the application mode, cache host, or feature flags. A Secret is for confidential data such as database passwords, tokens, and private keys. Kubernetes can expose either object as environment variables, command-line arguments, or mounted files.
ConfigMap for non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: php-app-config
data:
APP_ENV: "production"
CACHE_HOST: "redis.default.svc.cluster.local"
LOG_CHANNEL: "stderr"
FEATURE_NEW_CHECKOUT: "false"
Secret for credentials
apiVersion: v1
kind: Secret
metadata:
name: php-app-secrets
type: Opaque
stringData:
DB_HOST: "mysql.default.svc.cluster.local"
DB_DATABASE: "app"
DB_USERNAME: "app_user"
DB_PASSWORD: "replace-before-applying"
APP_KEY: "replace-with-a-random-application-key"
stringData lets kubectl accept plain strings and encode them into the Secret object. It does not make the values safe to commit to a public repository, and base64 encoding is not encryption. Use your organization’s secret-management process, restrict RBAC access, and avoid printing environment variables in logs.
Free tools Windows power users keep installed
One-click scans. No signup required.
Step 4: Create the Deployment
The following Deployment runs an init container, PHP-FPM, and NGINX in each Pod. The PHP container imports the ConfigMap and Secret as environment variables. Resource requests help the scheduler place Pods; limits prevent an individual container from consuming unlimited CPU or memory. Tune the values with application measurements rather than copying them unchanged into every workload.
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app
labels:
app: php-app
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: php-app
template:
metadata:
labels:
app: php-app
spec:
# Uncomment and set this when the registry is private.
# imagePullSecrets:
# - name: registry-credentials
initContainers:
- name: copy-application
image: registry.example.com/acme/php-app:2026-10-15.1
command: ["sh", "-c"]
args: ["cp -a /var/www/html/. /workdir/"]
volumeMounts:
- name: app-files
mountPath: /workdir
containers:
- name: php-fpm
image: registry.example.com/acme/php-app:2026-10-15.1
imagePullPolicy: IfNotPresent
ports:
- name: fpm
containerPort: 9000
envFrom:
- configMapRef:
name: php-app-config
- secretRef:
name: php-app-secrets
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
readinessProbe:
tcpSocket:
port: fpm
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: fpm
initialDelaySeconds: 15
periodSeconds: 20
volumeMounts:
- name: app-files
mountPath: /var/www/html
- name: nginx
image: nginx:1.27-alpine
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "250m"
memory: "256Mi"
volumeMounts:
- name: app-files
mountPath: /var/www/html
readOnly: true
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
volumes:
- name: app-files
emptyDir: {}
- name: nginx-config
configMap:
name: nginx-config
The Pod labels and Deployment selector must match exactly. If you change one, change the other. The emptyDir is deleted when the Pod is deleted or moved, so it is suitable here only because the image is the source of truth for application code.
Rank #3
- Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
- Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
- Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
- Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
- What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.
Step 5: Add a Service for stable internal access
Create a ClusterIP Service that selects the NGINX container’s HTTP port. The Service does not select containers by name; it selects Pods by labels. A selector typo produces a Service with no endpoints.
apiVersion: v1
kind: Service
metadata:
name: php-app
labels:
app: php-app
spec:
type: ClusterIP
selector:
app: php-app
ports:
- name: http
port: 80
targetPort: http
Use a ClusterIP Service for internal traffic. If your platform provides a cloud load balancer and you need direct external access without host/path routing, change the type to LoadBalancer. For multiple hostnames, URL paths, and TLS termination, use an Ingress instead.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteStep 6: Expose HTTP and HTTPS with an Ingress
An Ingress resource is only a set of routing rules; an Ingress controller must be installed to implement them. The following example assumes a controller whose class is named nginx. Create the TLS Secret in the same namespace before applying the Ingress, or omit the TLS section while testing.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: php-app
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "20m"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-example-com-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: php-app
port:
number: 80
Set the DNS record for app.example.com to the address published by the controller or its external load balancer. The TLS Secret must contain the certificate and private key for that hostname. Certificate renewal is an operational responsibility of your chosen certificate workflow.
When ingress-nginx sends traffic directly to PHP-FPM
Direct FastCGI routing is a different design from the NGINX sidecar above. The backend Service must target port 9000, and ingress-nginx needs controller-specific settings. The essential annotation is:
nginx.ingress.kubernetes.io/backend-protocol: "FCGI"
Set nginx.ingress.kubernetes.io/fastcgi-index: "index.php" and provide the FastCGI parameter ConfigMap expected by your ingress-nginx version and configuration. The parameter set must produce the correct script filename and document root for the PHP image. Create that ConfigMap before the Ingress object. This approach can work for a narrowly scoped application, but it does not automatically serve static files; a separate static web tier or suitable controller configuration is still needed.
Recommended Free Tools
Rank #4
- Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
- Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
- Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
- Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
- Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft
Step 7: Apply resources in a controlled order
Save the ConfigMaps, Secret, Deployment, Service, and Ingress as reviewed YAML files. Applying the configuration in one directory is repeatable, but creating dependencies first makes initial failures easier to interpret.
- Apply application configuration, the Secret, and the NGINX configuration:
kubectl apply -f configmap-app.yaml -f secret-app.yaml -f configmap-nginx.yaml - Apply the Deployment and wait for its rollout:
kubectl apply -f deployment.yaml kubectl rollout status deployment/php-app - Create the Service and inspect its endpoints:
kubectl apply -f service.yaml kubectl get service php-app kubectl get endpointslice -l kubernetes.io/service-name=php-app - Apply the Ingress after the Service has ready endpoints:
kubectl apply -f ingress.yaml kubectl get ingress php-app - Test from inside the cluster before debugging DNS or a public load balancer:
kubectl run curl --rm -it --image=curlimages/curl --restart=Never -- curl -i http://php-app/healthz - Then test the public hostname and TLS:
curl -i https://app.example.com/healthz
A successful rollout means the desired Pods became ready; it does not prove that every route, database query, upload, or background job works. Exercise a representative authenticated request and inspect application logs after the health check passes.
Configuration patterns that survive Pod replacement
Environment variables
Environment variables are convenient for framework settings and simple scalar values. They are injected when the container starts, so changing a ConfigMap or Secret does not automatically restart existing Pods. Trigger a rollout after a configuration change, or use a deliberate checksum annotation pattern in your deployment tooling.
Mounted files
Some libraries expect a certificate, JSON credential, or INI file on disk. Mount the Secret or ConfigMap as a volume and point the application at that path. Treat the mount as read-only unless the application explicitly needs to write a generated file. Never use a writable container layer as the only copy of a private key or user data.
Database migrations
Run migrations as an explicit release step or a controlled Kubernetes Job. Do not let every replica race to migrate the same database during startup. Ensure the application version is compatible with the schema during a rolling update.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Stateful dependencies, uploads, and sessions
Keep a database and Redis as separately designed components. They may be managed services or in-cluster stateful workloads, but either choice requires a backup, restore, upgrade, and failure plan. Connect to them through Kubernetes Services and place their hostnames in configuration.
Best Value
- 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
- Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
- Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
- HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
- What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.
User uploads, generated reports, and other durable files must not live only in the PHP container’s writable layer or in the sidecar’s emptyDir. Use object storage or a PersistentVolume with a storage class appropriate to the workload. Verify that the application can tolerate a Pod moving to another node. Sessions should use a shared, durable session store when more than one replica can receive requests; local in-process sessions cause users to appear logged out when traffic changes Pods.
The PHP front end and Redis example in Kubernetes’ stateless-application material illustrates the important boundary: the web workload is replicated, while the stateful dependency is managed as its own component.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Scaling and safe updates
Replicas and readiness
Increase spec.replicas only after the application is safe to run concurrently. A readiness probe removes an unready Pod from Service endpoints; it should test whether the Pod can serve traffic, not merely whether a process exists. Keep liveness checks less aggressive than readiness checks so a slow dependency does not cause restart loops.
Rolling releases
Use a new immutable image tag for each release. Kubernetes creates replacement Pods while retaining availability according to the Deployment strategy. Watch the rollout and inspect the previous ReplicaSet if the new Pods fail. Roll back a bad image with:
kubectl rollout undo deployment/php-app
kubectl rollout status deployment/php-app
Rolling updates do not roll back an incompatible database migration or undo files already written to external storage. Coordinate schema and data changes separately.
Resource and capacity planning
Requests influence scheduling and limits cap consumption. If requests are too low, Pods may be packed onto a node and contend for CPU or memory. If limits are too low, PHP workers can be terminated during legitimate traffic spikes. Observe real usage and adjust both containers independently; NGINX and PHP-FPM have different resource profiles.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Quick Recap
Service-only exposure versus Ingress
| Need | Use | Reason |
|---|---|---|
| Only workloads inside the cluster | ClusterIP Service | Provides stable DNS and load balancing without public exposure. |
| One public endpoint supplied by a cloud provider | LoadBalancer Service | Delegates external address provisioning to the platform. |
| Host or path routing, shared entry point, and TLS | Ingress plus an Ingress controller | Centralizes HTTP routing and certificate termination. |
| FastCGI directly from ingress-nginx | PHP-FPM Service plus FCGI annotations and parameters | Removes the NGINX sidecar but adds controller-specific configuration and static-file concerns. |
Troubleshooting by symptom
| Symptom | Checks | Likely correction |
|---|---|---|
| Deployment never becomes ready | kubectl describe pod, container logs, and probe events. |
Fix the image command, missing extension, bad health path, or insufficient resources. |
| Service has no endpoints | Compare Service selector with Pod labels; inspect EndpointSlices. |
Correct the labels or wait for readiness probes to pass. |
| NGINX returns 502 Bad Gateway | Check PHP-FPM logs, port 9000, and the fastcgi_pass target. |
Ensure FPM is listening in the foreground and that both containers share the expected network and files. |
| NGINX returns 404 for every PHP route | Inspect root, try_files, and SCRIPT_FILENAME. |
Align the document root with the image layout and ensure the script exists in the shared volume. |
| Ingress returns its default backend or 404 | Check ingressClassName, host header, controller events, and Service name/port. |
Use the installed controller class, a matching DNS hostname, and a valid backend Service. |
| Ingress returns 503 | Inspect Service endpoints and Pod readiness. | Resolve selector or probe failures before debugging the external load balancer. |
| Environment changes are ignored | Check the Pod’s environment and its creation timestamp. | Restart or roll out the Deployment after changing a ConfigMap or Secret. |
| Uploads disappear after a rollout | Check whether files were written to emptyDir or the container layer. |
Move durable files to object storage or a properly designed PersistentVolume. |
| Only some requests lose sessions | Check replica count and session storage. | Use a shared session backend rather than per-Pod local sessions. |
A repeatable deployment checklist
- Image tag is pinned, published, pullable by the cluster, and tested with the required PHP extensions.
- Application files are present at the same path expected by PHP-FPM and NGINX.
- Deployment labels and Service selectors match exactly.
- Readiness and liveness probes test the correct ports and paths.
- ConfigMap contains no passwords; Secret values are not committed to source control.
- Database, Redis, sessions, uploads, backups, and migrations have separate designs.
- Service endpoints are populated before exposing the Ingress.
- Ingress class, host, TLS Secret, and controller-specific annotations match the installed controller.
- Rollout status, logs, EndpointSlices, and an in-cluster health request have all been checked.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




