Dockerizing and Orchestrating Legacy PHP Systems on Modern Linode Infrastructure
Assessing Legacy PHP Application Dependencies
Before embarking on containerization, a thorough audit of the legacy PHP application’s dependencies is paramount. This involves identifying not only PHP extensions but also system-level libraries, external services (databases, message queues, caches), and specific file system configurations. For a typical monolithic PHP application, this might include:
- PHP version and required extensions (e.g.,
mysqli,gd,redis,openssl). - Web server configuration (e.g., Apache
.htaccessdirectives, Nginx rewrite rules). - Database connection details and specific SQL features used.
- File permissions and directory structures critical for application operation (e.g., upload directories, cache directories).
- Environment variables or configuration files that dictate application behavior.
- External API integrations and their network accessibility.
Tools like phpinfo(), static analysis tools (e.g., PHPStan, Psalm), and manual code review are essential. Documenting these dependencies forms the basis for constructing a robust Dockerfile.
Crafting the Dockerfile for a Legacy PHP Application
The Dockerfile is the blueprint for your container image. For legacy PHP, we often start with a stable base image and layer on the necessary components. A common approach is to use an official PHP image and then install additional packages and extensions.
Consider an application that requires PHP 7.4, the gd and redis extensions, and runs on Apache. The Dockerfile might look like this:
Example Dockerfile
# Use an official PHP runtime as a parent image
FROM php:7.4-apache
# Set the working directory in the container
WORKDIR /var/www/html
# Install system dependencies required for PHP extensions
RUN apt-get update && apt-get install -y \
libzip-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libssl-dev \
libwebp-dev \
libonig-dev \
unzip \
git \
vim \
cron \
&& rm -rf /var/lib/apt/lists/*
# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
&& docker-php-ext-install gd \
&& docker-php-ext-install zip \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install redis
# Enable Apache rewrite module
RUN a2enmod rewrite
# Copy application code into the container
COPY . /var/www/html
# Ensure correct permissions for web server
RUN chown -R www-data:www-data /var/www/html && chmod -R 755 /var/www/html
# Copy custom Apache configuration if needed
# COPY apache/000-default.conf /etc/apache2/sites-available/000-default.conf
# Copy custom PHP configuration if needed
# COPY php/php.ini /usr/local/etc/php/conf.d/custom.ini
# Expose port 80
EXPOSE 80
# Start Apache in the foreground
CMD ["apache2-foreground"]
Explanation:
FROM php:7.4-apache: Leverages an official PHP image with Apache pre-configured.apt-get install: Installs necessary build dependencies for PHP extensions and other utilities.docker-php-ext-install: A helper script to compile and install PHP extensions.a2enmod rewrite: Enables Apache’s rewrite module, crucial for many PHP frameworks.COPY . /var/www/html: Copies your application code into the web server’s document root.chown/chmod: Sets appropriate ownership and permissions for the web server user (www-data).EXPOSE 80: Informs Docker that the container listens on port 80.CMD ["apache2-foreground"]: The command to run when the container starts, keeping Apache in the foreground so the container doesn’t exit.
Building and Testing the Docker Image
Once the Dockerfile is in place, build the image. Navigate to the directory containing your Dockerfile and application code, then execute:
docker build -t my-legacy-php-app:latest .
This command builds the image and tags it as my-legacy-php-app:latest. After building, run a container to test:
docker run -d -p 8080:80 --name legacy-app-test my-legacy-php-app:latest
Access your application via http://localhost:8080. Check container logs for any errors:
docker logs legacy-app-test
If issues arise, inspect the container’s filesystem or run it interactively for debugging:
docker run -it --rm my-legacy-php-app:latest bash
Orchestrating with Docker Compose on Linode
For production deployments on Linode, Docker Compose simplifies managing multi-container applications. This is especially useful if your legacy PHP app depends on external services like a database or Redis cache.
Example docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: legacy_php_app
ports:
- "80:80"
volumes:
- ./app_data:/var/www/html/uploads # Example persistent volume for uploads
environment:
- DB_HOST=db
- DB_USER=legacyuser
- DB_PASS=securepassword
- DB_NAME=legacydb
- REDIS_HOST=redis
depends_on:
- db
- redis
networks:
- app-network
db:
image: mysql:5.7
container_name: legacy_db
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: legacydb
MYSQL_USER: legacyuser
MYSQL_PASSWORD: securepassword
networks:
- app-network
redis:
image: redis:6.2
container_name: legacy_redis
ports:
- "6379:6379"
networks:
- app-network
volumes:
db_data:
networks:
app-network:
driver: bridge
Explanation:
services: Defines the individual containers (app,db,redis).appservice: Builds the PHP application image from the current directory (.) using the specifiedDockerfile. It maps host port 80 to container port 80, mounts a volume for uploads, sets environment variables for database and Redis connections, and depends on thedbandredisservices.dbservice: Uses an official MySQL 5.7 image, persists data using a named volumedb_data, and configures the database with user credentials.redisservice: Uses an official Redis 6.2 image.volumes: Declares named volumes for persistent data storage.networks: Defines a custom bridge network for inter-container communication.
Deploying to Linode Kubernetes Engine (LKE) or Linode Compute Instances
On Linode, you have two primary orchestration options:
Option 1: Docker Compose on Linode Compute Instances
For simpler deployments or migrating from a single server, you can run Docker Compose directly on a Linode Compute Instance. Provision a Linode instance, install Docker and Docker Compose, then deploy your application.
# On your Linode Compute Instance: sudo apt-get update && sudo apt-get install -y docker.io docker-compose # Clone your application repository git clone <your-repo-url> cd <your-app-directory> # Start the application docker-compose up -d
To manage traffic and expose your application to the internet, consider using Linode’s Load Balancers in front of multiple instances running your Docker Compose setup. For persistent storage, ensure your volumes are configured correctly on the Linode instance or use Linode Block Storage.
Option 2: Linode Kubernetes Engine (LKE)
For more robust, scalable, and resilient deployments, LKE is the recommended path. This involves converting your docker-compose.yml into Kubernetes manifests (Deployments, Services, PersistentVolumeClaims, etc.).
Kubernetes Manifests (Example Snippets)
Deployment for the PHP App:
apiVersion: apps/v1
kind: Deployment
metadata:
name: legacy-php-app-deployment
spec:
replicas: 3 # Scale as needed
selector:
matchLabels:
app: legacy-php-app
template:
metadata:
labels:
app: legacy-php-app
spec:
containers:
- name: legacy-php-app
image: <your-docker-registry>/my-legacy-php-app:latest # Replace with your image
ports:
- containerPort: 80
env:
- name: DB_HOST
value: "legacy-db-service" # Kubernetes Service name for the DB
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: DB_NAME
value: "legacydb"
- name: REDIS_HOST
value: "legacy-redis-service" # Kubernetes Service name for Redis
volumeMounts:
- name: uploads-volume
mountPath: /var/www/html/uploads
volumes:
- name: uploads-volume
persistentVolumeClaim:
claimName: legacy-app-uploads-pvc
Service for the PHP App:
apiVersion: v1
kind: Service
metadata:
name: legacy-php-app-service
spec:
selector:
app: legacy-php-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP # Or LoadBalancer if directly exposing
You would similarly create Deployments and Services for MySQL and Redis, and PersistentVolumeClaims (PVCs) to manage storage. Linode Kubernetes Engine integrates with Linode Block Storage for persistent volumes.
The process involves:
- Pushing your Docker image to a container registry (e.g., Docker Hub, Linode Container Registry).
- Applying your Kubernetes manifests to your LKE cluster using
kubectl apply -f <your-manifest-file.yaml>. - Configuring an LKE Ingress Controller or a Linode Load Balancer to route external traffic to your
legacy-php-app-service.
Monitoring and Maintenance
Once deployed, robust monitoring is crucial. Utilize tools like Prometheus and Grafana (often deployed within Kubernetes) to track application performance, resource utilization, and error rates. For LKE, Linode’s managed Kubernetes service offers integrated monitoring capabilities. Regularly update base images, PHP versions (where feasible), and security patches. Implement a CI/CD pipeline to automate builds, tests, and deployments, ensuring a smooth transition for your legacy system onto modern infrastructure.