I do most of my Grafana development on a headless VM running on my home server — always on, available over Tailscale when remote, and the heavy builds run on plugged-in hardware instead of draining laptop battery.
The workflow is: connect to the VM, start the dev server, tunnel port 3000 back to hit localhost:3000 in a local browser.
Two small tools make this easier: sshp for persistent
shell sessions, and tunnelctl for managing tunnels by
name.
For persistent shell sessions, I use shpool — to borrow its tagline, think tmux, then aim lower. Sessions survive disconnects, so I can close my laptop, reconnect later, and pick up where I left off.
To make connecting easier, I've wrapped it in a convenience function. The syntax sshp grafana+dev-vm connects to host dev-vm and attaches to (or creates)
the session named grafana. If no + is present, it falls through to regular ssh:
if [[ "$1" == *+* ]]; then
local session="${1%+*}"
local host="${1#*+}"
shift
ssh -t "$host" "shpool attach '$session' || shpool create '$session'" "$@"
else
ssh "$@"
fiManages named SSH tunnels with persistent configuration. Running tunnelctl up grafana prompts for connection details the first time, then remembers them for subsequent runs.
When starting a tunnel, we first check if a metadata file exists for this name. If so, we source it to get the previous values:
# Load previous values as defaults
if [[ -f "$metafile" ]]; then
source "$metafile"
default_host="$host"
default_local_port="$local_port"
default_remote_port="$remote_port"
fiWe then prompt for each parameter using gum, pre-filling with the defaults. On subsequent runs, hitting enter accepts the previous config:
host=$(gum input --prompt "Host: " --value "$default_host")
local_port=$(gum input --prompt "Local port: " --value "$default_local_port")
remote_port=$(gum input --prompt "Remote port: " --value "$default_remote_port")Finally, we start the tunnel in the background, save the PID for later cleanup, and write the current values back to the metadata file:
ssh -N -L "${local_port}:localhost:${remote_port}" "$host" &
echo "$!" > "$(_tunnel_pidfile "$name")"
echo "host=$host\nlocal_port=$local_port\nremote_port=$remote_port" > "$metafile"