Debian Container & Virtualization: Docker, Kubernetes & KVM Guide

Tyler Maginnis | February 07, 2024

DebianDockerKubernetesKVMcontainersvirtualizationorchestration

Need Professional Debian Server Support?

Get expert assistance with your debian server support implementation and management. Tyler on Tech Louisville provides priority support for Louisville businesses.

Same-day service available for Louisville area

Debian Container & Virtualization: Docker, Kubernetes & KVM Guide

Modern infrastructure relies heavily on containerization and virtualization technologies. This comprehensive guide covers setting up Docker containers, Kubernetes orchestration, and KVM virtualization on Debian servers, providing you with the tools to build scalable and efficient infrastructure.

Docker Installation and Configuration

Installing Docker

# Update package index
sudo apt update

# Install prerequisites
sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release

# Add Docker's official GPG key
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# Add Docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin

# Enable and start Docker
sudo systemctl enable docker
sudo systemctl start docker

# Add user to docker group
sudo usermod -aG docker $USER

Docker Configuration

# Configure Docker daemon
sudo nano /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ],
  "dns": ["8.8.8.8", "8.8.4.4"],
  "registry-mirrors": ["https://mirror.gcr.io"],
  "insecure-registries": [],
  "debug": false,
  "experimental": true,
  "features": {
    "buildkit": true
  },
  "metrics-addr": "127.0.0.1:9323",
  "default-runtime": "runc",
  "runtimes": {
    "runc": {
      "path": "runc"
    }
  }
}

Restart Docker:

sudo systemctl restart docker

Docker Security Configuration

# Enable user namespace remapping
sudo nano /etc/docker/daemon.json

Add:

{
  "userns-remap": "default"
}

Create subuid and subgid mappings:

sudo nano /etc/subuid
dockremap:100000:65536

sudo nano /etc/subgid
dockremap:100000:65536

Docker Compose Example

# Create project directory
mkdir -p ~/docker-projects/webapp
cd ~/docker-projects/webapp

# Create docker-compose.yml
nano docker-compose.yml
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    container_name: webapp-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/ssl:/etc/nginx/ssl
      - ./app:/var/www/html
    depends_on:
      - php
    networks:
      - webapp-network
    restart: unless-stopped

  php:
    build:
      context: ./php
      dockerfile: Dockerfile
    container_name: webapp-php
    volumes:
      - ./app:/var/www/html
    environment:
      - PHP_MEMORY_LIMIT=256M
      - PHP_MAX_EXECUTION_TIME=300
    networks:
      - webapp-network
    restart: unless-stopped

  mysql:
    image: mysql:8.0
    container_name: webapp-mysql
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
      - ./mysql/init:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    networks:
      - webapp-network
    restart: unless-stopped

  redis:
    image: redis:alpine
    container_name: webapp-redis
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    command: redis-server --appendonly yes
    networks:
      - webapp-network
    restart: unless-stopped

volumes:
  mysql-data:
  redis-data:

networks:
  webapp-network:
    driver: bridge

Custom PHP Dockerfile

mkdir -p php
nano php/Dockerfile
FROM php:8.2-fpm

# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip \
    libzip-dev \
    libgd-dev \
    libcurl4-openssl-dev

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip curl

# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Set working directory
WORKDIR /var/www/html

# Copy custom PHP configuration
COPY php.ini /usr/local/etc/php/conf.d/custom.ini

# Create non-root user
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www

# Change ownership
RUN chown -R www:www /var/www/html

# Switch to non-root user
USER www

EXPOSE 9000
CMD ["php-fpm"]

Kubernetes Installation and Setup

Installing Kubernetes with kubeadm

# Disable swap
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

# Install prerequisites
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl

# Add Kubernetes repository
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

# Install Kubernetes components
sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

# Enable kernel modules
sudo modprobe br_netfilter
sudo modprobe overlay

# Configure sysctl
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

sudo sysctl --system

Initialize Kubernetes Cluster

# Initialize control plane (master node)
sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.1.100

# Configure kubectl for regular user
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# Install Flannel network plugin
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# For single-node cluster, allow pods on master
kubectl taint nodes --all node-role.kubernetes.io/control-plane-

Join Worker Nodes

On worker nodes:

# Run the join command from master node output
sudo kubeadm join 192.168.1.100:6443 --token <token> --discovery-token-ca-cert-hash <hash>

Deploy Sample Application

# Create namespace
kubectl create namespace webapp

# Create deployment
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
          requests:
            memory: "64Mi"
            cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: webapp
spec:
  selector:
    app: nginx
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
EOF

Kubernetes Dashboard

# Deploy dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml

# Create admin user
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
EOF

# Get token
kubectl -n kubernetes-dashboard create token admin-user

Helm Package Manager

# Install Helm
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt update
sudo apt install helm

# Add repositories
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add stable https://charts.helm.sh/stable
helm repo update

# Install example application
helm install my-wordpress bitnami/wordpress --namespace webapp

KVM Virtualization Setup

Installing KVM

# Check virtualization support
egrep -c '(vmx|svm)' /proc/cpuinfo

# Install KVM and tools
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager

# Add user to groups
sudo usermod -aG libvirt $USER
sudo usermod -aG kvm $USER

# Enable and start libvirtd
sudo systemctl enable libvirtd
sudo systemctl start libvirtd

Network Configuration for KVM

# Create bridge network
sudo nano /etc/network/interfaces
# Physical interface
auto eth0
iface eth0 inet manual

# Bridge interface
auto br0
iface br0 inet static
    address 192.168.1.100
    netmask 255.255.255.0
    gateway 192.168.1.1
    dns-nameservers 8.8.8.8 8.8.4.4
    bridge_ports eth0
    bridge_stp off
    bridge_fd 0
    bridge_maxwait 0

Create Virtual Machine

# Download OS image
wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-11.6.0-amd64-netinst.iso

# Create VM using virt-install
sudo virt-install \
  --name debian-vm \
  --ram 2048 \
  --vcpus 2 \
  --disk path=/var/lib/libvirt/images/debian-vm.qcow2,size=20,format=qcow2 \
  --os-variant debian11 \
  --network bridge=br0 \
  --graphics vnc,listen=0.0.0.0 \
  --noautoconsole \
  --cdrom debian-11.6.0-amd64-netinst.iso

# List VMs
sudo virsh list --all

# Start VM
sudo virsh start debian-vm

# Connect to VM console
sudo virsh console debian-vm

VM Management Scripts

# Create VM backup script
sudo nano /usr/local/bin/vm-backup.sh
#!/bin/bash

# KVM VM Backup Script

BACKUP_DIR="/backup/vms"
DATE=$(date +%Y%m%d_%H%M%S)
VMS=$(virsh list --all --name)

mkdir -p "$BACKUP_DIR"

for VM in $VMS; do
    if [ ! -z "$VM" ]; then
        echo "Backing up VM: $VM"

        # Create snapshot
        virsh snapshot-create-as --domain $VM backup-$DATE --disk-only --atomic

        # Get disk path
        DISK_PATH=$(virsh domblklist $VM | grep vda | awk '{print $2}')

        # Backup disk
        cp "$DISK_PATH" "$BACKUP_DIR/${VM}_${DATE}.qcow2"

        # Remove snapshot
        virsh snapshot-delete $VM backup-$DATE

        # Backup XML configuration
        virsh dumpxml $VM > "$BACKUP_DIR/${VM}_${DATE}.xml"
    fi
done

# Cleanup old backups (keep 7 days)
find "$BACKUP_DIR" -name "*.qcow2" -o -name "*.xml" -mtime +7 -delete

VM Template Creation

# Create template script
sudo nano /usr/local/bin/create-vm-template.sh
#!/bin/bash

# VM Template Creation Script

TEMPLATE_NAME="debian-template"
TEMPLATE_DIR="/var/lib/libvirt/templates"
BASE_IMAGE="/var/lib/libvirt/images/debian-base.qcow2"

mkdir -p "$TEMPLATE_DIR"

# Create base VM
virt-install \
  --name $TEMPLATE_NAME \
  --ram 1024 \
  --vcpus 1 \
  --disk path=$BASE_IMAGE,size=10,format=qcow2 \
  --os-variant debian11 \
  --network bridge=br0 \
  --graphics none \
  --console pty,target_type=serial \
  --location 'http://deb.debian.org/debian/dists/stable/main/installer-amd64/' \
  --extra-args 'console=ttyS0,115200n8 serial'

# Customize template
virt-customize -a $BASE_IMAGE \
  --run-command 'apt-get update' \
  --install cloud-init,qemu-guest-agent \
  --run-command 'systemctl enable qemu-guest-agent' \
  --run-command 'apt-get clean' \
  --run-command 'rm -rf /var/lib/apt/lists/*'

# Sysprep
virt-sysprep -a $BASE_IMAGE \
  --enable ssh-hostkeys,ssh-userdir,machine-id,customize

# Convert to template
qemu-img convert -O qcow2 -c $BASE_IMAGE $TEMPLATE_DIR/$TEMPLATE_NAME.qcow2

echo "Template created: $TEMPLATE_DIR/$TEMPLATE_NAME.qcow2"

Container Orchestration with Docker Swarm

Initialize Docker Swarm

# Initialize swarm on manager node
docker swarm init --advertise-addr 192.168.1.100

# Join worker nodes (use token from init output)
docker swarm join --token <token> 192.168.1.100:2377

# List nodes
docker node ls

# Create overlay network
docker network create --driver overlay --attachable webapp-net

Deploy Stack

# Create stack file
nano docker-stack.yml
version: '3.8'

services:
  web:
    image: nginx:alpine
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
      placement:
        constraints:
          - node.role == worker
    ports:
      - "80:80"
    networks:
      - webapp-net
    volumes:
      - web-content:/usr/share/nginx/html
    configs:
      - source: nginx-config
        target: /etc/nginx/nginx.conf

  visualizer:
    image: dockersamples/visualizer:latest
    ports:
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      placement:
        constraints:
          - node.role == manager
    networks:
      - webapp-net

volumes:
  web-content:
    driver: local

networks:
  webapp-net:
    external: true

configs:
  nginx-config:
    file: ./nginx.conf

Deploy stack:

docker stack deploy -c docker-stack.yml webapp

Container Registry Setup

Deploy Private Docker Registry

# Create registry with authentication
mkdir -p ~/docker-registry/{auth,certs,data}

# Generate certificates
openssl req -newkey rsa:4096 -nodes -sha256 \
  -keyout ~/docker-registry/certs/domain.key \
  -x509 -days 365 \
  -out ~/docker-registry/certs/domain.crt \
  -subj "/CN=registry.example.com"

# Create htpasswd file
docker run --rm --entrypoint htpasswd \
  httpd:2 -Bbn admin password > ~/docker-registry/auth/htpasswd

# Run registry
docker run -d \
  --restart=always \
  --name registry \
  -v ~/docker-registry/auth:/auth \
  -v ~/docker-registry/certs:/certs \
  -v ~/docker-registry/data:/var/lib/registry \
  -e REGISTRY_AUTH=htpasswd \
  -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  -p 5000:5000 \
  registry:2

Configure Docker to Use Registry

# Add certificate to Docker
sudo mkdir -p /etc/docker/certs.d/registry.example.com:5000
sudo cp ~/docker-registry/certs/domain.crt /etc/docker/certs.d/registry.example.com:5000/ca.crt

# Login to registry
docker login registry.example.com:5000

# Tag and push image
docker tag myapp:latest registry.example.com:5000/myapp:latest
docker push registry.example.com:5000/myapp:latest

Monitoring and Management

Container Monitoring with Prometheus

# Create monitoring stack
mkdir -p ~/monitoring
cd ~/monitoring

# Prometheus configuration
nano prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'docker'
    static_configs:
      - targets: ['localhost:9323']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['localhost:9100']

  - job_name: 'cadvisor'
    static_configs:
      - targets: ['localhost:8080']

Docker Compose for monitoring:

version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    ports:
      - "9090:9090"
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    volumes:
      - grafana-data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_USERS_ALLOW_SIGN_UP=false
    ports:
      - "3000:3000"
    restart: unless-stopped

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    ports:
      - "9100:9100"
    restart: unless-stopped

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    ports:
      - "8080:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    restart: unless-stopped

volumes:
  prometheus-data:
  grafana-data:

Backup and Recovery Script

sudo nano /usr/local/bin/container-backup.sh
#!/bin/bash

# Container Backup Script

BACKUP_DIR="/backup/containers"
DATE=$(date +%Y%m%d_%H%M%S)
CONTAINERS=$(docker ps -a --format "{{.Names}}")

mkdir -p "$BACKUP_DIR"

# Backup containers
for CONTAINER in $CONTAINERS; do
    echo "Backing up container: $CONTAINER"

    # Export container
    docker export $CONTAINER | gzip > "$BACKUP_DIR/${CONTAINER}_${DATE}.tar.gz"

    # Backup volumes
    VOLUMES=$(docker inspect $CONTAINER | jq -r '.[0].Mounts[] | select(.Type=="volume") | .Name')
    for VOLUME in $VOLUMES; do
        docker run --rm -v $VOLUME:/data -v $BACKUP_DIR:/backup \
            alpine tar czf /backup/${CONTAINER}_${VOLUME}_${DATE}.tar.gz -C /data .
    done
done

# Backup Docker Compose projects
for PROJECT in ~/docker-projects/*; do
    if [ -f "$PROJECT/docker-compose.yml" ]; then
        PROJECT_NAME=$(basename $PROJECT)
        tar czf "$BACKUP_DIR/compose_${PROJECT_NAME}_${DATE}.tar.gz" -C $PROJECT .
    fi
done

# Cleanup old backups
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete

echo "Container backup completed"

Security Best Practices

Container Security Scanning

# Install Trivy
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt update
sudo apt install trivy

# Scan image
trivy image nginx:latest

# Scan running container
docker export $(docker ps -q -f name=webapp) | trivy image --input -

Security Policies

# Create AppArmor profile for containers
sudo nano /etc/apparmor.d/docker-nginx
#include <tunables/global>

profile docker-nginx flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>

  network inet tcp,
  network inet udp,
  network inet icmp,

  deny network raw,

  deny /proc/sys/kernel/** wklx,
  deny /sys/kernel/** wklx,

  capability chown,
  capability dac_override,
  capability setuid,
  capability setgid,
  capability net_bind_service,

  /usr/sbin/nginx ix,
  /etc/nginx/** r,
  /var/log/nginx/** rw,
  /var/www/** r,
  /run/nginx.pid rw,
}

Load profile:

sudo apparmor_parser -r /etc/apparmor.d/docker-nginx

Kubernetes Security

# Create security policy
kubectl apply -f - <<EOF
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: restricted
spec:
  privileged: false
  allowPrivilegeEscalation: false
  requiredDropCapabilities:
    - ALL
  volumes:
    - 'configMap'
    - 'emptyDir'
    - 'projected'
    - 'secret'
    - 'downwardAPI'
    - 'persistentVolumeClaim'
  runAsUser:
    rule: 'MustRunAsNonRoot'
  seLinux:
    rule: 'RunAsAny'
  fsGroup:
    rule: 'RunAsAny'
  readOnlyRootFilesystem: false
EOF

Performance Optimization

Docker Performance Tuning

# Optimize Docker daemon
sudo nano /etc/docker/daemon.json
{
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true",
    "overlay2.size=10G"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Hard": 64000,
      "Soft": 64000
    }
  },
  "max-concurrent-downloads": 10,
  "max-concurrent-uploads": 10,
  "live-restore": true,
  "userland-proxy": false
}

Resource Limits

# Docker Compose with resource limits
version: '3.8'

services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G
        reservations:
          cpus: '1'
          memory: 1G
    mem_swappiness: 0
    cpu_shares: 1024
    blkio_config:
      weight: 500

Conclusion

Containerization and virtualization technologies provide the foundation for modern, scalable infrastructure. This guide covers the essential aspects of implementing Docker, Kubernetes, and KVM on Debian servers.

Key takeaways: - Start with Docker for simple containerization needs - Use Kubernetes for complex orchestration requirements - Implement KVM for full virtualization when needed - Always prioritize security in container deployments - Monitor resource usage and performance - Implement proper backup and recovery procedures - Keep all components updated

With these technologies properly configured, you can build flexible, efficient, and scalable infrastructure on Debian servers.