Skip to content

About

TShock Terraria server on k3s with a wake-on-connect proxy that scales to zero when idle

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

Repository files navigation

lazy terraria server

TShock Terraria server on k3s, with a wake-on-connect proxy that scales the server to zero when nobody's playing.

Build proxy image Docker Image TShock k3s

The Terraria dedicated server busy-loops on the main thread and pegs a full CPU core whether anyone's online or not. This repo wraps it with a tiny Go proxy that listens on the public LoadBalancer, scales the StatefulSet on the first TCP connection, and scales back to zero after an idle window.


Contents


Architecture

The proxy uses the Kubernetes scale subresource on the StatefulSet — no custom CRDs, no operators.

Wake-on-connect flow

For a full write-up of how this works, see the blog post: Scale-to-zero Terraria server on k3s with a wake-on-connect proxy.

Quick start

# 1. Apply everything
kubectl apply -f k3s/

# 2. Watch first-boot world generation
kubectl -n terraria logs -f statefulset/terraria

# 3. Grab the external IP
kubectl -n terraria get svc terraria

Connect from the Terraria client to the LB IP on port 7777.

First connect after idle takes ~10–30s. The proxy is scaling the pod up and waiting for TShock to bind the port. The client's connect timeout is generous; if it gives up, just retry.

Repository layout

Path Purpose
k3s/ k3s manifests: namespace, PVCs, ConfigMap, StatefulSet (TShock), Deployment (proxy), Services, RBAC.
proxy/ Go source + Dockerfile for the wake-on-connect proxy.
.github/workflows/docker-publish-proxy.yml CI that builds and pushes the proxy image to Docker Hub.

Configuration

Server (TShock)

  • World autocreation — k3s/configmap.yaml (serverconfig.txt). Only used on first boot to seed the world; further changes belong in TShock's runtime config.
  • TShock runtime — /tshock/config.json on the tshock-config PVC. Generated on first boot:
    kubectl -n terraria exec -it statefulset/terraria -- sh
    # vi /tshock/config.json
  • Server-side characters / starting inventory — sscconfig.json in the ConfigMap (seeded to the PVC on first boot).
  • Plugins — drop .dll files into the tshock-plugins PVC (mounted at /plugins) and roll the pod.
  • World files — live on the terraria-worlds PVC (k3s local-path).

Proxy

Tuned via env vars on deployment/terraria-proxy:

Variable Default Description
LISTEN_ADDR :7777 Address the proxy listens on.
BACKEND_ADDR terraria-backend.terraria.svc.cluster.local:7777 Cluster-internal address it dials.
NAMESPACE terraria Namespace of the target StatefulSet.
STATEFULSET terraria StatefulSet to scale.
IDLE_TIMEOUT 10m Idle window before scaling to 0.
WAKE_TIMEOUT 120s How long to wait for the backend to accept TCP after scaling up.
CHECK_INTERVAL 30s How often the idle watcher runs.

Wake-on-connect proxy

The public terraria LoadBalancer selects app: terraria-proxy. When a TCP connection arrives, the proxy:

  1. PATCHes statefulset/terraria to replicas: 1 if it's at 0.
  2. Polls TCP terraria-backend:7777 until it accepts (or WAKE_TIMEOUT expires).
  3. Splices the client connection through.

When the active connection count hits 0 and stays there for IDLE_TIMEOUT, the watcher scales back to 0.

What the proxy does not do
  • It doesn't parse the Terraria protocol — any TCP connect (port scan, LB probe, accidental nc) will wake the server.
  • It doesn't gate by password or version — that's TShock's job.
  • It doesn't do graceful drain at scale-down. The pod gets a normal SIGTERM with terminationGracePeriodSeconds: 30, which TShock handles cleanly. Bump the grace period if your world is large.

Build the proxy image manually

cd proxy
docker build -t docker.io/timdoddcool/terraria-proxy:latest .
docker push docker.io/timdoddcool/terraria-proxy:latest

Or for a single-node k3s install, import directly into the local containerd:

docker save timdoddcool/terraria-proxy:latest -o proxy.tar
sudo k3s ctr images import proxy.tar

Operations

Console

kubectl -n terraria attach -it statefulset/terraria
# detach: Ctrl-P Ctrl-Q

Useful console commands: save, say, exit, plus all TShock commands.

First-boot admin setup

kubectl -n terraria attach -it statefulset/terraria
# look for: "SetupCode: 1234567"
# then in-game: /setup 1234567
# then in-game: /user add <name> <password> superadmin
# then in-game: /setup   (disables the setup code)

Disable the proxy temporarily

kubectl -n terraria scale deployment/terraria-proxy --replicas=0
kubectl -n terraria scale statefulset/terraria --replicas=1
kubectl -n terraria patch svc terraria --type=merge -p \
  '{"spec":{"selector":{"app":"terraria"}}}'

Force-flush before destructive ops

# from console
save
exit

CI / Docker Hub

.github/workflows/docker-publish-proxy.yml builds proxy/ and publishes to Docker Hub on every push to main (when proxy/** changes) and on v* tags.

Setup in GitHub:

  1. Settings → Secrets and variables → Actions → Variables — DOCKERHUB_USERNAME = timdoddcool.
  2. Settings → Secrets and variables → Actions → Secrets — DOCKERHUB_TOKEN from https://hub.docker.com/settings/security.
  3. Push to main.

Image tags published:

Trigger Tags
Push to main latest, sha-<short>
Tag v1.2.3 1.2.3, 1.2, sha-<short>

Upgrading

TShock

Bump image: ghcr.io/pryaxis/tshock:stable to a pinned tag in k3s/statefulset.yaml, then:

kubectl apply -f k3s/
kubectl -n terraria rollout restart statefulset/terraria

Proxy

Edit proxy/, push to main, the CI publishes :latest. Then:

kubectl -n terraria rollout restart deployment/terraria-proxy

License

MIT.

About

TShock Terraria server on k3s with a wake-on-connect proxy that scales to zero when idle

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages