PHP – Docker & Kubernetes

Learning Objectives- By the end of this guide, you’ll be able to:
– Containerize a PHP application using Docker.
– Run multi-container applications with Docker Compose.
– Deploy applications to Kubernetes locally.
– Understand best practices for development and production.

Overview
This guide walks a PHP developer through containerizing a PHP app with Docker, running multi-container apps with docker-compose, and deploying to Kubernetes (local minikube / kind) with clear, copy-paste examples. Each section has step-by-step commands and example files you can use immediately.

Note: This guide assumes basic PHP knowledge. Always test in a safe environment before production.

 

1. Why Containers & Why Kubernetes? (Short)

* Docker: Packages your app + runtime + dependencies into an image that runs the same anywhere.
* Kubernetes: Orchestrates many containers across nodes — scaling, self-healing, routing, storage.

Why this matters for PHP devs:
– Reproducible environments (no “works on my machine”).
– Easy to run multiple services (PHP-FPM, Nginx, MySQL, Redis) together.
– Simple local-to-production path: same image runs locally and in production clusters.

 

2. Prerequisites

* Basic command-line familiarity.
* A PHP project (even a simple index.php).
* Git installed.
* Docker Desktop (Windows/Mac) or Docker Engine + docker-compose (Linux).
* For Kubernetes: minikube or kind installed, plus kubectl.

 

