Skip to content

How We Got Here: From Bare Metal to Serverless

Arc: Infrastructure Eras covered: 6 Timeline: ~2000-2025 Read time: ~12 min


The Original Problem

In 2000, if you needed a new server, you wrote a purchase order. Then you waited four to eight weeks for a Dell PowerEdge to arrive on a pallet. A technician racked it, cabled it, installed an OS from a CD, and handed you an IP address. If you needed more capacity for a product launch next month, you prayed that the PO was approved in time. If the launch flopped, you owned the hardware for three to five years anyway.

Capacity planning was a guessing game played with spreadsheets and fear. Over-provision and you waste budget. Under-provision and the site goes down on launch day. Every server was a pet with a name, a story, and irreplaceable local state.


Era 1: Colocation and Bare Metal (~2000-2006)

The Solution

Companies rented cage space in colocation facilities rather than running their own data centers. You shipped your servers to Equinix or Rackspace, they provided power, cooling, and network uplinks. Managed hosting providers like Rackspace and SoftLayer would also rent you dedicated servers by the month.

What It Looked Like

# Your "infrastructure provisioning" workflow
1. Call sales rep at hosting provider
2. Sign 12-month contract for 4x Dell 2950s
3. Wait 3-5 business days for provisioning
4. SSH in, discover it's running RHEL 4
5. Spend a day configuring it by hand
6. rsync your app from the staging server

Why It Was Better

  • No upfront capital expenditure for a data center
  • Someone else handled physical security and power redundancy
  • You could get a server in days instead of weeks
  • Bandwidth was pooled and cheaper than running your own circuit

Why It Wasn't Enough

  • Still locked into monthly or annual contracts
  • Scaling up meant calling your account manager
  • Scaling down meant paying for idle hardware
  • Hardware failures were your problem to diagnose remotely
  • Each server was still a unique snowflake

Legacy You'll Still See

Bare metal hosting is alive and well. Companies like Hetzner and OVH offer dedicated servers at prices that embarrass cloud VMs for steady-state workloads. High-frequency trading, game servers, and ML training clusters still run on bare metal. If you join a company with "on-prem" infrastructure, this is the world you're stepping into.


Era 2: Virtual Machines and VMware (~2003-2010)

The Solution

VMware ESX (2001) and later ESXi made it practical to run multiple operating systems on a single physical server. Suddenly one $10,000 server could host ten virtual machines. Xen (2003) brought open-source virtualization, and KVM (2007) baked it into the Linux kernel itself.

What It Looked Like

# vSphere CLI to clone a VM template
vmware-cmd /vmfs/volumes/datastore1/template-centos/template-centos.vmx clone \
  /vmfs/volumes/datastore1/web-prod-03/web-prod-03.vmx

# Or the vCenter GUI: right-click template → "Deploy from template"
# Fill in name, datastore, network, resource pool
# Wait 5-15 minutes for clone + boot

Why It Was Better

  • Server utilization jumped from 10-15% to 60-80%
  • Provisioning dropped from weeks to minutes
  • VM snapshots enabled crude but useful rollback
  • Hardware independence — move VMs between physical hosts
  • VMotion let you evacuate a host for maintenance without downtime

Why It Wasn't Enough

  • VMware licensing was expensive and opaque
  • vCenter became a critical single point of failure
  • VM sprawl replaced server sprawl — now you had 500 VMs nobody tracked
  • VMs were still pets — people SSH'd in and made manual changes
  • Storage (SAN/NAS) became the new bottleneck and budget sinkhole

Legacy You'll Still See

VMware is still the dominant hypervisor in enterprise data centers. vSphere, vCenter, and vSAN run huge swaths of the Fortune 500. If you work in an enterprise with "private cloud," you are almost certainly interacting with VMware. The vSphere client is still the daily tool for many infrastructure teams.


Era 3: Public Cloud IaaS (~2006-2013)

The Solution

Amazon launched EC2 in August 2006 with a single instance type (m1.small) in a single region (us-east-1). For the first time, anyone with a credit card could provision a server via an API call and pay by the hour. Google App Engine (2008) and Microsoft Azure (2010) followed.

What It Looked Like

# 2008-era EC2 provisioning
ec2-run-instances ami-2bb65342 \
  --instance-type m1.small \
  --key my-keypair \
  --group web-servers

# Wait 2-3 minutes, get an IP
# SSH in and start configuring
ssh -i my-keypair.pem root@ec2-75-101-214-185.compute-1.amazonaws.com

Why It Was Better

  • Zero upfront commitment — pay only for what you use
  • Provision in minutes, terminate when done
  • API-driven — scriptable from day one
  • Geographic distribution became possible for small teams
  • Elastic — add capacity for Black Friday, remove it Tuesday

Why It Wasn't Enough

  • Early cloud was unreliable (us-east-1 outages became legendary)
  • "Lift and shift" meant cloud bills often exceeded colo costs
  • No good way to manage hundreds of instances consistently
  • Vendor lock-in concerns were immediate and real
  • Network performance was unpredictable (noisy neighbors)

Legacy You'll Still See

EC2 instances are still the backbone of most cloud architectures. The mental model of "launch a VM, SSH in, configure it" persists even when better patterns exist. Many companies are still mid-migration from colo to cloud. The "just launch an EC2 instance" reflex is the default solution for many engineers.


Era 4: Containers and Orchestration (~2013-2018)

The Solution

Docker (2013) made Linux containers accessible to ordinary developers. Instead of shipping code to a server and hoping the dependencies matched, you shipped the entire runtime environment as an image. Kubernetes (2014, GA 2015) solved the "I have 500 containers, now what?" problem with automated scheduling, scaling, and self-healing.

