A small bash CLI that wraps the Heroku CLI to run commands against every app in a pipeline stage.
Replaces the older Elixir version of this tool.
- macOS or Linux
bash(any version that ships with the OS is fine)- The
herokuCLI, installed and authenticated (heroku login)
curl -fsSL https://raw.githubusercontent.com/DefactoSoftware/heroku-scripts/main/install.sh | shOr manually:
curl -fsSL https://raw.githubusercontent.com/DefactoSoftware/heroku-scripts/main/bin/heroku-scripts -o ~/.local/bin/heroku-scripts
chmod +x ~/.local/bin/heroku-scriptsThe heroku CLI must be authenticated. heroku-scripts picks credentials in
this order:
HEROKU_API_KEY— if already set, it is used as-is, soHEROKU_API_KEY=… heroku-scripts …always works.- 1Password — if
HEROKU_SCRIPTS_OP_REFholds a 1Password secret reference andHEROKU_API_KEYis not set, the key is read once via the 1Password CLI (op) and reused for every call. - Otherwise the heroku CLI's own stored login (
heroku login) is used.
If your heroku credentials are brokered by 1Password (shell plugin / desktop
app), every heroku call triggers an interactive approval and account
selector. pipeline-cmd runs heroku in parallel, backgrounded subshells with
no controlling terminal, so those prompts can't be answered and the run stalls.
Resolving the key once, up front sidesteps this: 1Password approves a single
time and the exported key flows to every child process.
Store your Heroku API key in 1Password, then point the script at it (add this to your shell profile):
export HEROKU_SCRIPTS_OP_REF="op://Private/Heroku/credential"Get the reference from the 1Password app (right-click a field → Copy Secret
Reference) or op item get "Heroku" --format json.
Prefer not to configure the script? Use op run instead — no env var needed:
HEROKU_API_KEY="op://Private/Heroku/credential" op run -- heroku-scripts apps my-pipe stagingheroku-scripts apps <pipeline> <stage>
heroku-scripts pipeline-cmd <pipeline> <stage> "<heroku command>" [--concurrency=N] [--no-stream] [-a] [--table|--csv]
heroku-scripts pipeline-task <pipeline> <stage> <MixTask> [--concurrency=N]
heroku-scripts promote <app> <to-team> <pipeline> [--dry-run] [--yes]Run heroku-scripts help for the full command list.
List every app in the staging stage of the my-pipe pipeline:
heroku-scripts apps my-pipe stagingSet a config var on every staging app:
heroku-scripts pipeline-cmd my-pipe staging "config:set EMAIL_SENDER=noreply@example.com"pipeline-cmd prints one record per app. On a terminal it renders an aligned
table; when the output is piped or redirected it switches to CSV
(appname;output) so it stays easy to parse and grep. Force either with
--table or --csv.
# on a terminal
appname | output
---------------+-----------------------------------------
my-app | EMAIL_SENDER: noreply@example.com
my-app-worker | EMAIL_SENDER: noreply@example.com
# piped
appname;output
my-app;EMAIL_SENDER: noreply@example.com
my-app-worker;EMAIL_SENDER: noreply@example.com
Records stream out as each app finishes (in completion order), so output
appears progressively instead of all at once at the end — table mode included,
since the column width comes from the app list. Pass --no-stream to
withhold output until every app finishes and print it sorted by app name —
useful for reproducible, diff-friendly output.
Apps whose output is empty (e.g. config:get for a var that isn't set) are
skipped by default, and a count of skipped apps is printed to stderr. Pass
-a/--all to include them:
heroku-scripts pipeline-cmd my-pipe production "config:get ADFS_METADATA_URL"
# ...only apps that have the var...
# 18 app(s) with empty output skipped (use -a/--all to include them)The output field is the app's raw combined heroku output, so it may span multiple lines and contain semicolons. Treat the stream as something to read or grep, not as strict CSV.
Run a mix task on every production app, four at a time:
heroku-scripts pipeline-task my-pipe production GiveRaiseToPeople --concurrency=4Move an app and its -staging sibling to another team and pipeline:
heroku-scripts promote my-app my-team my-pipepromote runs destructive, largely irreversible operations, so it prints what
it will do and asks for confirmation first. Pass --dry-run to preview the
exact heroku commands, or --yes to skip the prompt.
Static analysis runs through ShellCheck and the test suite through bats; both run in CI on every push:
shellcheck bin/heroku-scripts install.sh
bats testThe tests stub the heroku CLI on PATH, so they never touch a real account.