Manage a directory of binaries without a package manager.
curl -sSfL https://get.anchore.io/binny | sudo sh -s -- -b /usr/local/bin... or, you can specify a release version and destination directory for the installation:
curl -sSfL https://get.anchore.io/binny | sudo sh -s -- -b <DESTINATION_DIR> <RELEASE_VERSION>Keep a configuration in your repo with the binaries you want to manage. For example:
# .binny.yaml
tools:
- name: gh
version:
want: v2.33.0
constraint: < v3
method: github-release
with:
repo: cli/cli
- name: quill
version:
want: v0.4.1
constraint: < v0.5
method: github-release
with:
repo: anchore/quill
- name: chronicle
version:
want: v0.7.0
method: github-release
with:
repo: anchore/chronicleThen you can run:
binny install [name...]to install all tools in the configuration (or the given tool names)binny checkto verify all configured tools are installed, return exit code 1 if any are missing or inconsistentbinny update [name...]to update any pinned versions in the configuration with the latest available versions (and within any given constraints)binny listto list all tools in the configuration and the installed store
By default, tools are installed in a .tool directory in the current working directory. This can be configured via the store.root option (e.g., to use ~/.tool for a user-wide install).
Use --ignore-cooldown with install or update to bypass the release cooldown check.
You can add tools to the configuration one of two ways:
- manually, by adding a new entry to the configuration file (see the Configuration section below)
- with the binny add <method> commands, which will handle the configuration for you
The configuration file is a YAML file with a list of tools to manage. Each tool has a name, a version, and a method for installing it. You can optionally specify a specific method for checking the latest version of the tool, however, this is not necessary as all install methods have a default version resolver.
Top-level options that apply to all tools:
| Option | Description |
|---|---|
cooldown |
A duration to wait after a version is published before it can be installed (e.g. 7d, 168h). This is a supply chain security measure that gives time for malicious versions to be detected and pulled. Individual tools can override this value. Only applies to install and update commands, and only with github-release and go-proxy version resolvers (the git resolver does not support cooldown). |
# .binny.yaml
cooldown: 7d
tools:
- name: gh
# ...Each tool has the following configuration options:
name: chronicle
version:
want: v0.7.0
constraint: <= v0.9.0
cooldown: 3d # optional: override the global cooldown for this tool
method: github-release
with:
# arbitrary key-value pairs for the version resolver method
method: go-install
with:
# arbitrary key-value pairs for the install method| Option | Description |
|---|---|
name |
The name of the tool to install. This is used to determine the installation directory and the name of the binary. |
version.want |
The version of the tool to install. This can be a specific version, or a version range. |
version.constraint |
A constraint on the version of the tool to install. This is used to determine the latest version of the tool to update to. |
version.cooldown (optional) |
A per-tool cooldown duration that overrides the global cooldown value (e.g. 3d, 0 to disable). Only applies when resolving the latest version during install or update. Not supported by the git version resolver. |
version.method |
The method to use to determine the latest version of the tool. See the Version Resolver Methods section for more details. |
version.with |
The configuration options for the version method. See the Version Resolver Methods section for more details. |
method |
The method to use to install the tool. See the Install Methods section for more details. |
with |
The configuration options for the install method. See the Install Methods section for more details. |
Install methods specify where the tool binary should be pulled or built from.
The github-release install method uses the GitHub Releases API to download the latest release of a tool. It requires the following configuration options:
| Option | Description |
|---|---|
repo |
The GitHub repository to reference releases from. This should be in the format <owner>/<repo> |
assets (optional) |
Regex pattern(s) to filter release assets. Can be a single string or array of strings for priority matching |
keep-archive-files (optional) |
Keep the whole extracted archive (at .tool/.binny-dirs/<name>/) and link .tool/<name> to the binary inside it. Useful for tools that read files next to their own binary (e.g. tsc and its lib/*.d.ts) |
When multiple assets match the OS/architecture, the assets field allows you to specify which one to select:
# single regex pattern
- name: hugo
method: github-release
with:
repo: gohugoio/hugo
assets: "^hugo_extended_[0-9]" # match hugo_extended but not hugo_extended_withdeploy
# multiple patterns (first match wins)
- name: tool
method: github-release
with:
repo: owner/tool
assets:
- "^tool_premium_[0-9]" # try premium version first
- "^tool_[0-9]" # fall back to standard versionThe default version resolver for this method is github-release.
The go-install install method uses go install to install a tool (go build for a local module, see below). It requires
the following configuration options:
| Option | Description |
|---|---|
module |
The FQDN to the Go module (e.g. github.com/anchore/syft) or a local path |
entrypoint (optional) |
The path within the repo to the main package for the tool (e.g. cmd/syft) |
ldflags (optional) |
A list of ldflags to pass to go install (e.g. -X main.version={{ .Version }}) |
args (optional) |
A list of args/flags to pass to go install (e.g. -tags containers_image_openpgp) |
env (optional) |
A list key=value environment variables to use when running go install |
The module option allows for a special entry:
.or./path/to/module/on/disk
A local module is built the same way as with go-build: go build runs from inside the module directory,
so it can live anywhere on disk: the repo you run binny from, a sibling checkout, a git submodule, or a
subdirectory of a monorepo. A local path starts with ./, ../ or / (or is absolute on windows); anything
else is a remote module path. Relative paths (here and in the git resolver's path) are resolved against the
directory binny is run from, same as the store root. binny fails early, naming the resolved path, if the
directory doesn't exist. ldflags, args and env are passed to go build instead of go install.
Since go runs from the module directory, that's also where it looks for a go.work and reads the go /
toolchain directives, not where binny was run from. If a go.work in a parent directory doesn't list the
module (common for a nested module in a monorepo), add GOWORK=off to env. With the default
GOTOOLCHAIN=auto, a module that asks for a newer toolchain than the one installed will download it.
- name: mytool
version:
want: current # the checkout's HEAD commit
method: git
with:
path: ../mytool
method: go-install
with:
module: ../mytool # a sibling checkout
entrypoint: cmd/mytoolnote: a local module's installed version is its resolved version (with want: current, the HEAD commit),
not the state of the working tree. Uncommitted edits don't change that version, so binny considers the tool
already installed and won't rebuild it. With want: current, commit the change; otherwise (or with a branch
name, which resolves to itself) remove the tool from the store (e.g. delete it from .tool/) to force a rebuild.
The ldflags allow for templating with the following variables:
| Variable | Description |
|---|---|
{{ .Version }} |
The resolved version of the tool (which may differe from that of the version.want value) |
In addition to these variables, sprig functions are allowed; for example:
ldflags:
- -X main.buildDate={{ now | date "2006-01-02T15:04:05Z07:00" }}For more information about sprig functions, see the sprig documentation.
The default version resolver for local modules is git, otherwise it is go-proxy.
The go-build install method builds a tool from source by cloning the repository and running go build.
Unlike go-install, this method honors replace directives in go.mod since the build happens within
the full repository context. This is useful for tools that rely on replace directives for local development,
forks, or patches.
| Option | Description |
|---|---|
module |
The FQDN to the Go module (e.g. github.com/anchore/syft) or a local path |
entrypoint (optional) |
The path within the repo to the main package for the tool (e.g. cmd/syft) |
ldflags (optional) |
A list of ldflags to pass to go build (e.g. -X main.version={{ .Version }}) |
args (optional) |
A list of args/flags to pass to go build (e.g. -trimpath) |
env (optional) |
A list key=value environment variables to use when running go build |
source (optional) |
How to obtain source code: git (default) clones the repository, go-proxy downloads via go mod cache |
repo-url (optional) |
Explicit git repository URL (auto-derived for github.com modules) |
The module option allows for a special entry:
.orpath/to/module/on/disk
When using a local path, no cloning or downloading occurs - the build runs directly in the specified directory,
honoring any replace directives in the local go.mod. Local paths behave as described for go-install above.
The ldflags, args, and env options support templating with the following variables:
| Variable | Description |
|---|---|
{{ .Version }} |
The resolved version of the tool (which may differ from the version.want value) |
In addition to these variables, sprig functions are allowed.
Source modes:
-
git(default): Clones the repository using git. For GitHub modules, the repository URL is automatically derived from the module path. For other hosts, use therepo-urloption to specify the git URL explicitly. Awantthat is a git ref is resolved against the repository itself (git ls-remote), whatever version method is configured, so the tool is rebuilt when the ref moves. The recorded version (and{{ .Version }}) is the full commit sha the ref points at. Accepted refs:- a branch or tag, including ones with
/(e.g.main,feature/x); tags win over a branch of the same name - a PR / MR head:
pull/123(GitHub) ormerge-requests/45(GitLab), or spelled out aspull/123/head - any full ref name, e.g.
refs/pull/123/head HEAD, the repository's default branch- a commit sha (kept as written, full or short: it can't move; a full sha is never looked up, so a branch or tag named after it can't redirect the build)
A name shaped like a prefixed version (e.g.
bun-v1.4.3,release-1.30) is tracked when the repo says it's a branch, and treated as a version (left to the version method) when it's a tag. To build a branch from a fork, pointrepo-urlat the fork and setwantto the branch. Refs that can't be resolved fail, unless the version method isgo-proxywithallow-unresolved-version: true, which passes the ref through as-is. - a branch or tag, including ones with
-
go-proxy: Downloads the source viago mod download. This uses the Go module proxy cache and may be faster for publicly available modules, but does not support private repositories without proper GOPRIVATE configuration. Awantthat is a git ref is resolved by the go command the same way as forgo-install(seego-proxyunder version methods), so the recorded version is a pseudo-version.
Example configurations:
# Build from GitHub source (URL auto-derived)
- name: mytool
version:
want: v1.2.3
method: go-build
with:
module: github.com/owner/repo
entrypoint: cmd/mytool
# Build with custom ldflags
- name: mytool
version:
want: v1.0.0
method: go-build
with:
module: github.com/owner/repo
entrypoint: cmd/mytool
ldflags:
- -X main.version={{ .Version }}
- -X main.commit=abc123
env:
- CGO_ENABLED=0
# Build from go proxy source instead of git
- name: mytool
version:
want: v1.0.0
method: go-build
with:
module: github.com/owner/repo
source: goproxy
# Build from non-GitHub host with explicit repo URL
- name: mytool
version:
want: v1.0.0
method: go-build
with:
module: gitlab.com/owner/repo
repo-url: https://gitlab.com/owner/repo.gitThe default version resolver for local modules is git. For GitHub modules, the default is github-release.
For other remote modules, the default is go-proxy.
The hosted-shell install method uses a hosted shell script to install a tool. It requires the following configuration options:
| Option | Description |
|---|---|
url |
The URL to the hosted shell script (e.g. https://raw.githubusercontent.com/anchore/syft/main/install.sh) |
args (optional) |
Arguments to pass to the shell script (as a single string) |
If the URL refers to either github.com or raw.githubusercontent.com then the default version resolver is github-release.
Otherwise, the version resolver must be specified manually.
The version method specifies how to determine the latest version for a tool.
The git version method will use a git repo on disk as a source for resolving versions via tags. It requires the following configuration options:
| Option | Description |
|---|---|
path |
The path to the git repository on disk |
The version.want option allows a special entry:
current: use the current commit checked out in the repo
note: this method is still under development. Currently it is most useful for tools that are being used where that are developed:
- name: binny
version:
# since the module is . then "current" means whatever is checked out
want: current
method: go-install
with:
# aka: github.com/anchore/binny, without going through github / go-proxy (stay local)
module: .
entrypoint: cmd/binny
ldflags:
- -X main.version={{ .Version }}
- -X main.gitCommit={{ .Version }}
- -X main.gitDescription={{ .Version }}
# note: sprig functions are available: http://masterminds.github.io/sprig/
- -X main.buildDate={{ now | date "2006-01-02T15:04:05Z07:00" }}The github-release version method uses the GitHub Releases API to determine the latest release of a tool. It requires the following configuration options:
| Option | Description |
|---|---|
binary |
Binary to select if there are multiple within the release archive (defaults to the tool name) |
repo |
The GitHub repository to reference releases from. This should be in the format <owner>/<repo> |
The version.want option allows a special entry:
latest: don't pin to a version, use the latest available
Tags with a prefix before the semver portion (e.g. bun-v1.4.3 or cli/v2.0.0) are updated too. Only tags with
the same prefix are considered, and constraints apply to the version after the prefix. If no newer tag with that
prefix can be found, the pin is left as-is. With latest, the prefix of the repo's latest release is used.
Note: this approach will require a GitHub API token to be set in the GITHUB_TOKEN environment variable if there
is a version constraint or release cooldown used, or when a prefixed pin's repo has a latest release with a different
prefix (e.g. a monorepo releasing cli/v2.0.0 and lib/v1.0.0).
The go-proxy version method asks the go command (go list -m) to determine the latest version of a Go module, so it
follows the same GOPROXY, GOPRIVATE/GONOPROXY, GOSUMDB and auth settings that go install does, including any
set in the tool's env (templated entries, any containing {{, are left out of the lookup). It requires the following
configuration options:
| Option | Description |
|---|---|
module |
The FQDN to the Go module (e.g. github.com/anchore/syft) |
allow-unresolved-version |
Skip resolving latest or a git ref and pass it to go install as-is |
The version.want option allows a special entry:
latest: don't pin to a version, use whatevergo install <module>@latestwould install (the newest release over a prerelease, skipping retracted versions, or a pseudo-version for a module with no tags)- a git ref (branch, tag, or commit sha): track that ref, e.g. to try an unreleased fix
tools:
- name: grype
version:
want: main
method: go-install
with:
module: github.com/anchore/grype
entrypoint: cmd/grypeA ref is resolved by the go command to the version it points at right now (a pseudo-version like
v0.0.0-20261015120000-abcdef123456 for a branch or commit), and that exact version is what gets installed and
recorded (it is also what {{ .Version }} renders as in ldflags, args, and env). Once the branch moves,
binny check fails for the tool (it no longer matches what is installed) and binny install picks up the new commit,
while binny update leaves want: main alone. This means check needs network access for ref-pinned tools, the
same as for latest. A module proxy caches branch lookups for a while, so a commit pushed moments ago may not show
up right away.
Some things to know about refs:
- refs the go command can't resolve (typos, deleted branches, modules it can't reach) fail with its error rather than
falling back to the literal ref, set
allow-unresolved-version: trueto pass the ref through instead - refs containing
/(e.g.feature/x,pull/123) aren't valid module versions, use thego-buildmethod (git source) for those - a version
constraintcan't be combined with a ref cooldownisn't enforced for refs (there is no older version to fall back to), binny warns when the ref points at a commit newer than the cooldowngo-buildwith the defaultgitsource resolves refs against the repository instead (seego-buildabove)
