Internal tooling
RunPod GPU Pod Dashboard: Start/Stop Buttons for On-Demand Dev GPUs
A one-page dashboard that turns a handful of RunPod API calls and SSH commands into two buttons - Start and Stop - so spinning up a GPU box for a developer doesn't need a manual runbook every time.
Source code not published
Why I built this
Our company keeps a RunPod account around so developers can get a GPU box on demand - spin one up, run a training job or test a model, then let it go. The problem was never starting a pod; the RunPod console and API make that easy enough. The problem was everything around it:
- Starting a pod meant running the right API call with the right template/GPU settings, then waiting for it to come up, then manually wiring in the developer's SSH key before handing over the connection details.
- I wanted a consistent start/stop workflow alongside connection details. RunPod's console also supports Stop; this dashboard brings that action into the team's workflow. Stopping releases compute resources, but retained storage can still incur charges. See RunPod's Pod management documentation.
None of that is hard, but it's exactly the kind of small, repetitive, error-prone task that shouldn't need a person keeping the steps in their head. So I built a small local dashboard: one card per pod, a Start button and a Stop button, live status, and the SSH connection details shown right there the moment the pod is ready.
What it looks like
Each configured pod gets its own card. A stopped pod just shows the two buttons; once it's started, the card fills in with live connection details and a ready-to-copy SSH command - no more digging through the RunPod console to find the IP and port RunPod assigned this time:

Running state - IP, port, hourly cost, and a copy-to-clipboard SSH command, all populated automatically once the pod actually comes up:

Stopped state - just the pod's identity and a Start button. Nothing to configure, nothing to remember:

(Pod IDs, IPs, and usernames shown are placeholder demo data - this dashboard sits in front of a company RunPod account, so real identifiers aren't shown here.)
How it's put together
The whole thing is one Python file using only the standard library - http.server for the web
server, urllib for talking to RunPod's GraphQL API, and a background thread pushing live updates
to the page over Server-Sent Events. No pip install needed to run it, no Flask, no requests.
Which pods exist and who they belong to is just a small JSON file, so adding a third or fourth pod
slot is a two-line edit, not a code change.
Flow: clicking "Start"
- The button fires a
POST /api/start/:slot, which hands off to a background thread and returns immediately - the click doesn't block the UI while RunPod spins the pod up. - The worker calls RunPod's
podResumemutation, then pollspod(podId)every few seconds untildesiredStatus == RUNNINGand a runtime with assigned ports actually exists. - Once it's up, the worker reads back the public IP/port RunPod assigned for SSH, then connects
through RunPod's own SSH proxy (
ssh.runpod.io, authenticated with an account-level key) to drop the developer's public key into~/.ssh/authorized_keys- this works even on a completely fresh pod with no prior SSH setup, because the proxy authenticates independently of anything on the pod itself. - Every state change - "waiting", "running", "error" - is pushed to the browser over SSE, so the card updates live without the page ever needing to poll or refresh.
Stopping a pod is the same shape in reverse and much simpler: podStop is called directly against
the RunPod API, the card clears its connection details, and the badge flips back to "Stopped" - no
SSH session, no manual shutdown command, and no risk of reaching for Terminate by mistake.
What it can actually do
- Start / Stop per pod - each pod defined in
pods.jsongets its own card with independent state; starting one doesn't touch the others. - Live progress, not a spinner - the card shows exactly what's happening ("Resuming pod...", "Waiting for pod... (40s)", "Configuring SSH keys...") instead of a generic loading indicator.
- Automatic SSH key setup - no more manually SSH-ing in to create
.ssh/authorized_keysbefore handing a pod to a developer; the dashboard does it as part of the start flow. - One-click connection details - IP, port, and hourly cost shown as soon as the pod is ready,
with copy-to-clipboard for both the
ip:portpair and the full SSH command. - Background status sync - a periodic check against the RunPod API keeps card state accurate even if a pod was started or stopped from somewhere else (like the RunPod console directly).
- Zero-install, double-click launch - packaged with a small
.batlauncher and a generated desktop shortcut/icon, so running it doesn't require anyone to open a terminal or install a single package - it only needs Python itself.
What I took away from this
The interesting part of this project wasn't RunPod's API - it's a straightforward GraphQL endpoint
with a couple of mutations. It was noticing that the actual friction was procedural, not technical:
a repeatable workflow was useful alongside the platform console; the earlier workflow involved
SSH-ing in and shutting it down by hand every single time. Wrapping that one API call in a button
removed a small recurring chore entirely instead of just writing it down as a runbook step. It was
also a good exercise in restraint - reaching for http.server and Server-Sent Events instead of
pulling in Flask and a frontend framework kept the whole tool to one file that runs anywhere Python
does, with nothing to install and nothing to go out of date.