What It Looked Like

# Dockerfile — your entire runtime, declared
FROM node:14-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
# Build, push, deploy
docker build -t myapp:v1.2.3 .
docker push registry.example.com/myapp:v1.2.3
kubectl set image deployment/myapp myapp=registry.example.com/myapp:v1.2.3

Why It Was Better

  • Consistent environments from laptop to production
  • Density: run dozens of services on a single VM
  • Fast startup: seconds instead of minutes
  • Immutable deployments: never patch in place, replace
  • Microservices became practical (each service = own container)

Why It Wasn't Enough

  • Kubernetes complexity became a meme for good reason
  • Container networking was a new domain to master
  • Persistent storage in containers was painful
  • Teams needed new skills: YAML engineering, Helm templating
  • The "you need a platform team" tax was real

Legacy You'll Still See

Docker and Kubernetes are the current mainstream. Most greenfield projects start here. But many organizations are still migrating from VMs to containers. Docker Compose persists for local development even in K8s shops. "We're moving to Kubernetes" is still an active project at thousands of companies.


Era 5: Managed Kubernetes and PaaS (~2018-2023)

The Solution

Running your own Kubernetes control plane was painful and expensive. Cloud providers launched managed services — EKS (2018), GKE (already 2015), AKS (2018) — that handled the control plane. Simultaneously, modern PaaS offerings like Heroku (reborn), Render, Railway, and Fly.io offered container-based deployment without any Kubernetes exposure.

What It Looked Like

# EKS — managed K8s, you manage the nodes
eksctl create cluster --name prod --region us-west-2 --nodes 5

# Or Fargate — managed K8s, AWS manages the nodes too
eksctl create cluster --name prod --region us-west-2 --fargate

# Or skip K8s entirely — Render
# git push, automatic build and deploy
# render.yaml
services:
  - type: web
    name: myapp
    env: node
    buildCommand: npm ci && npm run build
    startCommand: npm start

Why It Was Better

  • No more etcd backup anxiety
  • Control plane upgrades handled by the provider
  • Fargate/Cloud Run: no nodes to manage at all
  • PaaS options gave small teams production-grade deploys without K8s expertise
  • Focus shifted from infrastructure to application concerns

Why It Wasn't Enough

  • Managed K8s still required deep K8s knowledge for workloads
  • Multi-cloud K8s was a fantasy for most organizations
  • Cost management became a discipline unto itself (FinOps)
  • PaaS offerings had sharp edges at scale
  • Platform teams still needed to build internal developer platforms on top

Legacy You'll Still See

This is the current mainstream for most companies. EKS, GKE, and AKS clusters run most cloud-native workloads. The tension between "just use K8s" and "just use a PaaS" is a live debate in every engineering org choosing its stack.


Era 6: Serverless and Edge (~2020-2025)

The Solution

AWS Lambda (2014, but mainstream adoption ~2020), Cloudflare Workers (2018), and Deno Deploy (2021) pushed the abstraction further: don't think about servers, containers, or clusters. Write a function, deploy it, pay per invocation. Edge computing moved execution to CDN points of presence, reducing latency to single-digit milliseconds globally.

What It Looked Like

// AWS Lambda handler
export const handler = async (event) => {
  const userId = event.pathParameters.id;
  const user = await db.getUser(userId);
  return {
    statusCode: 200,
    body: JSON.stringify(user),
  };
};
# serverless.yml
service: user-api
provider:
  name: aws
  runtime: nodejs18.x
functions:
  getUser:
    handler: handler.handler
    events:
      - http:
          path: users/{id}
          method: get

Why It Was Better

  • True pay-per-use: zero traffic = zero cost
  • No capacity planning for bursty workloads
  • Operational burden approaches zero for simple use cases
  • Edge deployment gives global low-latency without multi-region infra
  • Teams can ship features without any infrastructure knowledge

Why It Wasn't Enough

  • Cold starts remain a real problem for latency-sensitive apps
  • Vendor lock-in is near-total (Lambda code is portable; the ecosystem around it is not)
  • Debugging distributed serverless is hard
  • Cost at scale can exceed containers (the per-invocation model has crossover points)
  • Stateful workloads and long-running processes don't fit the model

Legacy You'll Still See

Serverless is growing but hasn't replaced containers. Most organizations use a mix: serverless for event-driven glue, containers for core services, VMs for legacy workloads. The "serverless vs containers" debate is ongoing, and the answer is usually "both."


Where We Are Now

The industry runs on a spectrum from bare metal to serverless, and most organizations use multiple points on that spectrum simultaneously. The dominant pattern is managed Kubernetes for core services, serverless for event-driven workloads, and VMs for legacy applications. The dream of "one abstraction to rule them all" has not materialized. FinOps (cloud cost management) has emerged as a critical discipline because the ease of provisioning makes it easy to waste money.

Where It's Going

WebAssembly (Wasm) is the most credible candidate for the next runtime abstraction — faster cold starts than containers, stronger sandboxing, and truly portable across cloud and edge. Platforms like Fermyon and Cosmonic are betting on this. Meanwhile, AI workloads are pulling some teams back toward bare metal (GPU clusters) while pushing others toward serverless inference endpoints. The pendulum between "give me control" and "just run my code" will keep swinging.

The Pattern

Every generation trades control for convenience, then a subset of users demands the control back. The winning abstraction is not the one that hides the most — it's the one whose leaks are manageable for the widest set of use cases.

Key Takeaway for Practitioners

Don't chase the newest abstraction reflexively. Choose the layer where your team can debug problems at 2 AM. If you can't explain what happens when your function times out, you're probably one abstraction too high.

Cross-References