Tools
Use top-level tools when a workflow depends on portable command-line binaries whose versions matter. Dagu installs the declared tools before the DAG run starts, prepends the resolved tool directory to PATH for that DAG run, and then runs your steps.
This keeps the workflow definition reproducible: the DAG records the tools it needs instead of relying on whatever happens to be installed on a worker.
Powered by aqua
Dagu's tools feature is powered internally by aqua from the aquaproj project. This lets Dagu install pinned, reproducible CLI tools while still shipping as a single binary. You do not need to install the aqua CLI separately.
Quick Example
tools:
- jqlang/jq@jq-1.7.1
steps:
- id: inspect
run: jq --version
- id: transform
run: jq '.items[] | .name' data.json
depends: inspectThe package is installed before the first step runs. The jq binary is then available to every host command step through PATH.
When to Use Tools
Use tools for portable CLIs where the exact binary version affects correctness, security, or reproducibility:
- JSON/YAML processors such as
jqandyq - linters, formatters, release helpers, and code generators
- converters and small build utilities available through the aqua registry
- profiling or diagnostic tools such as
pprof
Do not use tools for commands that intentionally depend on user or worker preconfiguration, local profiles, plugins, login state, or credentials. Treat those commands as worker prerequisites instead. Examples include gcloud, cloud CLIs with local profiles, and AI agent CLIs such as Claude Code, Codex, Gemini CLI, OpenCode, or Aider.
If a CLI can be installed as a pure binary but still needs credentials, tools only handles the binary. Provide credentials separately with environment variables or secrets.
Syntax
For the common case, use package shorthand:
tools:
- jqlang/jq@jq-1.7.1
- mikefarah/yq@v4.45.1Each entry is:
<aqua-package>@<version-or-ref>provider is optional and defaults to aqua.
Pin Versions
Every package must have a pinned version or ref. The floating value latest is rejected.
For release-asset packages, the version is usually a release tag:
tools:
- jqlang/jq@jq-1.7.1For packages that support source refs, prefer an immutable commit SHA:
tools:
- google/pprof@d04f2422c8a17569c14e84da0fae252d9529826bTags are convenient, but a commit SHA is a stronger reproducibility boundary when the package supports it.
Package Object Form
Use object form when you need an explicit display name or command list:
tools:
packages:
- package: jqlang/jq
version: jq-1.7.1
commands: [jq]Fields:
| Field | Required | Description |
|---|---|---|
package | Yes | Aqua package name, for example jqlang/jq. |
version | Yes | Pinned aqua version, release tag, or supported source ref. |
commands | No | Command names exposed to steps. Omit for the common case; Dagu infers commands from aqua registry metadata. |
name | No | Display name. Defaults to the first command or the package basename. |
registry | No | Registry name for the package. Defaults to the configured registry. |
commands must contain executable names only, not paths or shell fragments. For example, jq is valid; bin/jq and jq --version are not.
Registry
When registry is omitted, Dagu uses its pinned standard aqua registry commit. This gives a stable registry snapshot without making every DAG repeat the registry ref.
You can override the standard registry ref:
tools:
registry:
type: standard
ref: 5e2f56743d66abe9dfc7c56d35086511b7dc92d8
packages:
- jqlang/jq@jq-1.7.1For a custom registry file hosted in GitHub contents, use github_content:
tools:
registry:
type: github_content
repo_owner: example
repo_name: aqua-registry
ref: 9f73f3c0b6a3b2f6f8f2db3c77d8f2f79e420f5a
path: registry.yaml
packages:
- example/tool@v1.2.3For custom registries, registry.ref is required. Prefer a commit SHA for immutability.
Runtime Behavior
Before the DAG starts, Dagu:
- Generates an aqua config for the DAG's
tools. - Computes a toolset hash from the tool declaration and worker platform.
- Installs the tools into the worker-local data directory.
- Writes a manifest of resolved commands.
- Prepends the toolset
bindirectory toPATHfor that DAG run.
The cache layout is:
<tools-dir>/aqua/root/
<tools-dir>/aqua/locks/
<tools-dir>/aqua/envs/<os>-<arch>/<toolset-hash>/tools-dir defaults to {paths.data_dir}/tools and can be changed with paths.tools_dir or DAGU_TOOLS_DIR.
In distributed shared-nothing mode, each worker uses its own local tools directory. A worker installs the toolset the first time it runs a DAG requiring it, then reuses the cache for later runs with the same platform and toolset hash.
Cache hits reuse the manifest and command shims without taking an install lock. When a toolset must be prepared, Dagu uses worker-local locks for the toolset environment, missing registry cache entries, cold aqua-proxy bootstrap, and overlapping package/version/platform installs. Independent toolsets with disjoint packages can prepare in parallel on the same worker.
Sub-DAGs
tools is scoped to one DAG run. A sub-DAG is a separate DAG run, so it does not inherit the parent DAG's managed tool environment.
This applies to child DAGs started with action: dag.run and child DAGs queued with action: dag.enqueue.
If a child DAG uses a managed command, declare tools in the child DAG too:
steps:
- action: dag.run
with:
dag: child_parse
---
name: child_parse
tools:
- jqlang/jq@jq-1.7.1
steps:
- id: parse
run: jq '.items[]' input.jsonIf the parent also runs jq in its own steps, declare the same tool in the parent as well. This is not a runtime duplication problem: the worker cache is keyed by platform and toolset hash, so the same package is reused on the same worker after installation.
This rule also applies to inline sub-DAGs in the same YAML file. Each YAML document is its own DAG definition for tool declarations.
Packaged actions follow the same rule. If an official or third-party action uses external CLIs, put top-level tools in the action workflow file, not in dagu-action.yaml. The caller DAG's tools are not inherited across the action boundary, and the action manifest accepts only apiVersion, name, dag, inputs, and outputs.
In distributed shared-nothing mode, the worker that executes the child DAG prepares the child DAG's tools in that worker's local data directory. Different workers maintain independent caches, and later runs on the same worker reuse the local cache.
Current Limitations
DAG tools currently apply to host-executed command steps. They are not supported yet with:
- DAG-level
container - step-level
container docker.runorcontainer.runk8s.runorkubernetes.runssh.run
For those execution modes, install the needed binary inside the container image, Kubernetes image, or remote host instead.
Troubleshooting
If a command is not found:
- confirm the package exists in the selected aqua registry
- confirm the package supports the worker OS and architecture
- use object form with
commandsif Dagu cannot infer the executable name - check that the step runs on the host, not in a container, Kubernetes job, or SSH session
If the worker has no network access, pre-warm the worker cache by running the DAG once in an environment with registry and package download access.
