Workflow Basics
Learn the fundamentals of writing Dagu workflows.
Your First Workflow
Details: write your first workflow and run it.
Create hello.yaml:
steps:
- run: echo "Hello from Dagu!"Run it:
dagu start hello.yamlWorkflow Structure
Details: typical workflow and top-level fields.
A typical workflow uses these top-level fields:
# Metadata
description: Process daily data
group: Analytics
labels: [etl, production]
# Run control
schedule: "0 2 * * *"
working_dir: /srv/dagu/data-pipeline
# Defaults for every step
defaults:
retry_policy:
limit: 2
interval_sec: 30
timeout_sec: 600
# Runtime inputs
params:
- name: DATE
eval: "`date +%Y-%m-%d`"
default: "2026-03-14"
# Environment
env:
- RUN_DATE: ${params.DATE}
# Tool versions
tools:
- astral-sh/uv@0.11.14
# Steps
steps:
- id: process
run: uv run --python 3.13.9 python process.py "${params.DATE}" "${env.RUN_DATE}"
# Handlers
handler_on:
failure:
run: notify-error.shAdd defaults when several steps share retry, timeout, environment, or precondition settings.
Steps
The basic unit of execution.
Details: step fields.
Step Names
Details: step identity.
Step names are optional. When omitted, Dagu automatically generates names based on the action:
steps:
- run: echo "First step" # Auto-named: cmd_1
- run: | # Auto-named: cmd_2
echo "Multi-line"
echo "Script"
- id: explicit_name # Explicit name
run: echo "Third step"
- action: http.request # Auto-named: http_4
with:
method: GET
url: https://api.example.com
- action: template.render # Auto-named: template_5
with:
template: "Hello, {{ .name }}!"
data:
name: Dagu
- action: dag.run # Auto-named: dag_6
with:
dag: child-workflowShell Commands
Details: shell command steps.
Use run for shell commands and scripts:
tools:
- astral-sh/uv@0.11.14
steps:
- run: echo "Hello World"
- run: ls -la
- run: uv run --python 3.13.9 python script.pyThis is equivalent to:
tools:
- astral-sh/uv@0.11.14
steps:
- id: step_1
run: echo "Hello World"
- id: step_2
run: ls -la
depends: step_1
- id: step_3
run: uv run --python 3.13.9 python script.py
depends: step_2Multiple Commands
Details: multiple shell commands.
Multiple commands share the same step configuration:
tools:
- nodejs/node@v22.21.1
steps:
- id: build_and_test
run: |
npm install
npm run build
npm test
env:
- NODE_ENV: production
working_dir: /app
retry_policy:
limit: 3
interval_sec: 10Instead of duplicating env, working_dir, retry_policy, preconditions, container, etc. across multiple steps, combine commands into one step.
Commands run in order and stop on first failure. Retries restart from the first command.
Trade-off: You lose the ability to retry or resume from the middle of the command list. If you need granular control over individual command retries, use separate steps.
For non-shell work, use an explicit action and put action-specific inputs under with.
Multi-line Scripts
Details: script behavior.
tools:
- astral-sh/uv@0.11.14
steps:
- run: |
#!/bin/bash
set -e
echo "Processing..."
uv run --python 3.13.9 python analyze.py data.csv
echo "Complete"If you omit shell, Dagu uses the interpreter declared in the script's shebang (#!) when present.
Shell Selection
Details: configure the shell.
Set a default shell for every step at the DAG level, and override it per step when needed:
shell: /bin/bash # Default shell for the whole workflow
shell_args: ["-e", "-u"] # Default shell args for every run step
steps:
- id: bash_task
run: echo "Runs with bash -e -u"
- id: zsh_override
run: echo "Uses zsh instead"
with:
shell: /bin/zsh # Step-level overrideThe shell value accepts either a string ("bash -e") or an array (["bash", "-e"]). Use shell_args when you want to keep the shell command and its default arguments separate. Arrays avoid quoting issues when you need multiple flags.
When you omit a step-level shell, Dagu runs through the DAG shell (or system default) and automatically adds -e on Unix-like shells so scripts stop on first error. If you explicitly set shell on a step, include -e yourself if you want the same errexit behavior.
tools:
- astral-sh/uv@0.11.14
steps:
- run: |
import pandas as pd
df = pd.read_csv('data.csv')
print(df.head())
with:
shell: uv run --python 3.13.9 pythonDependencies
Details: execution order.
tools:
- astral-sh/uv@0.11.14
steps:
- id: download
run: wget data.csv
- id: process
run: uv run --python 3.13.9 python process.py
depends: download
- id: upload
run: aws s3 cp output.csv s3://bucket/
depends: processParallel Execution
Details: parallel execution and parallel with dependencies.
You can run steps in parallel using explicit dependencies:
steps:
- id: setup
run: echo "Setup"
- id: task1
run: echo "Task 1"
depends: setup
- id: task2
run: echo "Task 2"
depends: setup
- id: finish
run: echo "All tasks complete"
depends: [task1, task2]Working Directory
Details: working_dir field and quickstart example.
Set working_dir at the DAG level when most steps run from the same project directory. Relative paths resolve from the DAG file directory.
working_dir: /home/user/project
tools:
- astral-sh/uv@0.11.14
steps:
- id: process
run: uv run --python 3.13.9 python main.py
- id: inspect_input
working_dir: /data/input
run: ls -laUse step-level working_dir only for the steps that need a different directory.
Defaults
Details: step defaults and merge rules.
Use defaults for settings that most steps should share. A step can override the default when it needs different behavior.
defaults:
retry_policy:
limit: 3
interval_sec: 10
timeout_sec: 300
env:
LOG_LEVEL: info
steps:
- id: fetch
run: ./fetch.sh
- id: publish
run: ./publish.sh
retry_policy:
limit: 1
interval_sec: 60
depends: fetchEnvironment Variables
Details: DAG-level variables, step-level variables, and variable expansion.
Define environment variables at DAG-level or step-level:
env:
- API_KEY: secret123
- ENV: production
steps:
- id: dev_test
run: echo "Running in $ENV"
env:
- ENV: development # Overrides DAG-levelTIP
Dagu filters system environment variables for security. See Environment Variables for details on filtering, inheritance, and .env file support.
Capturing Output
Details: output basics, output file format, and output names.
Store command output in variables:
steps:
- id: get_version
run: |
printf 'version=%s\n' "$(git rev-parse --short HEAD)" >> "$DAGU_OUTPUT_FILE"
outputs:
- name: version
- id: build
run: docker build -t "app:${steps.get_version.outputs.version}" .
depends: get_versionBasic Error Handling
Details: error handling.
Continue on Failure
Details: continue_on syntax and failure behavior.
steps:
- id: optional_step
run: maybe-fails.sh
continue_on:
failure: true
- id: always_runs
run: cleanup.sh
depends: optional_stepSimple Retry
Details: step retry policy and retry backoff.
steps:
- id: flaky_api
run: curl https://unstable-api.com
retry_policy:
limit: 3
interval_sec: 10Timeouts
Details: step timeouts and timeout limits.
Prevent steps from running forever:
steps:
- id: long_task
run: echo "Processing data"
timeout_sec: 300 # 5 minutesStep Descriptions
Details: step identity fields.
Document your steps:
tools:
- astral-sh/uv@0.11.14
steps:
- id: etl_process
description: |
Extract data from API, transform to CSV,
and load into data warehouse
run: uv run --python 3.13.9 python etl.pyLabels and Organization
Details: label YAML formats and metadata fields.
Group related workflows:
labels:
- reports
- customer
- daily
group: Analytics # UI groupingSee Also
- Control Flow - Conditionals and loops
- Data & Variables - Pass data between steps
- Error Handling - Advanced error recovery
- Parameters - Make workflows configurable
