Karthik Menon

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.

Python (stdlib only)http.server + Server-Sent EventsRunPod GraphQL APISSH (RunPod SSH proxy)JSON config

Repository link coming soon

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.
  • Stopping one was worse: RunPod's web console only exposes Terminate, which destroys the pod outright. There's no "pause it, keep it, stop billing" button in the UI at all. The only way to actually stop a pod without losing it was to SSH in and shut it down from the terminal - or call RunPod's API directly, which most people don't know exists or reach for.

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:

RunPod dashboard showing one running pod card with SSH connection details and one stopped pod card

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

Close-up of a running pod card with connection info and SSH command

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

Close-up of a stopped pod card showing only the Start/Stop controls

(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"

  1. 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.
  2. The worker calls RunPod's podResume mutation, then polls pod(podId) every few seconds until desiredStatus == RUNNING and a runtime with assigned ports actually exists.
  3. 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.
  4. 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.json gets 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_keys before 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:port pair 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 .bat launcher 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: the platform's own console simply didn't expose a safe way to stop a pod, so the fallback was 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.