1. Understanding the Basics
Virtualization
What it is: Running multiple virtual machines (VMs) on a single physical server, each with its own complete operating system.
Real-life analogy: Like having multiple apartments in one building - each apartment has its own kitchen, bathroom, and utilities.
Containerization
What it is: Running applications in isolated user spaces (containers) that share the host operating system kernel.
Real-life analogy: Like having roommates sharing an apartment - they share the kitchen and utilities but have their own private bedrooms.
2. Setting Up Our PHP Example
Let's create a simple PHP script to test both environments:
<?php
// info.php
echo "PHP Version: " . phpversion() . "\n";
echo "Server Software: " . $_SERVER['SERVER_SOFTWARE'] . "\n";
echo "Hello from " . gethostname() . "\n";
// Test MySQL connection
try {
$pdo = new PDO('mysql:host=db;dbname=test', 'root', 'password');
echo "Database: Connected successfully!\n";
} catch (PDOException $e) {
echo "Database: Connection failed - " . $e->getMessage() . "\n";
}
?>
3. Virtualization with VirtualBox/Vagrant
Step 1: Install Virtualization Tools
# Install VirtualBox and Vagrant
# Download from: https://www.virtualbox.org/ and https://www.vagrantup.com/
Step 2: Create Vagrantfile
# Vagrantfile
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/focal64"
config.vm.network "private_network", ip: "192.168.56.10"
config.vm.synced_folder ".", "/var/www/html"
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y software-properties-common
add-apt-repository -y ppa:ondrej/php
apt-get update
# Install PHP
apt-get install -y php5.6 php5.6-mysql apache2 libapache2-mod-php5.6
# Configure Apache
a2enmod php5.6
systemctl restart apache2
SHELL
end
Step 3: Start the Virtual Machine
vagrant up
vagrant ssh
Step 4: Test PHP
# Inside the VM
cd /var/www/html
echo '<?php phpinfo(); ?>' > info.php
Visit http://192.168.56.10/info.php in your browser.
4. Containerization with Docker
Step 1: Install Docker
# Install Docker Desktop from https://www.docker.com/products/docker-desktop
Step 2: Create Dockerfile for PHP
# Dockerfile
FROM ubuntu:16.04
RUN apt-get update && \
apt-get install -y software-properties-common && \
add-apt-repository -y ppa:ondrej/php && \
apt-get update && \
apt-get install -y \
php5.6 \
php5.6-mysql \
php5.6-cli \
apache2 \
libapache2-mod-php5.6
COPY info.php /var/www/html/
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
EXPOSE 80
CMD ["apache2ctl", "-D", "FOREGROUND"]
Step 3: Build and Run Container
# Build the image
docker build -t php5.6-app .
# Run the container
docker run -d -p 8080:80 --name php-app php5.6-app
# Test
curl http://localhost:8080/info.php
5. Multi-Container App with Docker Compose
docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "8080:80"
volumes:
- ./app:/var/www/html
- ./logs:/var/log/apache2
depends_on:
- db
environment:
- DB_HOST=db
- DB_NAME=test
- DB_USER=root
- DB_PASS=password
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: test
volumes:
- db_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "3306:3306"
phpmyadmin:
image: phpmyadmin/phpmyadmin
environment:
PMA_HOST: db
PMA_PORT: 3306
ports:
- "8081:80"
depends_on:
- db
volumes:
db_data:
Initialize Database
-- init.sql
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (name, email) VALUES
('John Doe', 'john@example.com'),
('Jane Smith', 'jane@example.com');
Updated PHP Script
<?php
// app/index.php
$host = getenv('DB_HOST') ?: 'db';
$dbname = getenv('DB_NAME') ?: 'test';
$user = getenv('DB_USER') ?: 'root';
$pass = getenv('DB_PASS') ?: 'password';
echo "<h1>PHP Containerized App</h1>";
echo "<p>PHP Version: " . phpversion() . "</p>";
echo "<p>Server: " . $_SERVER['SERVER_SOFTWARE'] . "</p>";
echo "<p>Hostname: " . gethostname() . "</p>";
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "<h2>Database Connection: ✅ Successful</h2>";
// Fetch users
$stmt = $pdo->query("SELECT * FROM users");
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "<h3>Users from Database:</h3>";
echo "<ul>";
foreach ($users as $user) {
echo "<li>{$user['name']} - {$user['email']}</li>";
}
echo "</ul>";
} catch (PDOException $e) {
echo "<h2>Database Connection: ❌ Failed</h2>";
echo "<p>Error: " . $e->getMessage() . "</p>";
}
// Log access
error_log("Page accessed from: " . $_SERVER['REMOTE_ADDR']);
?>
Run the Application
# Start all services
docker-compose up -d
# Check status
docker-compose ps
# View logs
docker-compose logs -f
# Test the application
curl http://localhost:8080
6. Kubernetes Orchestration
Step 1: Install Minikube
# Install minikube
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# Start cluster
minikube start
Step 2: Create Kubernetes Manifests
php-deployment.yaml
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: php5.6-app:latest
ports:
- containerPort: 80
env:
- name: DB_HOST
value: "mysql-service"
- name: DB_NAME
value: "test"
- name: DB_USER
value: "root"
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-secret
key: password
volumeMounts:
- name: app-data
mountPath: /var/www/html
volumes:
- name: app-data
configMap:
name: php-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: php-config
data:
index.php: |
<?php
echo "PHP Version: " . phpversion() . "<br>";
echo "Running in Kubernetes Pod: " . gethostname();
?>
mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
- name: MYSQL_DATABASE
value: "test"
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
services.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
password: cGFzc3dvcmQ= # base64 encoded "password"
---
apiVersion: v1
kind: Service
metadata:
name: php-service
spec:
selector:
app: php-app
ports:
- port: 80
targetPort: 80
type: LoadBalancer
---
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: php-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: php-app.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: php-service
port:
number: 80
Step 3: Deploy to Kubernetes
# Apply configurations
kubectl apply -f php-deployment.yaml
kubectl apply -f mysql-deployment.yaml
kubectl apply -f services.yaml
kubectl apply -f ingress.yaml
# Check status
kubectl get pods
kubectl get services
kubectl get deployments
# Access the application
minikube service php-service
7. Logging and Monitoring
Application Logging
<?php
// app/logging.php
function log_message($level, $message, $context = []) {
$timestamp = date('Y-m-d H:i:s');
$log_entry = [
'timestamp' => $timestamp,
'level' => $level,
'message' => $message,
'context' => $context,
'pod' => gethostname()
];
error_log(json_encode($log_entry));
}
// Usage
log_message('INFO', 'Page accessed', [
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT']
]);
?>
Docker Compose with Monitoring Stack
# docker-compose.monitoring.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prom_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
depends_on:
- prometheus
node-exporter:
image: prom/node-exporter
ports:
- "9100:9100"
volumes:
prom_data:
grafana_data:
prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'php-app'
static_configs:
- targets: ['web:80']
metrics_path: /metrics
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
8. Automated Deployment Pipeline
GitHub Actions Workflow
# .github/workflows/deploy.yml
name: Deploy PHP App
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t php5.6-app .
- name: Run tests
run: |
docker run --rm php5.6-app php -v
docker run --rm php5.6-app curl -s http://localhost | grep -q "PHP Version"
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/php5.6-app:latest
- name: Deploy to Kubernetes
run: |
echo "${{ secrets.KUBECONFIG }}" > kubeconfig
export KUBECONFIG=kubeconfig
kubectl set image deployment/php-app php-app=${{ secrets.DOCKERHUB_USERNAME }}/php5.6-app:latest
kubectl rollout status deployment/php-app
Jenkins Pipeline
// Jenkinsfile
pipeline {
agent any
environment {
DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials')
}
stages {
stage('Build') {
steps {
sh 'docker build -t php5.6-app .'
}
}
stage('Test') {
steps {
sh '''
docker run --rm php5.6-app php -v
docker run -d --name test-app -p 8080:80 php5.6-app
sleep 10
curl -s http://localhost:8080 | grep "PHP Version"
docker stop test-app
'''
}
}
stage('Push') {
steps {
sh '''
docker tag php5.6-app myrepo/php5.6-app:latest
docker login -u $DOCKERHUB_CREDENTIALS_USR -p $DOCKERHUB_CREDENTIALS_PSW
docker push myrepo/php5.6-app:latest
'''
}
}
stage('Deploy') {
steps {
sh '''
kubectl set image deployment/php-app php-app=myrepo/php5.6-app:latest
kubectl rollout status deployment/php-app
'''
}
}
}
post {
always {
cleanWs()
}
success {
slackSend channel: '#deployments', message: 'PHP app deployed successfully!'
}
failure {
slackSend channel: '#deployments', message: 'PHP app deployment failed!'
}
}
}
9. Key Differences Summary
| Aspect | Virtualization | Containerization |
|---|---|---|
| Isolation | Full OS level | Process level |
| Startup Time | Minutes | Seconds |
| Performance | Higher overhead | Near-native |
| Image Size | GBs | MBs |
| Resource Usage | High | Efficient |
| Portability | Moderate | Excellent |
10. Best Practices
Security
# Use non-root user
RUN groupadd -r phpuser && useradd -r -g phpuser phpuser
USER phpuser
Optimization
# Multi-stage build
FROM ubuntu:16.04 as builder
# Build steps...
FROM ubuntu:16.04
COPY --from=builder /app /var/www/html
Monitoring
# Health checks
docker exec container_name tail -f /var/log/apache2/access.log
kubectl top pods
This comprehensive tutorial takes you from basic concepts to production-ready deployment pipelines. Start with the virtualization example to understand the fundamentals, then progress through containerization, orchestration, and automation. Each step builds upon the previous one, giving you hands-on experience with modern deployment practices.
Connecting Links
If you found this series helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

If you want more helpful content like this, feel free to follow me:
Top comments (0)