Kubernetes-Native PHP 8+ Deployments: Orchestrating Microservices with Laravel Octane and Docker Swarm
Architecting for Scale: Kubernetes-Native PHP 8+ with Laravel Octane and Docker Swarm
This post details a robust, Kubernetes-native deployment strategy for PHP 8+ microservices, leveraging Laravel Octane for high-performance applications and Docker Swarm for orchestration. We’ll focus on practical implementation, from containerization to service discovery and scaling, providing actionable code and configuration examples suitable for production environments.
Containerizing Laravel Octane Applications with Docker
The foundation of our Kubernetes-native approach is efficient containerization. For Laravel Octane applications, this means optimizing the Dockerfile to ensure fast builds and lean images. We’ll use a multi-stage build to separate build dependencies from the runtime environment.
Consider a typical Laravel Octane application. The core components are PHP, Composer dependencies, and the application code itself. For Octane, we’ll utilize Swoole or RoadRunner as the application server, which requires specific PHP extensions.
Dockerfile for Laravel Octane (Swoole)
# Stage 1: Build dependencies
FROM php:8.2-fpm AS builder
# Install necessary extensions for Swoole and common PHP needs
RUN apt-get update && apt-get install -y \
git \
unzip \
libzip-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libssl-dev \
libonig-dev \
libxml2-dev \
zlib1g-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd zip pdo pdo_mysql mbstring exif pcntl sockets \
&& pecl install swoole \
&& docker-php-ext-enable swoole
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /app
# Copy composer.json and composer.lock
COPY composer.json composer.lock ./
# Install dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Stage 2: Production image
FROM php:8.2-fpm-alpine
# Install necessary extensions for Swoole and common PHP needs (Alpine variant)
RUN apk update && apk add --no-cache \
git \
unzip \
libzip-dev \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
openssl-dev \
oniguruma-dev \
libxml2-dev \
zlib-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd zip pdo pdo_mysql mbstring exif pcntl sockets \
&& pecl install swoole \
&& docker-php-ext-enable swoole
# Copy application code from builder stage
COPY --from=builder /app /app
# Copy optimized vendor directory
COPY --from=builder /app/vendor /app/vendor
# Copy .env.example and create .env
COPY .env.example .env
# Expose port for Swoole
EXPOSE 8000
# Set entrypoint to run Octane
CMD ["php", "artisan", "octane:start", "--host=0.0.0.0", "--port=8000"]
This Dockerfile ensures that only necessary production dependencies are included in the final image, leading to smaller image sizes and reduced attack surface. The multi-stage build also speeds up subsequent builds by caching the Composer dependencies.
Orchestration with Docker Swarm
While Kubernetes is the ultimate goal, Docker Swarm offers a simpler, yet powerful, orchestration layer for managing containerized applications. It’s particularly useful for teams transitioning to cloud-native architectures or for simpler microservice deployments.
Docker Compose for Swarm Services
We’ll define our microservices using a docker-compose.yml file, which Swarm can directly interpret. This file will specify the services, their configurations, networks, and volumes.
version: '3.8'
services:
app.users:
image: your-dockerhub-username/laravel-octane-users:latest
deploy:
replicas: 3
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
ports:
- "8000:8000"
networks:
- app-network
environment:
- DB_HOST=database
- DB_PORT=3306
- DB_DATABASE=users_db
- DB_USERNAME=user
- DB_PASSWORD=password
- APP_ENV=production
- APP_KEY=${APP_KEY} # Assuming APP_KEY is set in .env on manager node
volumes:
- app_users_data:/app/storage
app.products:
image: your-dockerhub-username/laravel-octane-products:latest
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 5s
restart_policy:
condition: on-failure
ports:
- "8001:8000"
networks:
- app-network
environment:
- DB_HOST=database
- DB_PORT=3306
- DB_DATABASE=products_db
- DB_USERNAME=user
- DB_PASSWORD=password
- APP_ENV=production
- APP_KEY=${APP_KEY}
volumes:
- app_products_data:/app/storage
database:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: users_db
MYSQL_USER: user
MYSQL_PASSWORD: password
volumes:
- db_data:/var/lib/mysql
networks:
- app-network
# Add other microservices and their respective databases here
networks:
app-network:
driver: overlay
volumes:
app_users_data:
app_products_data:
db_data:
To deploy this stack to a Docker Swarm cluster:
# Ensure you have a Swarm cluster initialized # docker swarm init --advertise-addr# Deploy the stack docker stack deploy -c docker-compose.yml my_app_stack
Service Discovery and Load Balancing with Traefik
For robust service discovery and load balancing in a Swarm environment, Traefik is an excellent choice. It integrates seamlessly with Docker and can automatically discover services and configure routing rules.
Traefik Configuration for Docker Swarm
# docker-compose.yml for Traefik
version: '3.8'
services:
traefik:
image: traefik:v2.9 # Use a specific, stable version
command:
- --api.insecure=true # For development/testing, use proper auth in production
- --providers.docker=true
- --providers.docker.swarmmode=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443 # If using TLS
ports:
- "80:80"
- "8080:8080" # Traefik dashboard
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- app-network
deploy:
placement:
constraints:
- node.role == manager # Run Traefik on manager nodes for simplicity, or use dedicated nodes
networks:
app-network:
external: true # Use the network defined in your application stack
To integrate Traefik with your application services, you need to add labels to each service in your docker-compose.yml. These labels tell Traefik how to route traffic.
# Example of adding labels to app.users service
services:
app.users:
image: your-dockerhub-username/laravel-octane-users:latest
deploy:
replicas: 3
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
ports:
- "8000:8000" # This port is for direct access if needed, Traefik will use the internal port
networks:
- app-network
environment:
- DB_HOST=database
- DB_PORT=3306
- DB_DATABASE=users_db
- DB_USERNAME=user
- DB_PASSWORD=password
- APP_ENV=production
- APP_KEY=${APP_KEY}
volumes:
- app_users_data:/app/storage
labels:
- "traefik.enable=true"
- "traefik.http.routers.users.rule=Host(`users.yourdomain.com`)" # Your domain for this service
- "traefik.http.routers.users.entrypoints=web"
- "traefik.http.services.users.loadbalancer.server.port=8000" # The port Octane is listening on inside the container
# ... other services with similar labels ...
After deploying Traefik and updating your application services with labels, you can access your microservices via the domain names specified in the `Host` rule. Traefik will automatically handle load balancing across the replicas of each service.
Transitioning to Kubernetes: Key Considerations
While Docker Swarm provides a solid orchestration foundation, Kubernetes offers a more mature and feature-rich ecosystem for managing complex, large-scale deployments. The transition involves mapping Swarm concepts to Kubernetes primitives.
Mapping Swarm to Kubernetes
- Docker Compose Stack maps to a Kubernetes Deployment (for stateless applications) or StatefulSet (for stateful applications like databases).
- Docker Services map to Kubernetes Pods managed by Deployments/StatefulSets.
- Docker Networks map to Kubernetes Networks (often managed by CNI plugins like Calico or Flannel).
- Docker Swarm Load Balancer maps to Kubernetes Services (e.g., ClusterIP, NodePort, LoadBalancer) and potentially Ingress Controllers (like Traefik, Nginx Ingress).
- Docker Volumes map to Kubernetes Persistent Volumes (PVs) and Persistent Volume Claims (PVCs).
- Docker Secrets map to Kubernetes Secrets.
The core challenge in migrating from Swarm to Kubernetes lies in understanding and configuring these Kubernetes resources. Tools like Kompose can assist in converting Docker Compose files to Kubernetes manifests, but manual refinement is often necessary for production readiness.
Kubernetes Deployment Manifest Example
# deployment-users.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: users-deployment
labels:
app: users
spec:
replicas: 3
selector:
matchLabels:
app: users
template:
metadata:
labels:
app: users
spec:
containers:
- name: users
image: your-dockerhub-username/laravel-octane-users:latest
ports:
- containerPort: 8000
env:
- name: DB_HOST
value: "mysql-users" # Kubernetes Service name for the database
- name: DB_PORT
value: "3306"
- name: DB_DATABASE
value: "users_db"
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: mysql-secrets
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secrets
key: password
- name: APP_ENV
value: "production"
- name: APP_KEY
valueFrom:
secretKeyRef:
name: app-secrets
key: app_key
volumeMounts:
- name: storage
mountPath: /app/storage
volumes:
- name: storage
persistentVolumeClaim:
claimName: users-storage-pvc
---
# service-users.yaml
apiVersion: v1
kind: Service
metadata:
name: users-service
spec:
selector:
app: users
ports:
- protocol: TCP
port: 80
targetPort: 8000 # The port Octane is listening on
type: ClusterIP # Internal service, exposed via Ingress
---
# ingress-users.yaml (using Nginx Ingress Controller as an example)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: users-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # Adjust as needed
spec:
rules:
- host: users.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: users-service
port:
number: 80
This Kubernetes manifest defines a Deployment for the users microservice, a ClusterIP Service for internal access, and an Ingress resource to expose it externally. Similar manifests would be created for other microservices and their respective databases.
Advanced Considerations: Observability and CI/CD
A production-ready Kubernetes-native deployment requires robust observability and a streamlined CI/CD pipeline. For PHP applications, this includes:
Observability Stack
- Logging: Centralized logging using EFK (Elasticsearch, Fluentd, Kibana) or Loki/Promtail/Grafana. Ensure your Octane application logs to stdout/stderr for easy collection.
- Metrics: Prometheus for metrics collection, with exporters for PHP (e.g., using OpenTelemetry or custom exporters) and the application server (Swoole/RoadRunner). Grafana for visualization.
- Tracing: OpenTelemetry with Jaeger or Zipkin for distributed tracing across microservices. Integrate tracing into your Laravel application using appropriate packages.
CI/CD Pipeline
Automate your build, test, and deployment process using tools like GitLab CI, GitHub Actions, or Jenkins. A typical pipeline would:
# Example GitHub Actions workflow snippet
name: CI/CD Pipeline
on:
push:
branches: [ main ]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress --no-suggest
- name: Build Docker image
run: docker build -t your-dockerhub-username/laravel-octane-users:${{ github.sha }} .
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Push Docker image
run: docker push your-dockerhub-username/laravel-octane-users:${{ github.sha }}
deploy-swarm: # Example for Swarm
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Deploy to Swarm
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
APP_KEY: ${{ secrets.APP_KEY }} # Ensure this is set in Swarm secrets
run: |
echo "Deploying version ${{ github.sha }}"
# Commands to update the stack with the new image tag
# e.g., docker stack deploy -c docker-compose.yml my_app_stack
deploy-kubernetes: # Example for Kubernetes
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Set up Kubernetes config
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Update Kubernetes Deployment
run: |
kubectl set image deployment/users-deployment users=your-dockerhub-username/laravel-octane-users:${{ github.sha }} -n default
# Apply other deployment updates as needed
Implementing these practices ensures that your PHP microservices are not only performant and scalable but also maintainable and observable in a production Kubernetes environment.