Installation Links:
– Docker: [https://docs.docker.com/get-docker/](https://docs.docker.com/get-docker/)
– Minikube: [https://minikube.sigs.k8s.io/docs/start/](https://minikube.sigs.k8s.io/docs/start/)
– Kind: [https://kind.sigs.k8s.io/docs/user/quick-start/](https://kind.sigs.k8s.io/docs/user/quick-start/)
– Kubectl: [https://kubernetes.io/docs/tasks/tools/](https://kubernetes.io/docs/tasks/tools/)

 

3. Docker Basics (Quick Commands)

bash
Check Docker version
docker –version

Run a container interactively (e.g., Alpine Linux shell)
docker run –rm -it alpine sh

List running containers
docker ps

List all containers (including stopped)
docker ps -a

List images
docker images

Stop a container
docker stop <container_id_or_name>

Remove a container
docker rm <container_id_or_name>

Remove an image
docker rmi <image_id_or_name>

Tip: Use docker –help for more commands. Containers are running instances of images.

 

4. Containerizing a Simple PHP App

Assume a minimal app structure:

my-php-app/
├─ public/
│ └─ index.php
├─ composer.json (optional)
└─ Dockerfile

Example index.php (public/index.php):

php
<?php
echo “Hello from PHP in a container!\n”;

 

Example Dockerfile (for quick dev with PHP’s built-in server):

dockerfile
Use PHP CLI image
FROM php:8.2-cli

Set working directory
WORKDIR /app

Copy app files
COPY . /app

Expose port 8000
EXPOSE 8000

Run PHP built-in server
CMD [“php”, “-S”, “0.0.0.0:8000”, “-t”, “public”]

Build and Run:

bash
Build the image
docker build -t my-php-app:local .

Run the container, mapping port 8000
docker run –rm -p 8000:8000 my-php-app:local

Open http://localhost:8000 in your browser

Warning: The built-in server is for dev/testing only. For production-like setups, use PHP-FPM + Nginx (next section).

 

5. Realistic Setup: PHP-FPM + Nginx (Recommended)

We’ll run two containers: one for php-fpm, one for nginx, with code mounted as a volume for live editing.

Files:
– Dockerfile-php (builds PHP-FPM image)
– nginx/default.conf (Nginx config)
– docker-compose.yml
– .dockerignore (optional, to speed up builds)

Dockerfile-php:

dockerfile
FROM php:8.2-fpm

Install common extensions and tools
RUN apt-get update && apt-get install -y \
libzip-dev unzip git && \
docker-php-ext-install pdo pdo_mysql zip

Install Composer (optional, for dependency management)
COPY –from=composer:2 /usr/bin/composer /usr/bin/composer

Set working directory
WORKDIR /var/www/html

Copy composer files and install dependencies (if composer.json exists)
COPY composer.json composer.lock ./
RUN composer install –no-dev –optimize-autoloader

nginx/default.conf:

nginx
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html;

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

location ~ \.php$ {
fastcgi_pass php-fpm:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}

docker-compose.yml:

yaml
version: ‘3.8’
services:
php-fpm:
build:
context: .
dockerfile: Dockerfile-php
volumes:
– ./:/var/www/html
expose:
– 9000
healthcheck:
test: [“CMD”, “php”, “-v”]
interval: 30s
timeout: 10s
retries: 3

nginx:
image: nginx:1.25
depends_on:
php-fpm:
condition: service_healthy Wait for PHP to be healthy
ports:
– “8080:80”
volumes:
– ./:/var/www/html:ro
– ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
healthcheck:
test: [“CMD”, “curl”, “-f”, “http://localhost”]
interval: 30s
timeout: 10s
retries: 3

Run:

bash
Build and start services
docker compose up –build

Open http://localhost:8080

Adding a Database: Extend docker-compose.yml for MySQL:

yaml
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: myapp
MYSQL_USER: user
MYSQL_PASSWORD: password
ports:
– “3306:3306”
volumes:
– db_data:/var/lib/mysql

volumes:
db_data:

Update PHP code to connect: new PDO(‘mysql:host=db;dbname=myapp’, ‘user’, ‘password’);

 

6. Building Production Images (Multi-Stage Builds)

For smaller, secure images, use multi-stage builds to separate build and runtime.

Updated Dockerfile-php (production):

dockerfile
Build stage
FROM php:8.2-cli AS builder
WORKDIR /app
COPY composer.json composer.lock ./
RUN apt-get update && apt-get install -y unzip git && \
curl -sS https://getcomposer.org/installer | php && \
mv composer.phar /usr/local/bin/composer && \
composer install –no-dev –optimize-autoloader

Runtime stage
FROM php:8.2-fpm
RUN apt-get update && apt-get install -y libzip-dev && \
docker-php-ext-install pdo pdo_mysql zip
WORKDIR /var/www/html
COPY –from=builder /app/vendor ./vendor
COPY . .
RUN chown -R www-data:www-data /var/www/html
USER www-data

Security Tips:
– Run as non-root user (USER www-data).
– Use .dockerignore to exclude sensitive files.
– Scan images with tools like Trivy.

 

7. Common Docker Tips for PHP Devs

* Mount code as a volume in dev for live edits.
* Use composer install –no-dev –optimize-autoloader in production.
* Keep Dockerfile layers minimal for faster builds/caching.
* Use .dockerignore to skip vendor, node_modules, .git, .env.

Example .dockerignore:

vendor/
node_modules/
.git/
.env
*.log

 

8. Debugging Inside Containers

* Get a shell: docker exec -it <container_name> bash
* Tail logs: docker logs -f <container_name>
* Run PHP CLI: docker exec -it <php_container> php -v
* Xdebug: Install in PHP image, set xdebug.remote_host=host.docker.internal (Docker Desktop) or use –add-host.

Troubleshooting:
– Port conflicts? Check docker ps and netstat -tlnp.
– Build fails? Use docker build –no-cache.
– Compose issues? Run docker compose logs.

 

9. Pushing Images to Registry

1. Login: docker login (Docker Hub or private).
2. Tag: docker tag my-php-app:local myhub/my-php-app:1.0
3. Push: docker push myhub/my-php-app:1.0

Use tags like staging, prod-2025-11-13.

 

10. Kubernetes Basics (Concepts)

* Pod: Smallest unit (1+ containers sharing network/storage).
* Deployment: Manages pod replicas for updates.
* Service: Stable network endpoint (ClusterIP, NodePort, LoadBalancer).
* ConfigMap/Secret: Inject config/secrets.
* PersistentVolume (PV)/PVC: Persistent storage.
* Ingress: HTTP routing (needs controller).

 

11. Running Kubernetes Locally (Minikube / Kind)

Minikube (single-node cluster):

bash
minikube start –driver=docker
kubectl get nodes

Kind (multi-node in Docker):

bash
kind create cluster –name dev-cluster
kubectl cluster-info –context kind-dev-cluster

Why choose? Minikube for simplicity; Kind for multi-node testing.

 

12. Example: Deploy PHP App to Kubernetes

Build and push your PHP image first (include code in image).

nginx-config.yaml (ConfigMap):

yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
default.conf: |
server {
listen 80;
root /var/www/html/public;
index index.php;
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }
}

deployment.yaml:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: php-nginx
template:
metadata:
labels:
app: php-nginx
spec:
containers:
– name: nginx
image: nginx:1.25
ports:
– containerPort: 80
volumeMounts:
– name: nginx-conf
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
– name: php
image: myhub/my-php:1.0
ports:
– containerPort: 9000
livenessProbe:
httpGet:
path: /
port: 9000
initialDelaySeconds: 30
periodSeconds: 10
volumes:
– name: nginx-conf
configMap:
name: nginx-config

service.yaml:

yaml
apiVersion: v1
kind: Service
metadata:
name: php-nginx-svc
spec:
selector:
app: php-nginx
type: NodePort
ports:
– port: 80
targetPort: 80
nodePort: 30080

ingress.yaml (optional, for external access):

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: php-nginx-ingress
spec:
rules:
– host: myapp.local
http:
paths:
– path: /
pathType: Prefix
backend:
service:
name: php-nginx-svc
port:
number: 80

Apply:

bash
kubectl apply -f nginx-config.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml If using Ingress

View pods
kubectl get pods

Access (Minikube): minikube service php-nginx-svc –url
Or port-forward: kubectl port-forward svc/php-nginx-svc 8080:80

Secrets Example: For DB passwords, use Secrets instead of ConfigMaps.

 

-Thank you, happy coding !!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *