Problems with Traditional Deployments
KubernetesTraditional DeploymentsVMsDevOpsInfrastructure Beginner 6 min read

Problems with Traditional Deployments

Deep dive into the limitations of physical server, virtual machine, and early deployment strategies that drove the need for container-based orchestration.

03 — Problems with Traditional Deployments

“Those who cannot remember the past are condemned to repeat it.” Understanding how deployments used to work — and why they failed — is the foundation for appreciating modern cloud-native approaches.


📌 Table of Contents


Three Eras of Deployment

timeline title Evolution of Application Deployment 1990s–2000s : Era 1 - Physical Servers : One app per machine : Massive hardware costs : Manual everything 2000s–2010s : Era 2 - Virtual Machines : Multiple VMs per server : Better utilisation : Still heavy OS overhead 2013–Present : Era 3 - Containers : Lightweight & fast : Consistent environments : Leads to Kubernetes

Era 1 — Physical Servers

In the early days, each application was deployed directly onto a dedicated physical (bare-metal) server.

graph TD subgraph "Physical Server A" OS_A["Operating System"] APP_A["🅰️ App A\n(using 10% CPU)"] end subgraph "Physical Server B" OS_B["Operating System"] APP_B["🅱️ App B\n(using 5% CPU)"] end subgraph "Physical Server C" OS_C["Operating System"] APP_C["©️ App C\n(using 8% CPU)"] end COST["💸 3 Servers\n3 OS Licences\n~77% of compute WASTED"] style COST fill:#e74c3c,color:#fff

Problems with Physical Server Deployments

ProblemDescription
Resource wasteEach server runs one app at ~10–20% utilisation
High costSeparate hardware, power, cooling for each app
Slow provisioningWeeks to order, rack, and configure new servers
No isolationOne app crash or bug can affect the whole OS
Scaling is painfulScaling means buying more physical hardware
Dependency conflictsApp A needs Python 2, App B needs Python 3 — impossible on same OS
Hardware failuresSingle point of failure with no easy fallback

Era 2 — Virtual Machines

VMs solved the resource waste problem by running multiple virtual machines on a single physical server using a hypervisor.

graph TD subgraph "One Physical Server" HW["🖥️ Physical Hardware\n(CPU, RAM, Disk)"] HYP["Hypervisor\n(VMware / Hyper-V / KVM)"] subgraph "VM 1" OS1["Guest OS (Linux)"] APP1["App A"] end subgraph "VM 2" OS2["Guest OS (Windows)"] APP2["App B"] end subgraph "VM 3" OS3["Guest OS (Linux)"] APP3["App C"] end HW --> HYP HYP --> VM1 & VM2 & VM3 end style HYP fill:#8e44ad,color:#fff

What VMs Solved ✅

  • Multiple apps on one physical server → better utilisation
  • Isolation between apps (separate Guest OS per VM)
  • Snapshots for backup and recovery
  • Faster provisioning vs bare metal

Problems That VMs Introduced ❌

graph LR subgraph "VM Overhead" V1["VM 1\n─────────\nGuest OS: 1–2 GB RAM\nApp: 200 MB\n─────────\nTotal: ~2 GB"] V2["VM 2\n─────────\nGuest OS: 1–2 GB RAM\nApp: 200 MB\n─────────\nTotal: ~2 GB"] V3["VM 3\n─────────\nGuest OS: 1–2 GB RAM\nApp: 200 MB\n─────────\nTotal: ~2 GB"] end WASTE["❌ 60–80% of RAM used\nby OS copies, not your app!"] style WASTE fill:#e74c3c,color:#fff style V1 fill:#f39c12,color:#fff style V2 fill:#f39c12,color:#fff style V3 fill:#f39c12,color:#fff
ProblemDescription
Heavy OS overheadEach VM carries a full OS (1–4 GB RAM wasted per VM)
Slow boot timeVMs take 1–3 minutes to start
Image bloatVM images are GBs in size; slow to transfer/deploy
Environment driftVMs configured differently in dev vs staging vs prod
Still needs manual opsSomeone must monitor, restart, and scale VMs
Slow CI/CDSpinning up test VMs for each build takes too long

Era 3 — Containers

Containers share the host OS kernel, so they have no Guest OS overhead. They are lightweight, start in milliseconds, and are completely portable.

graph TD subgraph "Container Approach" HW2["🖥️ Physical Hardware"] OS_HOST["Host OS (Linux Kernel)"] CR["Container Runtime (Docker)"] subgraph "Container 1 — 50 MB" C1["App A + Libs"] end subgraph "Container 2 — 80 MB" C2["App B + Libs"] end subgraph "Container 3 — 60 MB" C3["App C + Libs"] end HW2 --> OS_HOST --> CR CR --> C1 & C2 & C3 end style CR fill:#0db7ed,color:#fff style OS_HOST fill:#326ce5,color:#fff

Containers vs VMs — Side by Side

FeatureVirtual MachineContainer
SizeGBs (full OS included)MBs (just app + libs)
Boot time1–3 minutesMilliseconds
OS overheadFull Guest OS per VMShared Host OS kernel
IsolationStrong (hardware-level)Good (process-level)
PortabilityLimitedExcellent
Density10s of VMs per host100s of containers per host

The Deployment Workflow Problem

Even with containers, the deployment pipeline in traditional setups was fragile and manual.

flowchart LR subgraph "Traditional Deploy Process ❌" DEV["👨‍💻 Dev writes code\non laptop"] -->|FTP/SCP| STG["🖥️ Staging Server\n(manually configured)"] STG -->|Manual testing\n& approval| PROD["🏭 Production Server\n(manually SSH'd into)"] PROD -->|Something breaks| FIX["😰 Hotfix at 2 AM\nSSH + manual restart"] end style DEV fill:#3498db,color:#fff style FIX fill:#e74c3c,color:#fff

Issues in Traditional Deployment Pipelines

  • Manual SSH deployments — error-prone, not repeatable
  • Snowflake servers — each server configured slightly differently over time
  • No rollback strategy — reverting a bad deploy requires manual steps
  • Config stored on server — if the server dies, config is lost
  • Works on my machine — code works locally, fails on the server

The “It Works on My Machine” Problem

This is arguably the most famous problem in software deployment history.

sequenceDiagram participant DEV as 👨‍💻 Developer
(macOS, Node 18) participant OPS as 🔧 Ops Team
(Ubuntu, Node 14) participant PROD as 🏭 Production
(CentOS, Node 12) DEV->>OPS: "The app is ready!\nWorks perfectly on my Mac ✅" OPS->>PROD: Deploy to staging PROD-->>OPS: ❌ App crashes on startup OPS->>DEV: "It's broken in staging" DEV->>DEV: "But it works on my machine 🤷" Note over DEV,PROD: Root cause: Different Node.js versions,
OS libraries, and env variables Note over DEV,PROD: Containers SOLVE this by packaging
the exact environment with the app

Resource Waste & Cost Problems

xychart-beta title "Server CPU Utilisation — Traditional vs Containers" x-axis ["Server A", "Server B", "Server C", "Server D", "Server E"] y-axis "CPU Utilisation %" 0 --> 100 bar [15, 22, 10, 18, 12] line [75, 80, 82, 78, 85]

📊 Traditional (bar): Average 15% CPU utilisation — 85% of compute is wasted. 📈 Containers + K8s (line): 75–85% utilisation — resources are efficiently shared.


Scaling Problems

Traditional Scaling (Manual & Slow)

flowchart TD ALERT["📊 Traffic spike detected!\nCPU at 95%"] -->|Page on-call engineer| ONCALL["📟 Engineer wakes up\nat 3 AM"] ONCALL -->|Open cloud console\nmanually| NEWVM["⏳ Provision new VM\n5–15 minutes"] NEWVM -->|Install dependencies\nmanually configure| DEPLOY["Deploy app\nto new VM"] DEPLOY -->|Update load balancer\nmanually| LIVE["✅ Finally scaling\n20–30 min later"] DAMAGE["😤 Users already left\nSLA breached\nRevenue lost"] LIVE -.->|Too late| DAMAGE style ALERT fill:#e74c3c,color:#fff style DAMAGE fill:#c0392b,color:#fff style LIVE fill:#27ae60,color:#fff

Kubernetes Scaling (Automatic & Instant)

flowchart TD ALERT2["📊 CPU at 70%\n(HPA threshold crossed)"] -->|Kubernetes HPA\nautomatically| SCALE["📦 New Pod\nscheduled in <30 sec"] SCALE --> LIVE2["✅ Auto-scaled\nNo human needed"] style ALERT2 fill:#f39c12,color:#fff style LIVE2 fill:#27ae60,color:#fff

Summary Comparison

graph TD subgraph "❌ Traditional Problems" TP1["🐌 Slow provisioning\n(days to weeks)"] TP2["💸 Resource waste\n(10-20% utilisation)"] TP3["💣 Environment inconsistency\n('works on my machine')"] TP4["🔧 Manual scaling\n(slow & error-prone)"] TP5["🚨 Downtime during deployments\n(big-bang releases)"] TP6["🔒 Vendor/hardware lock-in\n(tied to specific machines)"] end subgraph "✅ Kubernetes Solutions" KS1["⚡ Instant container startup\n(milliseconds)"] KS2["📊 Right-sized resource usage\n(bin-packing + autoscale)"] KS3["📦 Containerised environments\n(identical everywhere)"] KS4["🤖 Horizontal Pod Autoscaler\n(automatic, CPU/memory-based)"] KS5["🔄 Rolling updates\n(zero downtime)"] KS6["🌍 Cloud-agnostic\n(run anywhere)"] end TP1 --> KS1 TP2 --> KS2 TP3 --> KS3 TP4 --> KS4 TP5 --> KS5 TP6 --> KS6 style TP1 fill:#e74c3c,color:#fff style TP2 fill:#e74c3c,color:#fff style TP3 fill:#e74c3c,color:#fff style TP4 fill:#e74c3c,color:#fff style TP5 fill:#e74c3c,color:#fff style TP6 fill:#e74c3c,color:#fff style KS1 fill:#27ae60,color:#fff style KS2 fill:#27ae60,color:#fff style KS3 fill:#27ae60,color:#fff style KS4 fill:#27ae60,color:#fff style KS5 fill:#27ae60,color:#fff style KS6 fill:#27ae60,color:#fff

🔗 Further Reading


← Previous: 02 - Why Kubernetes? Next → 04 - Monolithic vs Microservices