A small Bash CLI for managing Runpod infrastructure: network volumes, serverless endpoints, pods, templates, registries, billing, account balance, Hub listings, SSH keys, and live GPU stock. It speaks three Runpod APIs directly — REST API v2 for CRUD, GraphQL for account/hub/ssh/S3-datacentre stock, and the S3-compatible API for filling volumes.
rp is a personal, Bash-based wrapper around Runpod's APIs — not a replacement
for the official runpodctl. I built it
because a shell CLI is easy to extend and to wrap other tooling around — and
because a few workflows (notably volume sync, plus catalog and cluster)
and its single-key auth model aren't covered by runpodctl. Where commands overlap with runpodctl, the shared
flag spellings are accepted as a convenience only.
- Bash 5+
- Core tools (
curl,jq,awk,head,paste) —rpchecks for these on startup and names anything missing awsCLI — only forrp volume sync/rp volume ls(S3 fill and list)huggingface-cli— optional, only forrp volume sync --models
macOS or Linux (extracts into ~/.rp and symlinks /usr/local/bin/rp, asking
for sudo only for that symlink):
curl -fsSL https://raw.githubusercontent.com/objctp/runpod-cli/main/install.sh | bashThe installer verifies a SHA-256 checksum before extracting. Update later with
rp upgrade (or re-run the one-liner); pin a version with
curl ... | bash -s -- --version 0.1.0.
rp also checks for a newer release once a day and prints a one-line notice when
one is available (it names the right command for your install method). Set
RP_NO_UPDATE_CHECK=1 to disable the check.
macOS ships Bash 3.2, but
rpneeds Bash 5+. The installer detects this and refuses with the fix (brew install bash, then restart your shell).
For development (clone + symlink, no download required):
make install # symlinks bin/rp onto /usr/local/bin/rp (may need sudo)# or, without touching /usr/local/bin:export PATH="$PWD/bin:$PATH"The one-line installer above is the primary, recommended path. If you already
live in a package manager, rp is also published to Homebrew and npm:
brew install objctp/tap/rp # macOS / Linux (Homebrew)
npm install -g @objctp/rp # Node 22+ (wraps the same bash CLI)Both pull the same universal tarball and pin to the released rp-VERSION.tar.gz
checksum; brew rewrites the shebang to a Bash 5+ and npm runs a startup
preflight asserting Bash 5+/jq/curl.
Confirm it works:
rp version # installed version
rp _ping # ok: REST auth works (https://api.runpod.io/v2)The simplest path is rp auth login, which stores your key in a per-user
config (${XDG_CONFIG_HOME:-$HOME/.config}/rp) that survives any install method,
including npm global installs. Multiple accounts are supported — each is a
separate file, with one marked active:
rp auth login # prompts for the key; stored as "default"
rp auth login --name work --api-key <key># named account, marked active
rp auth list # show accounts
rp auth switch work # change the active account
rp auth status # show the active account + key source--account <name> on any command uses a specific account for that call.
Alternatively, set the variables directly (an exported RUNPOD_API_KEY always
wins over the stored config):
| Variable | Where to get it | Required for |
|---|---|---|
RUNPOD_API_KEY | Console → Settings → API Keys | everything |
RUNPOD_API_KEY_FILE | path to a file holding the key (e.g. a mounted K8s secret) | everything (alternative to RUNPOD_API_KEY) |
RUNPOD_S3_ACCESS_KEY | Console → Settings → S3 API Keys (your user_… id) | volume sync / ls |
RUNPOD_S3_SECRET_KEY | Console → Settings → S3 API Keys (an rps_… key, shown once) | volume sync / ls |
HF_TOKEN | huggingface.co → Access Tokens | volume sync --models (gated models) |
RP_MODEL_CACHE | any writable directory | volume sync --models download cache (default $RP_ROOT/.cache/models) |
The S3 key pair is separate from the REST API key — create it from its own
console page. rp auth login writes the key file at mode 600 (dir 700); the
CLI also warns if a manually-placed .env is group- or world-readable.
A representative tour — check stock, provision shared storage, run a pod, and stand up a scale-to-zero serverless endpoint:
rp stock dc # pick a datacentre, e.g. EU-RO-1
rp volume create --name shared-data --size 100 --dc EU-RO-1
rp pod create --name dev --image nvcr.io/nvidia/pytorch:23.10-py3 \
--gpu NVIDIA L4 --volume-gb 100
rp serverless create --name my-endpoint --template <id> \
--network-volume shared-data --gpus-from-volume shared-data \
--workers-min 0 --workers-max 3 --idle 600
rp serverless run <id> --input '{"prompt":"hello"}'# submit a job--network-volume auto-scopes the endpoint to that volume's datacentre;
--gpus-from-volume builds the GPU fallback list from types in stock, and the
volume mounts at /runpod-volume on serverless workers. To deploy from a Hub
listing instead of a template, use serverless create --hub-id <listing-id>
(resolve listings with rp hub search / rp hub get).
For command reference and worked examples, use rp doc (in-shell) or the docs
site — both are generated from the same source as the CLI, so they never fall
behind a release:
docs/- In-shell:
rp --help,rp <resource> --help,rp doc <command> [<verb>]
- S3 fill needs an S3-API-supported datacentre —
rp stock dcreads thes3apiEnabledflag live, so its S3 column is always current;rp volume createwarns if--dcis not S3-capable (andvolume syncrefuses). - Catalog stock is account-wide, not per-datacentre —
rp volume gpusand--gpus-from-volumereflect global availability; the volume's own datacentre still gates whether provisioning actually succeeds. volume sync --modelsis a double hop (HuggingFace → local cache → S3). Fine for once-and-rarely fills; use--source <dir>for a single hop.- Idempotent creates —
volume,template, andserverlesscreatereturn the existing resource when the name matches; pass--forceto duplicate.podandregistrycreatealways POST and may duplicate on re-run. rp apiis a raw escape hatch —rp api <METHOD> <path>calls the same transport as the resource verbs, so it reaches any v2 route the typed commands don't wrap yet (e.g. a brand-new endpoint). It honours--body,--plane,--jq,--limit, and--cursor.- List paging is client-side — Runpod's REST v2 has no server-side pagination
yet, so
--limit/--cursorslice the already-fetched list locally. The flags mirror the cursor shape Runpod will add, so they forward server-side later without a CLI change. - Stock and prices drift — re-run the CLI before each booking.
- runpodctl aliases — several commands also accept
runpodctl-style flag spellings (e.g.--gpu-id,--data-center-ids) alongsiderp's own. Runrp doc <command> <verb>to see which apply. --insecure(alias-k, orRP_INSECURE_TLS=1) — skips TLS certificate verification for in-pod runs where the CA bundle can't validate the API. Traffic stays encrypted; the server identity is not checked. Distinct fromRP_ALLOW_INSECURE_HTTP, which refuses plaintexthttp://.- Scriptable exit codes —
0success ·1transport/API/general ·2usage ·3auth (no key/creds) ·4not-found. Branch on$?rather than grepping stderr.
Lint, format, and tests are wired up via make:
make fmt # shfmt (2-space indents)
make lint # shellcheck
make test# bashunit (tests/)
make check # lint + testSee CONTRIBUTING.md for the repo layout, code style, and how to add a command or library helper.