User guide
You're a player — you want to play a game, or let your friends play. The infrastructure has already been provisioned, the desktop app is running, and the Discord bot is registered. This page covers the Discord slash commands and what you experience as a player connecting to a server.
For everything about running and operating the desktop app itself (dashboard, starting/stopping, watchdog config, cost view, logs, Discord bot setup, adding a game) see Using the app.
If none of those assumptions is true yet, start at the setup guide.
Two ways to drive it
| From | What you get | Who can do it |
|---|---|---|
| Desktop app | Full control: start/stop, edit watchdog knobs, view cost estimates, read live logs, manage Discord permissions. See Using the app. | Whoever can launch the app on your machine — there's no separate login, it's a local desktop app. |
| Discord slash commands | /server-start, /server-stop, /server-status, /server-list. Replies are ephemeral, so the channel doesn't get spammy. | Whoever is in the admin list or the relevant per-game entry. |
Both talk to the same AWS resources. You can start a server from Discord and stop it from the app, or vice versa. Status always reflects reality.
Discord slash commands
All four are /server-*. Replies are ephemeral (only the invoker sees
them).
/server-start <game>
Autocomplete filters the game list by what you've typed and by what you're
allowed to run. Press enter and you'll see a deferred ack within 3 s, then
an edit to "starting …", and finally (1–5 min later) a green
"running — {game}.yourdomain.com".
If a start for that game is already underway, from the desktop app or another /server-start, the reply is "A start for {game} is already in progress." and nothing is launched. The lease lasts 120 seconds, so a Stop followed by a Start inside that window gets the same reply.
Behind the scenes: interactions Lambda verifies the signature and kicks off
the followup Lambda; followup first takes a 120-second per-game start lease
(the same one a desktop Start takes), then runs ECS RunTask and writes a 15-minute
PENDING#{taskArn} row to DynamoDB; when EventBridge sees the task
transition to RUNNING, the update-dns Lambda upserts the A record and PATCHes
your original Discord message with the resolved hostname.
/server-stop <game>
Runs ecs.stopTask. The update-dns Lambda deletes the Route 53 record —
the same path for every game, HTTPS or not — when the resulting STOPPED
event fires. Confirmation edit usually within a few seconds.
/server-status [game]
One game if specified, otherwise every game you have status permission
on. The followup Lambda calls ListTasks + DescribeTasks and resolves
the ENI public IP if running.
/server-list
Same as /server-status with no game argument — shows everything you can
see.
Playing on a server
Once a game is RUNNING:
- DNS:
{game}.yourdomain.comresolves to the task's public IP after up todnsTtlseconds (default 30). - Ports: whatever the operator configured under
ports. UDP/TCP games connect directly to that port on the resolved hostname. HTTPS games (https = true) connect over 443 athttps://{game}.yourdomain.com— TLS terminates in-task via a Caddy sidecar with an automatically-issued Let's Encrypt certificate, and the sidecar proxies to the game'swebPort(never directly reachable); there is no load balancer involved anywhere in the path. An HTTPS game can also declare additional ports (for example a UDP voice port) that you connect to directly, subject to their visibility. The Discord connect message for an HTTPS game shows thehttps://URL rather than a port number. - Reconnect if the task restarts: the new task has a different public IP, but the DNS record is re-UPSERTed within seconds of RUNNING. Use the hostname, not the IP.
When the watchdog will stop you
The watchdog runs on an EventBridge schedule — default every 15 minutes — and for every running task it:
- Decides whether the task was idle over the last window, from exactly one
of two sources:
- A game that declares a
healthCheckis asked directly: the watchdog invokes the health-check Lambda, which probes the game server (for example, "are any players online?"). - A game with no declared
healthCheckis judged by network traffic: the watchdog reads the mean inbound bytes per second that ECS Container Insights reports for the game's task over the last window.
- A game that declares a
- If the task was idle — the health check said so, or the mean inbound rate
is below
watchdogMinBytesPerSecond— increments theidle_checkstag on the task. If the traffic data is missing or can't be read, the task counts as active, so a brand-new server is never stopped early. - If the counter reaches
watchdogIdleChecks, issuesStopTaskwith reasonWatchdog: idle for N minutes. The watchdog only stops the task — it never touches DNS or any load balancer itself. The task stopping triggers an ECS state-change event that a separate Lambda (update-dns) reacts to by deleting the DNS record. - If there's activity, resets the counter to 0.
Container Insights is enabled on the game cluster to provide the traffic metric. It adds roughly one cent per running game-hour, and nothing while no game is running.
So a burst of actual traffic resets the clock; an empty server shuts down
after interval × idle_checks minutes (default 60, tunable by whoever
operates the app — see Using the app).
Managing the app
Everything about running the desktop app itself — the dashboard, starting and stopping servers from it, editing watchdog knobs, viewing cost estimates, reading live logs, configuring the Discord bot, and adding a second game — is covered in Using the app, with one page per screen: Dashboard, Games, Discord, Logs, Costs, Audit, and Settings.
Further reading
- Architecture — the full diagram
and the
/server-startsequence, end to end. - Lambdas — what each Lambda does on every invocation.
- Using the app — the desktop app you (or whoever operates your deployment) use to drive everything above.