Back

Client work

MetricHost, a game-server hosting platform

A game studio wanted to sell server hosting to its players. They had a one-page concept and nobody to build it. My partner and I designed and built the platform and took it to a production-ready system in Europe and the US. The code belongs to the studio, so this page explains how it works without showing it.

Role
Platform engineer, with a partner
Since
Dec 2025
Regions
EU and US
Stack
Spring Boot · Kafka · k3s · Go
Published

The idea

Idle servers cost money

A game server with nobody on it still holds all of its memory, and memory is what the hosting bill is made of.

When a server goes quiet it winds down. When a player joins, it wakes up in under a second, and the player doesn't notice. The memory it gave back goes to servers that are busy. That brought the cost of an idle server down to about a third.

Ready-made panels like Pterodactyl and AMP keep idle servers running at full cost, and they run as one big process you can't scale piece by piece.

How it fits together

The architecture

Each part is kept apart from the others. A billing deploy never restarts a game server, a modded game server can't reach the billing service, and the admin console has its own login.

Players connect straight to their game server through a TCP proxy, so game traffic never waits behind a dashboard request.

The dashboard and APIs sit behind one gateway, in front of the Spring Boot services. They talk to each other through Kafka-compatible event streams (Redpanda), so something like deleting an account reaches every service without them calling each other.

Everything runs on k3s in two regions, with no ports open to the internet. Traffic comes in through Cloudflare tunnels. Inside the cluster, Cilium network policies decide which workloads may talk to each other, based on workload identity.

We ran our own Kubernetes on rented servers instead of a managed cloud service, because the managed services don't let a machine use disk as overflow memory, and the cost savings above depend on it.

The control plane

Adding machines automatically

Adding a machine takes several steps. It has to be created, booted, joined to the cluster, locked down and marked ready, and any of those can fail.

A small control plane in Go runs that sequence as a durable workflow on Temporal. My partner wrote most of it. If it crashes halfway, it carries on from the same step when it comes back, so no half-built machine is left behind.

For a while, a bug upstream crashed about three in ten machines on their first boot. The workflow deletes the broken machine and builds a new one, up to a fixed number of tries. First-boot failures went from about 30 percent to a few percent.

It adds machines when players are actually waiting for servers, and keeps one spare ready for a rush. Because it can spend money on its own, it has a budget limit, a cap on machines, and a cooldown between scale-ups.

Decisions

Four choices and what they cost

  1. Idle servers wind down in steps.

    First the CPU drops to almost nothing while the memory stays, which wakes in well under a second. If the server stays empty, its cold memory moves to fast local disk while it keeps running, which wakes in a few seconds.

    TradeoffEach step frees more memory and takes longer to wake, so every game sets its own limits. The proxy holds a returning player's connection while the server comes back.

  2. Each region's database is the source of truth.

    Gateways find servers through one shared global index, but each region has its own event stream. So the regional service writes to the index itself, after the server is safely saved.

    TradeoffIf that write fails it's logged and retried on the server's next status change, never rolled back. Until then players can't be routed to that server, but it can't be lost either.

  3. The admin console needs two people for impersonation.

    It's read-only by default and backed by two database users, one that can only read and one that can write to exactly two tables. Impersonating a user needs one person to ask and another to approve, and refunds are limited to billing roles and logged.

    TradeoffIt's slower for the operators, and every write needs a fresh second factor.

  4. US traffic stays in the US.

    Things like the console, the file manager and live metrics feel broken with a transatlantic round trip on every click. So operations on a server go straight to the region that owns it.

    TradeoffLogin, billing and the server index stay global so a session works everywhere. The seam between global and regional data is the cross-region write above.

Numbers

What changed

~1/3the cost of an idle server after wind-down
<1sto wake a lightly idle server
30%→few %first-boot failures after the rebuild-on-failure workflow
25→8 minbackend CI time after the rework

Sources

  1. Architecture, provisioning workflows, idle-server wind-down and the first-boot failure fix. The idle cost and CI time figures are from my own measurements on the project.the public MetricHost repoaccessed 2026-11-11
  2. Durable workflows that resume after a crashdocs.temporal.ioaccessed 2026-11-11