The Minecraft Server That Kills Itself (to Save Me Money)
January 2026 · 8 min read
Here's the pitch I gave my friends: imagine a Minecraft server that never stops existing, but costs almost nothing. The world is always there. Your builds are always there. The server itself? It only exists while someone is actually playing. The rest of the time, it's just a disk in a datacenter, dreaming.
A regular VM running Minecraft 24/7 isn't expensive, exactly. Maybe €25 a month for something decent. But we play in bursts. A weekend of intense building, then three weeks of nothing. Paying for 720 hours a month to use 15 of them offended me more than the amount itself did.
Separating the world from the server
The trick that makes everything else work is realizing that "the server" is two different things. There's the world (the save files, the terrain, the half-finished castle) and there's the process serving it. The world needs to be permanent. The process doesn't.
The world is permanent. The process is disposable.
So the world lives on a 20 GiB persistent disk, with backups going to a Cloud Storage bucket in the same region. The process runs on a preemptible e2-medium VM that mounts that disk on boot. Preemptible machines are 60 to 70% cheaper because Google reserves the right to yank them whenever it needs capacity. That sounds terrible for a game server, until you notice that our killswitch shuts it down on purpose anyway. Getting occasionally preempted is just the killswitch firing early. I even set automatic_restart = false in the Terraform: if Google takes the machine, nothing resurrects it. Only a human pressing the button, or nothing at all, decides whether it comes back. The machine has no will to live, and that's a feature.
The startup script does everything, every time
Because the VM is disposable, the startup script has to be the whole truth. Every boot begins from a blank Ubuntu 22.04 and rebuilds the runtime from scratch. Install Docker. Authenticate against Artifact Registry. Then handle the disk with the paranoia the disk deserves: detect it, format it only if it has no filesystem, mount it at /mnt/disks/minecraft-data, and write the mount into /etc/fstab so a reboot inside the VM's lifetime doesn't lose it. The format-only-if-blank check is the line I re-read most carefully in the whole repo, for the obvious reason. The disk is the world, and the script that gives the server life is one bad conditional away from lobotomizing it.
Then it pulls the server image (itzg's excellent minecraft-server base with RCON enabled, 2 G of heap) and runs it with --restart unless-stopped, so a crash inside the container heals itself without any cloud machinery noticing. The container exposes three ports: 25565 for the game, 25575 for RCON, and 8000 for a small API we'll get to. A mid-session preemption, from a player's perspective, is a laggy minute followed by everyone reconnecting.
The killswitch
The part I'm most fond of is a bash script that lets the VM decide when to die. Inside the container, next to the Minecraft process, a loop polls the server over RCON every 30 seconds:
players=$(rcon-cli list | grep -oE "There are ([0-9]+) of" | grep -oE "[0-9]+")
if [ "$players" -gt 0 ]; then
last_active=$(date +%s)
fi
When the gap since last_active passes the idle limit (an hour by default, read fresh from the environment on every loop iteration so I can tune it without redeploying), the shutdown sequence runs. First rcon-cli save-all. Then thirty seconds of grace for the write to land. And then the line that makes people do a double-take:
gcloud compute instances stop $VM_NAME --zone $ZONE
The VM stops itself. For that to work, the machine's service account needs compute.instanceAdmin.v1 on its own project, the IAM equivalent of handing someone the keys to their own off-switch. It felt strange to write, and then immediately correct. Nobody knows the server is idle better than the server. The full life cycle, from doorbell to self-termination:
sequenceDiagram
participant F as Friend
participant CR as Cloud Run dashboard
participant VM as Preemptible VM
participant MC as Minecraft container
participant D as Persistent disk
F->>CR: GET /start (no auth needed)
CR->>VM: start instance
VM->>D: mount world at /mnt/disks
VM->>MC: docker run (RCON enabled)
F->>MC: play for hours
loop every 30s
MC->>MC: rcon-cli list → player count
end
Note over MC: 0 players for 1 hour
MC->>MC: rcon-cli save-all
MC->>D: world safely written
MC->>VM: gcloud compute instances stop (self)
Note over VM,D: VM gone, world remains.<br/>Billing: ~€1/month for the disk
A stopped VM bills for its disk and nothing else. Twenty gigabytes of pd-balanced storage costs about a euro a month. That's the "never stops existing" part of the pitch. The world persists for pocket change while the compute evaporates.
Waking it back up
A server that kills itself needs a doorbell, and I didn't want the doorbell to be "message me and I'll run a gcloud command." So there's a tiny FastAPI app on Cloud Run that acts as the control plane. Max scale 1, 512 MiB, 60-second request timeout, scales to zero like everything else in this project.
It serves a little dashboard, and the interesting endpoint design is in the auth asymmetry. /start requires no authentication at all. Anyone with the link can wake the server, because the worst thing a stranger can do with it is briefly cost me a few cents until the killswitch notices nobody joined. /stop and the raw /rcon proxy sit behind a bearer token though, because those can actually ruin someone's evening. Scoping the security effort to the endpoints where damage is possible kept the whole thing simple. Friends bookmark one URL, press one button, and by the time Minecraft has found the server in the multiplayer list, it's usually up. The dashboard polls /status while the machine boots, so there's even a little "STARTING" state to watch instead of refreshing blindly.
Two details in the plumbing earn their keep. First, the RCON proxying is two-hop by design. The Cloud Run app never holds the RCON password. It forwards commands to a separate little API inside the container (that port 8000), authenticated with its own key, which runs rcon-cli as a subprocess and returns stdout, stderr and the return code as JSON. Each layer holds only the secret it needs. Second, because a stopped-and-started VM can come back with a different public IP, the control plane updates a DNS record via the Cloudflare API on boot. So the server address my friends have memorized keeps working, even though the machine behind it is technically a different lease on life each time.
What it actually costs
A month of typical usage, a few evenings and one big weekend, lands somewhere around two to four euros. Burst compute at preemptible rates, one persistent disk, backups, and a Cloud Run service that's asleep more than the VM is. The same setup running 24/7 on a standard VM would be roughly ten times that. The bill scales with fun had, which is the only fair way for a hobby to bill.
All of it is Terraform, which turned out to matter more than expected. The whole environment (firewall rules for the three ports, the service account with its self-termination role, the GCS bucket binding, the Cloud Run service with its env wiring) is one apply away from existing and one destroy away from not. When we lost interest for two months, I destroyed everything except the disk and the bucket. The world kept existing. The bill nearly didn't.
The twin in my living room
There's a second life for this idea. A variant of the same server runs on the k3s cluster in my living room, where the economics invert. At home, compute is a sunk cost because the NUC runs anyway, so the "killswitch" becomes a FastAPI controller that scales the Minecraft deployment to zero replicas instead of stopping a VM, and mc-router steers players between instances. Same philosophy, different substrate. The world is permanent, the process is disposable, and something small and deterministic is always watching for the moment nobody's playing. Of the two, the cloud version is the better party trick. The home version is the better neighbor, since nobody's electricity bill notices a sleeping deployment.
References
- itzg/docker-minecraft-server: The de-facto standard container image for running Minecraft. It handles EULA acceptance, memory flags, RCON setup and world persistence through environment variables, so the startup script configures a game server the same way it would any other twelve-factor app. Used because reinventing Minecraft ops in bash is a hobby I don't want.
- RCON: The remote-console protocol built into the Minecraft server. It lets external tools run server commands (list players, save the world) over a socket. It's the killswitch's eyes: polling
rcon-cli listevery 30 seconds is how the machine knows nobody is playing. - GCP preemptible VMs: Spare Google Cloud capacity at a 60-70% discount, with the catch that Google can reclaim the machine at any time. Perfect fit here, because a server designed to shut itself down anyway treats a preemption as the killswitch firing early. The discount is the whole cost model.
- Cloud Run: Google's serverless container platform, scale-to-zero included. It hosts the start/stop dashboard, which needs to exist 24/7 but run almost never. Serverless means the doorbell costs nothing while nobody is ringing it.
- Terraform: Infrastructure as code: the VM, disk, firewall rules, IAM roles and Cloud Run service are all declared in files, so the entire environment can be created or destroyed in one command. That's what made 'destroy everything except the world when we stop playing' a two-minute operation instead of a checklist.
- Cloudflare API: Programmatic DNS management. A stopped-and-restarted VM can come back with a new IP, so on boot the control plane updates the DNS record. The address friends memorized keeps working while the machine behind it changes.