← back to blog
Cloud Foundry passing · 5 January 2026 · 5 min read

The Leader Election That Was Looking for a Sidecar That Wasn't There

Cloud FoundryKubernetesPlatform Engineering

A Cloud Foundry application was logging a steady stream of the same error, on a loop:

java.net.ConnectException: Failed to connect to localhost/127.0.0.1:80
	at io.kubernetes.client.openapi.ApiClient.execute
	at io.kubernetes.client.extended.leaderelection.resourcelock.ConfigMapLock.get
	at io.kubernetes.client.extended.leaderelection.LeaderElector.tryAcquireOrRenew
msg: "Error retrieving resource lock leader-election-my-app"

Nothing in the app’s own configuration pointed at localhost:80 for anything. That’s what made it worth tracing rather than dismissing.

The library was doing exactly what it was designed to do

The stack trace names a Kubernetes client library’s leader-election implementation — the pattern where multiple replicas of a component race to acquire a lock (backed by a Kubernetes ConfigMap or Lease) so exactly one of them acts as the active leader at a time. That’s a completely standard, sensible pattern.

Inside an actual Kubernetes pod, this works because the client library talks to the Kubernetes API through a well-known local path — either the in-cluster service account token and DNS name, or, in some setups, a sidecar proxy listening on localhost that forwards to the real API server. Either way, “the Kubernetes API is reachable from inside this container” is an assumption baked into the environment, not something the application code sets up itself.

Cloud Foundry doesn’t have that environment

A Cloud Foundry app is a buildpack-run process in a container with none of that scaffolding — no in-cluster service account, no local API proxy, no kubernetes.default.svc. The leader-election library wasn’t misconfigured; it was doing precisely what it does inside Kubernetes, in a runtime that simply doesn’t have the thing it’s reaching for. ConnectException: Connection refused on localhost:80 is exactly what you’d expect when nothing is listening there at all — which nothing was, because nothing was ever meant to be.

Why this is worth remembering beyond this one case

The interesting failure here isn’t the specific library or the specific port. It’s that portable code carries the assumptions of the platform it was written for, and some of those assumptions are invisible until the code runs somewhere else. A leader-election dependency is an unusually clean example because it fails loudly and specifically — but the same shape of bug shows up with:

  • Libraries that assume a service mesh sidecar is always present for mTLS or retries.
  • Health-check frameworks that assume they can read /var/run/secrets/kubernetes.io/... to determine identity.
  • Anything using “auto-detect the platform” logic that has a Kubernetes branch and no meaningful fallback for everything else — it doesn’t error out cleanly, it takes the Kubernetes branch anyway and fails downstream in a way that has nothing obviously to do with “wrong platform.”

None of these show up in a code review unless the reviewer specifically knows to ask “what does this library assume is already running alongside it?” — a question that’s easy to skip when the library’s own README is written entirely from inside a Kubernetes worldview, which most cloud-native Java libraries are.

The practical takeaway

When you see a component reaching for localhost for something that clearly isn’t local application logic — a lock, a token, a metadata endpoint — treat that as a platform-assumption flag before treating it as a config bug. The fix is rarely “configure the right localhost address.” It’s usually “this dependency doesn’t belong in this runtime at all,” which is a very different, and much faster, conversation to have once you know that’s the question.