DEV Community

Cover image for Virtualisation vs. Containerization: A Complete Guide for Beginners
MD ARIFUL HAQUE
MD ARIFUL HAQUE

Posted on

Virtualisation vs. Containerization: A Complete Guide for Beginners

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";
}
?>
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Step 3: Start the Virtual Machine

vagrant up
vagrant ssh
Enter fullscreen mode Exit fullscreen mode

Step 4: Test PHP

# Inside the VM
cd /var/www/html
echo '<?php phpinfo(); ?>' > info.php
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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']);
?>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
    ?>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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']
]);
?>
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!'
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Optimization

# Multi-stage build
FROM ubuntu:16.04 as builder
# Build steps...

FROM ubuntu:16.04
COPY --from=builder /app /var/www/html
Enter fullscreen mode Exit fullscreen mode

Monitoring

# Health checks
docker exec container_name tail -f /var/log/apache2/access.log
kubectl top pods
Enter fullscreen mode Exit fullscreen mode

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!
Buy Me A Coffee

If you want more helpful content like this, feel free to follow me:

Top comments (0)