Everyone said "just use managed Kubernetes." We said no, and saved millions. This is the story of how a counterintuitive infrastructure bet paid off bigger than anyone expected, and the specific, unglamorous work that made it possible.

Why Bare Metal? The Hyperscaler Math

When you run Kubernetes on AWS or GCP, you're paying for three things: compute, the managed control plane markup, and data transfer (the silent killer). For an e-commerce platform with a million storefronts, data transfer fees alone were running $15K a month. You're also paying the hyperscaler's margin on top of hardware that costs them a fraction of what they charge you.

Bare metal eliminates the markup. You pay for the server, the colo space, and the bandwidth. That's it. But you give up something real: the cloud provider does a lot of invisible work that you now have to do yourself. For us, the most important piece of that invisible work was global routing.

The BGP Design: 9 Data Centres, One IP Prefix

The architecture we built uses BGP Anycast: the same /24 IP prefix announced from 9 different Points of Presence: Mumbai, Singapore, Frankfurt, Dubai, São Paulo, Tokyo, New York, London, Sydney. The internet's BGP routing protocol naturally directs each user to the topologically nearest announcement. No GeoDNS, no latency-based routing policies, no application layer tricks. The routing happens at the network layer, handled by 30-year-old infrastructure that powers the global internet.

BGP Anycast is how DNS root servers work. It's how Cloudflare's 200+ locations share a handful of IPs. We borrowed the same pattern — the difference is we had two people instead of a dedicated network team.

The routing daemon was BIRD. It's been on production networks since 1998, has meticulous documentation, and a parser that will refuse to start if you misplace a semicolon. Here's the core config that ran at every PoP:

# BIRD config — identical file, 9 locations
# The IP prefix is the same everywhere. BGP does the rest.

protocol bgp upstream_isp {
  local as 65001;
  neighbor 198.51.100.1 as 65000;   # this PoP's upstream ISP router
  ipv4 {
    import none;
    export filter {
      if net ~ [203.0.113.0/24] then accept;  # our Anycast /24
      reject;
    };
  };
  hold time 90;
  keepalive time 30;
}

MetalLB in BGP Mode: Kubernetes LoadBalancers Without a Cloud

On AWS, when you create a Kubernetes LoadBalancer service, the cloud-controller-manager calls the AWS API and provisions an ALB for you. On bare metal, there's no magic. You need something to assign IPs and make them routable. That's what MetalLB does in BGP mode.

MetalLB peers with BIRD on each node. When you kubectl apply a LoadBalancer service, MetalLB picks an IP from your Anycast pool and tells BIRD to start announcing it. The service becomes globally reachable via BGP in seconds. No cloud API. No managed load balancer. Just routing.

apiVersion: metallb.io/v1beta2
kind: BGPPeer
metadata:
  name: bird-router
  namespace: metallb-system
spec:
  myASN: 65001
  peerASN: 65001
  peerAddress: 127.0.0.1   # BIRD on the same node
  peerPort: 179

---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: anycast-pool
  namespace: metallb-system
spec:
  addresses:
    - 203.0.113.0/24   # our Anycast VIP range

---
apiVersion: metallb.io/v1beta1
kind: BGPAdvertisement
metadata:
  name: anycast-advert
  namespace: metallb-system
spec:
  ipAddressPools:
    - anycast-pool
  communities:
    - 65000:100   # no-export community — don't leak to peers

The DC7 Incident: What Happens When You Get BGP Community Wrong

Six weeks into the rollout, we pushed a BIRD config change to DC7 (our Dubai PoP) to add a new BGP community tag that we wanted upstream ISPs to respect as a traffic-engineering signal. The community value was valid syntax. The logic was correct. The problem: our Dubai ISP used that specific community value internally to mean "redistribute this route to our entire upstream peering mesh."

Our /24 started appearing in BGP tables it had no business being in. Traffic from unexpected regions started arriving at DC7, a PoP sized for Dubai traffic, suddenly absorbing Pakistani, Iranian, and Central Asian traffic because the ISP was redistributing our announcement. DC7's CPU spiked. NGINX started queueing. Latency climbed. We had no idea why, for about 8 minutes.

We caught it in 8 minutes via anomaly detection on per-PoP traffic volume. Fix: withdraw the community tag, reload BIRD. The ISP withdrew the spurious redistribution. Total duration: 11 minutes. We spent the next hour documenting every ISP's community string reservations before touching BIRD config on any other PoP.

ISP BGP community strings are not standardized. What 65000:100 means to one ISP is completely different from what it means to another. Always get the community documentation from your upstream before using any community values in production.

BGP Timer Tuning: The Detail That Matters

BGP sessions have a "hold timer": if keepalives stop arriving for this long, the session dies and routes are withdrawn. The default in many implementations is 90 seconds, with keepalives every 30. That means three missed keepalives before a session dies.

One of our ISPs had a non-standard default: 30-second hold timer, 10-second keepalives. Under normal conditions, fine. Under a brief burst of network jitter, our keepalive missed their window and the session dropped, pulling all our routes from that PoP. Traffic failed over to the next nearest PoP (fine), but we'd created an unnecessary blip. After that we explicitly set hold times in every BIRD config to match what we'd negotiated with each ISP. No more surprises from that.

Production Results

94%
Cost reduction vs AWS
<50ms
TTFB globally
99.9%
Uptime over 12 months
15min
New PoP provisioning time

Who Should Do This (and Who Shouldn't)

Do this if: you're paying significant cloud bills for workloads with predictable capacity, you have engineers who understand BGP or can learn it, and your users are spread across geographies where latency to a single cloud region is measurably bad.

Don't do this if: you need elastic capacity that spikes unpredictably, your team has no network engineering experience, or your traffic is concentrated in one region where a single cloud PoP is already close enough. The operational overhead is real. BIRD configs don't manage themselves. Hardware fails and someone has to deal with it at 2am.

The cost reduction only makes sense if your team can run BGP safely. If not, Cloudflare or AWS Global Accelerator do this work for you at a markup. Once you can do it yourself, that markup becomes optional.