Run-that-app is a minimalistic cross-platform application runner. It executes native CLI tools on Linux, macOS, Windows, and BSD without requiring a prior installation. The primary use case is running developer tools (linters, analyzers, formatters, etc) in scripts and CI pipelines.
Installing small developer tools at pinned versions across multiple operating systems is a surprisingly hard problem without a good solution.
Run-that-app sidesteps the problem entirely: instead of installing tools, it focuses on running them. For most development workflows, that's what you actually care about.
Run-that-app is intentionally minimalistic and non-invasive. It ships as a single stand-alone binary. Following the principle "perfection is achieved not when there is nothing left to add, but when there is nothing left to take away", run-that-app works without:
- application shims
- shell integrations
- hard links
- environment variables
- configuration files and settings
- dependencies
- plugins
- custom packaging and container formats
- a dedicated install step
- application repositories
- Docker
- WASM
- system daemons
- sudo
- emulation
- IDE plugins
- any other kind of bloat
Applications are downloaded directly from their original hosting location, typically in 1-2 seconds. Only the executable is stored on disk. Execution is 100% native, with no runtime overhead.
- actionlint
- alphavet
- conc
- contest
- cucumber-sort
- deadcode
- depth
- dprint
- exhaustruct
- funcorder
- gh
- ghokin
- go
- goda
- gofmt
- gofumpt
- golangci-lint
- goreleaser
- govulncheck
- ireturn
- keep-sorted
- mdbook
- mdbook-linkcheck
- node-prune
- node
- npm
- npx
- rclone
- ripgrep
- scc
- shellcheck
- shfmt
- staticcheck
- taplo
- tikibase
- uv
Linux and macOS:
curl https://raw.githubusercontent.com/kevgo/run-that-app/main/download.sh | shWindows (Powershell):
Invoke-Expression (Invoke-WebRequest -Uri "https://raw.githubusercontent.com/kevgo/run-that-app/main/download.ps1" -UseBasicParsing).ContentThe installer places the run-that-app executable into the current directory. To install elsewhere, execute the installer from that directory.
Compile from source:
cargo install --git https://github.com/kevgo/run-that-app
rta [run-that-app arguments] <app name>[@<app version>] [app arguments]Run actionlint at version 1.6.26:
The app version should contain only the version number (e.g. 1.6.26), even if
the Git tag is prefixed (e.g. v1.6.26).
Run-that-app arguments must appear before the name of the application to run. The application name is the first argument that does not start with a dash. All following arguments are passed through to the application.
Run ShellCheck version 0.9.0 with arguments
--color=always myscript.sh:
rta [email protected] --color=always myscript.shrta --appsNot all applications support all platforms. If binaries aren't distributed for
your platform, run-that-app can compile applications from source. If that
doesn't work, the --optional flag skips unsupported applications without
failing the command.
Example: run ShellCheck only if it is available on the current platform:
rta --optional [email protected] myscript.shThe --available command reports availability via its exit code.
The --which command prints the path to the resolved executable.
Example: run go vet with alphavet as a custom vet tool, but only if
alphavet is available:
rta --available alphavet && go vet "-vettool=$(rta --which alphavet)" ./...Some tools (e.g. deadcode)
report findings via stdout but exit with status code 0. The --error-on-output
treats any output as failure.
rta --error-on-output deadcodeShow the 10 most recent versions of an application:
rta --versions actionlintLimit the output to a specific number:
rta --versions=3 actionlintIf precompiled binaries are available (e.g. via GitHub releases), run-that-app use them. If not, it can compile applications from source.
To enforce compilation from source even when binaries exist:
rta --from-source <app>Run-that-app supports a configuration file named run-that-app, using the
asdf version file format:
actionlint 1.6.26
shellcheck 0.9.0
With this file in place, you no longer need to be specify the version explicitly:
rta actionlintThe file name intentionally differs from asdf and mise to avoid interference.
Add an application at its latest version (creates the config file if needed):
rta --add actionlint
Update all configured applications to their latest versions:
rta --update
Run-that-app can reuse tools already installed on your system. The executable
must be present in the PATH, and the version must be declared as system.
go system 1.21.3
This prefers the system-installed Go. If none is found, Go 1.21.3 is installed and used.
You can restrict acceptable versions for the externally installed app:
go [email protected].* 1.21.3
Some tools define their version in project files (e.g. Go via go.mod). Setting
the version to auto enables automatic detection:
go auto
Run-that-app emits ANSI colors if STDOUT and STDERR are connected to a TTY. You can override this behavior using the following environment variables:
CLICOLOR_FORCE=1always enables color output, even when not writing to a TTYNO_COLOR=1disables color output entirely
Some tools are distributed as part of another toolchain. In these cases, specify the version of the bundling application.
npm and npx are provided by Node.js. To use them, specify a Node version:
npm 20.10.0
To run an npm that was installed externally, provide its own version:
You can combine both declarations:
npm [email protected] 20.10.0
This prefers an existing npm ≥ 10.2, otherwise installs Node 20.10.0 and uses
the npm version that comes with it.
Gofmt is bundled with Go. Specify the Go version:
gofmt 1.21.6
This installs Go 1.21.6 and uses its bundled gofmt.
Example Makefile integration:
RTA_VERSION = 0.28.0 # version of run-that-app to use
# an example Make target that uses run-that-app
test: tools/rta@${RTA_VERSION}
tools/rta actionlint
# this Make target installs run-that-app if it isn't installed or has the wrong version
tools/rta@${RTA_VERSION}:
@rm -f tools/rta*
@mkdir -p tools
@(cd tools && curl https://raw.githubusercontent.com/kevgo/run-that-app/main/download.sh | sh)
@mv tools/rta tools/rta@${RUN_THAT_APP_VERSION}
@ln -s rta@${RUN_THAT_APP_VERSION} tools/rtaAdd tools/rta* to .gitignore.
Adding a new application is straightforward. See the developer documentation.
If it works for you, do it. In practice, package managers introduce issues:
- Different OSes use different package managers. You would need to support Homebrew, Nix, Scoop, Chocolatey, winget, DNF, pacman, apt, pkg, snap, zypper, xbps, portage, etc.
- Some environments like Windows or bare-bones Docker images don't have a package manager.
- Not all tools are packaged everywhere.
- Packaged application versions are often out of your control.
- Different projects often require different tool versions that would need to be installed in parallel.
Docker solves a different problem: shipping full runtime environments. For development tooling, it often adds unnecessary complexity and bloat:
- extra OS layers (especially on Windows and macOS)
- significant storage and memory overhead
- Docker-in-Docker issues on CI
- no help with CPU architecture mismatches
- no solution for binaries hosted on GitHub releases
Cross-platform Bash scripts quickly become fragile:
- they depend on external tools (
curl,tar,zip) - they and the external tools they use can behave differently across systems
- they don't work natively on Windows
Run-that-app is effectively a cross-platform Bash script, written in a strongly typed programming language with predictable behavior.
Run-that-app can compile from source. If that fails, it can gracefully degrade.
Run-that-app can run the package managers and runtimes for these ecosystems, which then run the respective tool.
Open an issue. Many cases are solvable.
That marketplace is in the source code. This avoids:
- weak schema-based configuration languages
- version skew between runner and marketplace
- synchronization overhead at runtime
It also enables fully deterministic tooling in locked-down environments.
These other cross-platform package managers might be a better fit for your use case.
Asdf is a mature cross-platform tool runner based on Bash (now also Go). It relies on plugins and shims and does not support Windows well.
Run-that-app is faster, simpler, and Windows-native.
Mise is a Rust-based successor to asdf with broader scope (env vars, tasks, shell integration).
Run-that-app is much simpler and focuses on doing one thing well.
Pkgx provides a polished UX, shell integration, and an app store.
Run-that-app trades polish for simplicity, determinism, and flexibility, including source builds and conditional execution.
